目录
[1.1 Spring是什么?](#1.1 Spring是什么?)
[1.2 什么是容器?](#1.2 什么是容器?)
[1.3 什么是IoC容器?](#1.3 什么是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的优势)
[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)
[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
类注解是添加到某个类上的,但是存在两个问题:
- 使用外部包里的类,没办法添加类注解
- 一个类,需要多个对象,比如数据库操作中需要多个数据源
这种场景,我们就需要使用方法注解@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支持更多的参数设置。