依赖注入(Dependency injection,简称DI)。
依赖,指的是在一个bean对象中存在着对另一个bean对象的引用(通常是成员变量),也就是依赖关系。
注入,指的是在bean的创建过程中,spring会自动地完成它所依赖的bean的创建,然后将引用指向这个依赖对象,即赋值。
@Autowired注解是spring中最常见的实现依赖注入的方式。
@Autowired注解的解析和处理,主要由spring容器中的AutowiredAnnotationBeanPostProcessor这个bean后置处理器来完成。
以类的成员变量为例,@Autowired实现依赖注入主要有以下3种方式:
1、标注在属性上:
@RestController
@RequestMapping(value = "/books")
public class BookController {
@Autowired
private BookService bookService;
这种情况,当BookController这个bean实例化之后,spring会扫描它内部的@Autowired注解,然后初始化的过程中进行统一处理。
对于bookService这个属性,会在bean factory中找到与其类型匹配的、已经创建好的单例bean对其赋值,如果找不到,就会先进行BookService类型的单例bean的创建(实例化+初始化),然后再对bookService这个属性赋值。
注意:@Autowired标注的变量类型,本身也得是spring bean,不然无法注册到spring容器,就无法进行自动注入。
2、标注在构造方法上:
@RestController
@RequestMapping(value = "/books")
public class BookController {
private BookService bookService;
private OrderService orderService;
//默认构造方法
public BookController() {
}
//含参构造方法1
@Autowired
public BookController(BookService bookService) {
this.bookService = bookService;
}
//含参构造方法2
public BookController(OrderService orderService) {
this.orderService = orderService;
}
这种情况下,spring会在这些自定义构造方法中,优先找到带有@Autowired注解的那个构造方法,来进行实例化;如果这里有多个@Autowired,程序就会报错,因为它不知道我们到底要哪个。
但是使用构造器实例化一定要依赖@Autowired注解吗?并不是,相关的源码片段如下:
// Candidate constructors for autowiring?
//寻找候选构造器
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
//判断是否进行构造器自动注入
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
// Preferred constructors for default construction?
//获取merged bean definition中的属性,使用首选的构造器自动注入
ctors = mbd.getPreferredConstructors();
if (ctors != null) {
return autowireConstructor(beanName, mbd, ctors, null);
}
// No special handling: simply use no-arg constructor.
//兜底策略:使用无参的构造函数进行实例化
return instantiateBean(beanName, mbd);
1)寻找候选的构造器,通常会选择@Autowired注解的构造器,或者一种不需要该注解的特殊场景:有且仅有一个构造函数,并且是含参的。
2)如果有候选构造器,或者merged bean definition中设置了自动注入模式为“AUTOWIRE_CONSTRUCTOR”,或者merged bean definition中constructorArgumentValues属性不为空(比如mybatis的mapper),则调用autowireConstructor方法。
autowireConstructor的大致策略:
根据constructorArgumentValues(构造器参数值)计算出需要的最小参数个数、然后结合候选构造器(如果为空,则获取bean中定义的所有构造器作为候选)的参数个数和类型,计算各构造器匹配度,选择最合适的构造器进行bean的实例化。
3)获取merged bean definition中设置的首选构造器,有则调用autowireConstructor方法。
4)兜底策略,如果以上方式均没有完成实例化,则使用无参数的构造函数进行实例化。
注意: spring在使用构造器进行依赖注入时,会先处理构造方法中的参数,确保参数bean的自动注入。比如示例里的bookService参数,如果之前没有实例化,这里就会先创建bean对象,再交给构造器使用。
3、标注在setter方法上:
@RestController
@RequestMapping(value = "/books")
public class BookController {
private BookService bookService;
@Autowired
public void setBookService(BookService bookService) {
this.bookService = bookService;
}
这种情况,跟直接标注在属性上的方式类似,也是在bean初始化的过程中完成的注入。
spring在拿到有@Autowired注解的setBookService方法后,同样是先解决了参数bookService的自动注入,确保参数对象创建之后,再调用setBookService方法,将值赋给bookService成员变量。
该注解只有一个required属性,默认为true,表示当前要依赖的这个bean必须存在于spring容器中,不然就报错。而当被显示设置为false时,则表示这个自动注入是可选的,找到类型匹配的bean就注入,没找到就不注入。
接下来分别在属性和方法上测试required属性为false的情况。
1、标注在属性上:
@RestController
@RequestMapping(value = "/books")
public class BookController {
@Autowired(required = false)
private BookService bookService;
在示例代码中把BookService的实现类BookServiceImpl的@Service注解去掉,使BookServiceImpl无法被扫描到,从而无法注册到bean factory中。然后运行代码,发现程序正常运行,只不过通过BookController这个bean的get方法拿到bookService属性时,会发现是null,说明并没有强制进行依赖注入。
2、标注在构造函数上:
@RestController
@RequestMapping(value = "/books")
public class BookController {
private BookService bookService;
@Autowired(required = false)
public BookController(BookService bookService) {
this.bookService = bookService;
}
再次运行代码,发现报错了:
Parameter 0 of constructor in org.example.controller.BookController required a bean of type ‘org.example.service.BookService’ that could not be found. – 构造函数里的BookService,这个类型bean找不到。
不是说不强制自动注入吗?为什么这里没有依赖会报错呢?
其实控制台这里还打印了一句话:
f.a.AutowiredAnnotationBeanPostProcessor : Inconsistent constructor declaration on bean with name ‘bookController’: single autowire-marked constructor flagged as optional - this constructor is effectively required since there is no default constructor to fall back to: public org.example.controller.BookController(org.example.service.BookService)
– 单个autowire标记的构造函数标记为可选——这个构造函数实际上是必需的,因为没有默认构造函数可以回退。
这里说的很清楚了,方法是可选了,但是由于没有无参数的默认构造函数可以兜底,spring只能拿这个方法来构建实例,但是在bean factory中又没注册过BookService类型的bean,参数无法实例化,所以构造失败。
其实如果只有一个构造方法,这个@Autowired加不加、是否可选都一样,因为别无选择。所以我们还是需要加上无参数的构造方法:
@RestController
@RequestMapping(value = "/books")
public class BookController {
private BookService bookService;
//默认构造方法
public BookController() {
}
//含参构造方法
@Autowired(required = false)
public BookController(BookService bookService) {
this.bookService = bookService;
}
或者我们也可以把@Autowired(required = false)添加在构造函数的参数上。既然构造方法别无选择,那就把本来需要注入的参数改为可选:
@RestController
@RequestMapping(value = "/books")
public class BookController {
private BookService bookService;
//含参构造方法
public BookController(@Autowired(required = false) BookService bookService) {
this.bookService = bookService;
}
事实证明上面两种方式,程序都能正常启动。
3、最后试试标注在setter方法上:
@RestController
@RequestMapping(value = "/books")
public class BookController {
private BookService bookService;
@Autowired(required = false)
public void setBookService(BookService bookService) {
this.bookService = bookService;
}
程序正常启动,本地方法setBookService的@Autowired注解,尽管是可选,但是在bean的初始化阶段,程序本着能执行尽量执行的原则,处理方法参数,但此时发现有一个参数bean无法实例化,方法执行不了。得了,正好这个方法又是可选,那就不执行了。
但是如果方法的@Autowired注解,其required属性为true,一定要执行,又不想报错呢?
我们还是可以将@Autowired(required = false)的注解加在参数上,这样找不到的参数bean就会被置为null,然后传入方法。
@Autowired注解在自动注入的时候,是按照bean的类型进行查找和注入的,还是看这个示例代码:
@RestController
@RequestMapping(value = "/books")
public class BookController {
@Autowired
private BookService bookService;
假如我们为BookService接口设计两个实现类,而且都注册成spring bean,那么在自动注入bookService属性的时候,就会找到这两个实现类bean,然后报错:
Description: Field bookService in org.example.controller.BookController required a single bean, but 2 were found
Action: Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
spring告诉我们,这个属性只需要一个bean进行注入,却找到了两个;建议把其中一个bean标记为@Primary,或者使用@Qualifier来指明使用哪个。
解决方案1,使用@Primary标注其中一个bean,作为主bean,这样每次自动注入时都会首选这个bean。
@Service
@Primary
public class BookServiceImpl2 implements BookService {
解决方案2,使用@Qualifier标注需要自动注入的依赖,同时设置@Qualifier的value属性,值为要使用的那个bean的名称。
这种方式相比@Primary更加灵活,两者同时使用时,@Qualifier的优先级也更高。
注意: 这里bean名称的填写,比如实现类BookServiceImpl2在容器中注册的bean,容器默认生成的beanName为首字母小写的"bookServiceImpl2"
@RestController
@RequestMapping(value = "/books")
public class BookController {
@Autowired
@Qualifier(value = "bookServiceImpl2")
private BookService bookService;
@Resource注解也是常见的用来实现依赖注入的注解。
@Resource注解的解析和处理,主要由spring容器中的CommonAnnotationBeanPostProcessor这个bean后置处理器来完成。
@Resource和@Autowired注解的主要区别在于:
1、归属问题:@Autowired是spring框架自己的注解,而@Resource是Java的通用注解。
2、注入方式:
@Autowired按照bean的类型进行注入,如果需要指定名称需要@Qualifier注解支持;
@Resource支持选择按bean的类型或名称进行注入,默认按照名称注入。
3、注解使用范围、参数类型等。
@Resource注解使用方式:
1、标注在属性上:
@RestController
@RequestMapping(value = "/books")
public class BookController {
@Resource
private BookService bookService;
这里BookService是个接口,其实现类BookServiceImpl已经在spring bean factory中注册。
1)只写一个@Resource注解,不设置属性。
这种情况下,spring会将 bookService 这个变量名当做默认的依赖bean名称,先去检查bean factory里是否有这个名字,
如果有就按bookService这个名字去找到一个bean,完成注入;
如果没有就回退到按类型BookService查找,最终找到BookServiceImpl这个bean,完成注入。
2)设置name属性,@Resource(name = “bookServiceImpl”)
这种情况下,spring认为你已经指定名字了,就不会再按类型兜底,而是直接拿这个名字去获取bean,能找到就注入,找不到就报错。
注意: 这里的name不能瞎写,根据它找到的bean,必须是BookService的实例(实现该接口),否则即使拿到或者创建出这个bean,也会在最后的类型检查中报错。
3)设置type属性,@Resource(type = BookServiceImpl.class)
这种情况下,和情况1)类似,只是在回退到按类型查找时,使用的类型不是依赖的类型BookService,而是注解中指定的type值。
注意: 这里的type不能瞎写,它必须是BookService的实现类,不然在注入元数据的组装阶段(注入开始之前)就会报错说类型不匹配。
4)同时设置name和type属性,@Resource(name = “bookServiceImpl”, type = BookServiceImpl.class)
这种情况下,和情况2)类似,直接拿这个name去获取bean,只是在最后拿到bean实例的时候,进行类型判断所使用的目标类型不是BookService,而是注解中指定的type值。同样的,type也不能瞎写,不然在注入元数据的组装阶段就会报错。
2、标注在普通方法上。
@Resource注解同样可以标注在setter方法上,在bean初始化的过程中,完成属性的依赖注入。
@RestController
@RequestMapping(value = "/books")
public class BookController {
private BookService bookService;
@Resource
public void setBookService(BookService bookService) {
this.bookService = bookService;
}