Spring IoC&DI

目录

前言

一、Spring

[1.1 Spring是什么?](#1.1 Spring是什么?)

[1.2 什么是容器?](#1.2 什么是容器?)

[1.3 什么是IoC容器?](#1.3 什么是IoC容器?)

二、IoC介绍

[2.1 传统程序开发](#2.1 传统程序开发)

[2.2 问题分析](#2.2 问题分析)

[2.3 解决方案](#2.3 解决方案)

[2.4 IoC程序开发](#2.4 IoC程序开发)

[2.5 Ioc的优势](#2.5 Ioc的优势)

三、IoC详解

[3.1 bean的存储](#3.1 bean的存储)

[3.1.1 @Controller(控制器存储)](#3.1.1 @Controller(控制器存储))

[3.1.2 @Service(服务存储)](#3.1.2 @Service(服务存储))

[3.1.3 @Repository(仓库存储)](#3.1.3 @Repository(仓库存储))

[3.1.4 @Configuration(配置存储)](#3.1.4 @Configuration(配置存储))

[3.2 为什么要这么多类注解?](#3.2 为什么要这么多类注解?)

[3.3 方法注解@Bean](#3.3 方法注解@Bean)

[3.3.1 方法注解要配合类注解使用](#3.3.1 方法注解要配合类注解使用)

[3.3.2 定义多个对象](#3.3.2 定义多个对象)

[3.3.3 重命名Bean](#3.3.3 重命名Bean)

四、DI详解

[4.1 属性注入](#4.1 属性注入)

[4.2 构造方法注入](#4.2 构造方法注入)

[4.3 Setter注入](#4.3 Setter注入)

[4.4 三种注入优缺点分析](#4.4 三种注入优缺点分析)

[4.5 @Autowired存在问题](#4.5 @Autowired存在问题)

[4.6 @Autowired与@Resource的区别](#4.6 @Autowired与@Resource的区别)


前言

在前面的章节,我们学到了Spring Boot和Spring MVC的开发,可以完成一些基本功能的开发了,但是什么使Spring呢?Spring、Spring Boot和Spring MVC又有什么关系呢?

一、Spring

1.1 Spring是什么?

Spring是一个开源框架,它让我们开发更加简单,Spring是包含众多工具方法的IoC容器

那么问题来了,什么是容器?什么是IoC容器?

1.2 什么是容器?

容器是用来容纳某种物品的装置。比如我们生活中的水杯,垃圾桶,冰箱这些都可比喻成容器。我们接触的容器有:

  • List/Map:数据存储容器
  • Tomcat:Web容器

1.3 什么是IoC容器?

IoC是Spring的核心思想,我们前面讲过,在类上添加@RestController和@Controller注解,就是把这个对象交给Spring管理,Spring框架启动时就会加载该类,把对象交给Spring管理,就是IoC思想。

IoC:Inversion of Controll(控制反转),也就是说Spring是一个控制反转的容器。

**什么是控制反转呢?**也就是控制权反转。什么的控制权发生了反转?获得依赖对象的过程被反转了。

也就是说,当需要某个对象时,传统开发模式中需要自己new创建对象,现在不需要再进行创建,把创建对象的任务交给容器,程序只需要依赖注入就可以了。

这个容器称为:IoC容器。Spring是一个IoC容器。

二、IoC介绍

接下来我们通过案例来了解一下什么是IoC。

2.1 传统程序开发

实现思路是这样的:

先设计轮子,再根据轮子的大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个"依赖"关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。

代码实现如下:

java 复制代码
//汽车类
public class Car {
    private Framework framework;

    public Car(int size) {
        framework = new Framework(size);
        System.out.println("car init...");
    }

    public void run() {
        System.out.println("car run...");
    }
}

//框架类
public class Framework {
    private Bottom bottom;

    public Framework(int size) {
        bottom = new Bottom(size);
        System.out.println("framework init...");
    }
}

//车身类
public class Bottom {
    private Tire tire;

    public Bottom(int size) {
        tire = new Tire(size);
        System.out.println("bottom init...");
    }
}

//轮胎类
public class Tire {
    private int size;

    public Tire(int size) {
        this.size = size;
        System.out.println("tire init... size" + size);
    }
}

2.2 问题分析

这样的设计看起来没问题,但是可维护性却很低。

接下来需求有了变更:随着对车的需求越来越大,个性化需求也会越来越多,我们需要对车有颜色需求。

那个时候程序就会出现问题:当最底层代码改动之后,整个调用链上的所有代码都需要修改

程序的耦合度非常高(修改一处代码,影响其它处的代码修改)

2.3 解决方案

我们尝试换种思路,我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘,底盘依赖车身,车身依赖汽车。

此时,我们只需要将原来有自己创建的下级类,改为传递的方式(也就是注入的方式),因为我们不需要在当前类中创建下级类了,所以下级类即使发生变化(创建或减少参数),当前类本身也无需修改任何代码,这样就完成了程序的解耦

2.4 IoC程序开发

基于以上思路,我们把调用汽车的程序改造下,把创建子类的方式,改为注入传递的方式,具体示例代码如下:

java 复制代码
//轮胎类
public class Tire {
    private int size;
    private String color;

    public Tire(int size, String color) {
        this.size = size;
        this.color = color;
        System.out.println("tire init.. size:" + size + "color:" + color);
    }
}

//底盘类
public class Bottom {
    private Tire tire;

    public Bottom(Tire tire) {
        this.tire = tire;
        System.out.println("tire init...");
    }
}

//车身类
public class Framework {
    private Bottom bottom;

    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("bottom init...");
    }
}

//汽车类
public class Car {
    private Framework framework;

    public Car(Framework framework) {
        this.framework = framework;
        System.out.println("car init..");
    }

    public void run() {
        System.out.println("car run..");
    }
}

代码经过以上调整,无论底层类如何变化,整个调用链是不用做任何变化的,这样就完成了代码之间的解耦,从而实现了更加灵活、通用的程序设计了。

2.5 Ioc的优势

在传统的代码中创建对象的顺序:Car->Framework->Bottom->Tire

改进之后解耦的代码的对象创建的顺序是:Tire->Bottom->Framewok->Car

通用程序的实现代码,类的创建顺序是反的,传统代码是Car控制并创建了Framework,依次往下,而改进之后的控制权发生了反转, 不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入到当前对象中,依赖对象的控制权不再由当前类控制了。

这部分代码,就是IoC容器做的工作。

从上面可以看出,IoC容器具备以下优点:

资源不由使用资源的双方管理,而是不使用资源的第三方管理,这可以带来很多好处。第一,资源集中处理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。

  • 资源集中管理:IoC容器会帮我们管理一些资源(对象等),我们需要使用时,只需要从IoC容器中去取就可以了。
  • 我们在创建实例的时候不需要了解其中的细节,降低了使用资源双方的依赖程度,也就是耦合度。

Spring就是一种容器,帮助我们来做了这些资源管理。

三、IoC详解

前面我们提到IoC控制反转,就是将对象的控制权交给Spring的IoC容器,由IoC容器创建及管理对象。

也就是bean的存储。

3.1 bean的存储

在之前的案例中,要把某个对象交给IoC管理容器,需要在类上添加一个注解,而Spring框架为了更好的服务web应用程序,通过了更丰富的注解。

共有两类注解类型可以实现:

  • 类注解:@Controller、@Service、@Repository、@Component、@Configuration
  • 方法注解:@Bean

3.1.1 @Controller(控制器存储)

使用@Controller存储bean的代码如下:

java 复制代码
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    public void doController() {
        System.out.println("do Controller...");
    }
}

如何观察这个对象已经存在Spring容器当中了呢?

接下里我们学习如何从Spring容器中获取对象

java 复制代码
import com.example.demo.controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		//从Spring上下文中获取对象
		UserController userController = context.getBean(UserController.class);
		//使用对象
		userController.doController();
	}
}

ApplicationContext:Spring上下文

因为对象都交给了Spring管理了,所以获取对象要从Spring中获取,那么就得到Spring的上下文。

关于上下文的理解

在最早学习多线程时,比如我们应用进行线程切换的时候,切换前都会把线程的状态信息暂时存储起来,这里的上下文就包括了当前线程的信息,等下次该线程又得到CPU调度的时候,从上下文中拿到线程上次运行的信息。

这里上下文,就是指当前的运行环境,也可以看作一个容器,容器里存放了很多内容,这些内容是当前运行的环境。

观察上述代码的程序运行结果:

如果把@Controller删掉,再观察运行结果:

报错显示:找不到类型是:com.example.demo.controller.UserController的bean

获取bean对象的其它方式。

上述代码是根据类型来查找对象,如果Spring容器中,同一个类型存在多个bean的话,怎么来获取呢?
ApplicationContext 也提供了其他获取bean的⽅式, ApplicationContext 获取bean对象的功能, 是⽗
类BeanFactory提供的功能。

最常用的是上述1,2,3种方式,获取到的bean是一样的。

其中1,3种都涉及到根据名称来获取对象,bean的名称是什么

Spring bean是Spring框架在运行时管理的对象,Spring会给管理的对象起一个名字。

比如学校管理学生,会给每个学生分配一个名字,根据学号,就可以找到相应的学生

Spring也是如此,给每个对象起一个名字,根据Bean的名称就可以获取到对应的对象。

Bean命名约定

我们看下官方文档的说明:

程序开发人员不需要为bean指定名称(BeanId),如果没有显式的提供名称,Spring容器将为该bean生成唯一的名称。

命名约定使用Java标准约定作为实例字段名,也就是说,bean名称以小写字母开头,然后使用驼峰式大小写。

比如:

类名:UserController,Bean的名称为:userController

也有一些特殊情况,当有多个字符并且第一个和第二个字符都是大写时,将保留原始的大小写,也就是说Bean的名称为类名

比如:

类名:UController,Bean的名称为:UController

java 复制代码
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		//从Spring上下文中获取对象
		//UserController userController = context.getBean(UserController.class);
		//使用对象
		//userController.doController();
		UController uController = (UController) context.getBean("UController");
		uController.uController();
	}
}

根据命名规则获取bean

java 复制代码
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		//从Spring上下文中获取对象
		UserController userController = context.getBean(UserController.class);
		//使用对象
		//userController.doController();
		UserController userController1 = (UserController) context.getBean("userController");
		UserController userController2 = context.getBean("userController", UserController.class);
		System.out.println(userController);
		System.out.println(userController1);
		System.out.println(userController2);
	}
}

运行结果;

地址一样,说明对象是同一个

获取bean对象,是父类BeanFactory提供的功能

ApplicationContext VS BeanFactory(常见面试题)

  • 继承关系和功能方面来说:Spring容器有两个顶级的接口:BeanFactory和ApplicationContext。其中BeanFactory提供了基础的访问容器的能力,而ApplicationContext属于BeanFactory的子类,它除了继承了BeanFactory的所有功能之外,它还拥有独特的特性,还添加了国家化支持、资源访问支持、以及事件传播等方面的支持。
  • 从性能方面来说:ApplicationContext是一次性加载并初始化所有的Bean对象,而BeanFactory是需要加载哪个才加载哪个,因此更加轻量。(空间换时间)

3.1.2 @Service(服务存储)

使用@Service存储bean的代码如下:

java 复制代码
import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void userService() {
        System.out.println("do service");
    }
}

读取bean的代码:

java 复制代码
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserService userService = context.getBean(UserService.class);
		userService.userService();
	}
}

运行结果截图:

3.1.3 @Repository(仓库存储)

使用@Repository 存储 bean 的代码如下所示:

java 复制代码
import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    public void userRepository() {
        System.out.println("Repository");
    }
}

读取bean的代码:

java 复制代码
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserRepository userRepository = context.getBean(UserRepository.class);
		userRepository.userRepository();
	}
}

运行结果截图:

3.1.4 @Component

使用@Component存储bean的代码如下所示:

java 复制代码
import org.springframework.stereotype.Component;

@Component
public class UserComponent {
    public void userComponent() {
        System.out.println("userComponent");
    }
}

获取bean的代码:

java 复制代码
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserComponent userComponent = context.getBean(UserComponent.class);
		userComponent.userComponent();
	}
}

运行结果截图:

3.1.4 @Configuration(配置存储)

使用@Configuration 存储 bean 的代码如下所示:

java 复制代码
@Configuration
public class UserConfiguration {
    public void userConfiguration() {
        System.out.println("userConfiguration");
    }
}

读取bean的代码:

java 复制代码
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserConfiguration userConfiguration = context.getBean(UserConfiguration.class);
		userConfiguration.userConfiguration();
	}
}

运行结果截图所示:

3.2 为什么要这么多类注解?

这个也是和咱们前面讲的应用分层是呼应的,让程序员看到类注解之后,就能直接了解当前类的用途。

  • @Controller:控制层,接收请求,对请求进行处理,并进行响应。
  • @Service:业务逻辑层,处理具体的业务逻辑。
  • @Repository:数据访问层,也称为持久层,负责数据访问操作。
  • @Configuration:配置层,处理项目中的一些配置信息。

程序的应用分层,调用流程如下:

类注解之间的关系

查看@Controller/@Service/@Repository/@Configuration等注解的源码发现:

其实这些注解里面都有一个注解@Component,说明它们本身就是在@Component注解上再进行的封装。

@Component是一个元注解,也就是说可以注解其它类注解,如@Controller、@Service等,这些注解被称为@Component的衍生注解。

3.3 方法注解@Bean

类注解是添加到某个类上的,但是存在两个问题:

  1. 使用外部包里的类,没办法添加类注解
  2. 一个类,需要多个对象,比如数据库操作中需要多个数据源

这种场景,我们就需要使用方法注解@Bean

先来看看方法注解如何使用:

java 复制代码
import lombok.Data;

@Data
public class UserInfo {
    private Integer id;
    private String name;
    private Integer age;
}

import org.springframework.context.annotation.Bean;


public class BeanConfig {
    @Bean
    public UserInfo userInfo(String name) {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName(name);
        userInfo.setAge(12);
        return userInfo;
    }
}

获取bean对象代码:

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserInfo userInfo = context.getBean(UserInfo.class);
		System.out.println(userInfo);
	}
}

运行结果截图:

这是什么原因导致的呢?

3.3.1 方法注解要配合类注解使用

在Spring框架的设计中,方法注解@Bean要配合类注解才能将对象正常存储到Spring容器中,如下代码所示:

java 复制代码
@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName("zhangsan");
        userInfo.setAge(12);
        return userInfo;
    }
}

再运行上述代码截图:

3.3.2 定义多个对象

对于同一个类,如何定义多个对象呢

比如多数据源的场景,类是同一个,但是配置不同,指向不同的数据源。

看下@Bean的使用

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName("zhangsan");
        userInfo.setAge(12);
        return userInfo;
    }
    
    @Bean
    public UserInfo userInfo2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(2);
        userInfo.setName("wangwu");
        userInfo.setAge(18);
        return userInfo;
    }
}

定义了多个对象的话,我们根据类型获取对象,获取的是哪一个对象呢?

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserInfo userInfo1 = context.getBean(UserInfo.class);
		System.out.println(userInfo1);
	}
}

运行结果截图:

报错信息显示:期望只有一个匹配,结果发现了两个,userInfo,userInfo2

从报错信息中,可以看出来,@Bean注解的bean,bean的名称就是它的方法名

接下来我们根据名称来获取bean对象

java 复制代码
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserInfo userInfo1 = (UserInfo) context.getBean("userInfo");
		UserInfo userInfo2 = (UserInfo) context.getBean("userInfo2");
		System.out.println(userInfo1);
		System.out.println(userInfo2);

	}
}

运行结果截图:

可以看出,@Bean可以针对同一个类,定义多个对象

3.3.3 重命名Bean

可以通过设置name属性给Bean对象进行重命名操作,如下代码所示:

java 复制代码
@Configuration
public class BeanConfig {
    @Bean(name = {"u1"})
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName("zhangsan");
        userInfo.setAge(12);
        return userInfo;
    }
}

此时就可以使用u1来获取UserInfo对象了,代码如下:

java 复制代码
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserInfo userInfo = (UserInfo) context.getBean("u1");
		System.out.println(userInfo);
    }
}

运行结果截图:

注意:重命名Bean之后就不能使用bean名称进行访问。

四、DI详解

DI:依赖注入

容器在运行期间,动态的为应用程序提供运行时所依赖的资源,称之为依赖注入。

IoC是一种思想,也就是目标,而思想只是一种指导原则,最终还是要有可行的落地方案,而DI就属于具体的实现。所以也可以说,DI是IoC的一种实现。

**依赖注入是一个过程,**是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象。

简单的来说,就是把对象取出来放到某个类的属性中

关于依赖注入,Spring给我们提供了三种方式:

  • 属性注入
  • 构造方法注入
  • Setter注入

下面我们按照实际开发的模式,将Service类注入到Controller类中。

4.1 属性注入

属性注⼊是使⽤ @Autowired 实现的,将Service类注入到Controller类中

Service类的实现代码如下:

java 复制代码
import org.springframework.stereotype.Service;

@Service
public class UserServ {
    public void sayHi() {
        System.out.println("hi, userService");
    }
}

Controller类的实现代码如下:

java 复制代码
@Controller
public class UserControl {
    @Autowired
    private UserServ userService;

    public void sayHi() {
        System.out.println("hi, userController");
        userService.sayHi();
    }
}

获取Controller中的sayHi方法:

java 复制代码
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserControl userController2 = (UserControl) context.getBean("userControl");
		userController2.sayHi();
    }
}

运行结果截图:

去掉@Autowired,再运行一下程序看看结果

注意事项:属性注入以类型进行匹配,与注入的属性名称无关,但是如果一个类型存在多个对象时,优先名称匹配,如果名称都匹配不上,那就报错。

4.2 构造方法注入

构造方法注入是在类的构造方法中实现注入,如下代码所示:

java 复制代码
import com.example.demo.service.UserServ;

public class UserController2 {
    private UserServ userServ;

    public UserController2(UserServ userServ) {
        this.userServ = userServ;
    }

    public void sayHi() {
        System.out.println("hi, userController2..");
        userServ.sayHi();
    }
}

运行结果截图:

注意事项:如果类中只有一个构造方法,那么@Autowired注解可以省略;如果类中有多个构造方法,那么需要添加@Autowired来明确指定到底使用哪个构造方法。

示例:加一个不带参数的构造方法

java 复制代码
@Controller
public class UserController2 {
    private UserServ userServ;

    public UserController2(UserServ userServ) {
        this.userServ = userServ;
    }

    public UserController2() {

    }

    public void sayHi() {
        System.out.println("hi, userController2..");
        userServ.sayHi();
    }
}

运行结果截图:

因为构造方法注入,会默认使用不带参数的构造方法进行注入

加上**@Autowired**来明确使用哪个构造方法进行注入

java 复制代码
import com.example.demo.service.UserServ;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController2 {
    private UserServ userServ;

    @Autowired
    public UserController2(UserServ userServ) {
        this.userServ = userServ;
    }

    public UserController2() {

    }

    public void sayHi() {
        System.out.println("hi, userController2..");
        userServ.sayHi();
    }
}

运行结果截图:

4.3 Setter注入

Setter注入和属性的setter方法实现类似,只不过在设置set方法的时候需要加上@Autowired注解,如下代码所示:

java 复制代码
import com.example.demo.service.UserServ;
import org.springframework.beans.factory.annotation.Autowired;

@Controller
public class UserController3 {
    private UserServ userServ;

    @Autowired
    public void setUserServ(UserServ userServ) {
        this.userServ = userServ;
    }

    public void sayHi() {
        System.out.println("hi, userController3...");
        userServ.sayHi();
    }
}

运行结果截图:

4.4 三种注入优缺点分析

属性注入

  • 优点:简洁,方便使用
  • 缺点:
    • 只能用于IoC容器,如果是非IoC容器不可用,并且只有在使用的时候才会出现空指针异常。
    • 不能注入一个final修饰的属性。

构造函数注入

  • 优点:
    • 可以注入final修饰的属性
    • 注入的对象不会被修改
    • 依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法
    • 通用性好,构造方法是JDK支持的,所以更换任何框架,它都是适用的
  • 缺点:
    • 注入多个对象时,代码会比较繁琐

Setter注入

  • 优点:方便在类实例之后,重新对该对象进行配置或者注入
  • 缺点:
    • 不能注入一个final修饰的属性
    • 注入对象可能会被改变,因为Setter方法可能会被多次调用,就有被修改的风险。

4.5 @Autowired存在问题

当同一个类型存在多个bean,使用@Autowired会存在问题

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {
    @Bean(name = {"u1"})
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName("zhangsan");
        userInfo.setAge(12);
        return userInfo;
    }

    @Bean
    public UserInfo userInfo2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(2);
        userInfo.setName("wangwu");
        userInfo.setAge(18);
        return userInfo;
    }
}
java 复制代码
import com.example.demo.config.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController4 {
    @Autowired
    private UserInfo userInfo;

    public void func() {
        System.out.println(userInfo);
    }
}

运行结果:

报错的原因,非唯一的bean对象。

如何解决上述问题呢?Spring提供了以下几种解决方案:

  • @Primary
  • @Qualifier
  • @Resource

使用@Primary:当存在多个相同类型的bean注入时,加上@Primary注解,来确定默认的实现。

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class BeanConfig {
    @Primary
    @Bean(name = {"u1"})
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName("zhangsan");
        userInfo.setAge(12);
        return userInfo;
    }

    @Bean
    public UserInfo userInfo2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(2);
        userInfo.setName("wangwu");
        userInfo.setAge(18);
        return userInfo;
    }
}

使用@Qualifier注解:指定当前要注入的bean对象,在@Qualifier的value属性中,指定注入的bean的名称。

注意:@Qualifier注解不能单独使用,必须配合@Autowired使用

java 复制代码
@Controller
public class UserController4 {
    @Qualifier("userInfo2")
    @Autowired
    private UserInfo userInfo;

    public void func() {
        System.out.println(userInfo);
    }
}

使用@Resource注解:是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。

java 复制代码
import com.example.demo.config.UserInfo;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

@Controller
public class UserController4 {
//    @Qualifier("userInfo2")
//    @Autowired
    @Resource(name = "userInfo2")
    private UserInfo userInfo;

    public void func() {
        System.out.println(userInfo);
    }
}

常见面试题:

4.6 @Autowired与@Resource的区别

  • @Autowired是Spring框架提供给的注解,而@Resource是JDK提供的注解
  • @Autowired默认是按照类型注入,而@Resource是按照名称注入,相比于@Autowired来说,@Resource支持更多的参数设置。