随着时代的发展,Spring项目的依赖注入方式越来越倾向于使用注解的方式。当然在一些比较老的项目,依然存在xml配置的方式,这里不讨论这些情况。常用的依赖注入的注解无非就是@Resource和@Autowired。
在 Spring 中实现依赖注入的最常见方式有以下 3 种:
- 属性注入(Field Injection)
- Setter 注入(Setter Injection)
- 构造方法注入(Constructor Injection)
每种方式都有其优缺点,下面我们就简单的来分析一下。
1、属性注入
日常开发中,属性注入是我们最常用的一种注入方式,代码实现如下:
优点:主要是使用简单,只需在属性注解即可实现依赖注入。
缺点:无法注入不可变对象、只适用于IoC容器以及可能违背单一设计原则。
注意:属性注入在底层实现是通过反射技术直接访问被注解的属性(Filed)注入进去的,并不会调用对应的set方法。
2、Setter注入
Setter注入,故名思意就是Spring在依赖注入的时候调用被注解的set方法,代码实现如下:
优点:通用性较高,可以在不重启容器的情况下修改配置;
缺点:不能注入不可变对象,且注入的对象可被修改。
注意:父类的属性注入和Setter注入,在创建子类的Bean时也会依赖注入。
3、构造方法注入
构造方法注入,故名思意就是Spring在依赖注入的时候调用被注解的构造方法,代码实现如下:
下面讨论一下构造方法注入时可能出现的几种情况:
1、当Bean中只有一个构造方法时,可以省略对应的注解,注入时会自动调用这一个构造方法:
2、如果Bean中有多个重载的构造方法时,需要使用注解到对应的构造方法上,则注入时就会选择这个构造方法:
3、但是Bean中有多个重载的构造方法时,并且没有使用注解标注到构造方法上,那么注入时会使用默认构造方法:
4、Bean中有多个重载的构造方法时,没有提供默认构造方法,也没有使用注解标注到构造方法上,则注入的时候会报错:
优点:完全符合单一职责设计原则,提前确保依赖不为空,并且可以注入不可变对象;
缺点:如果依赖过多会使构造方法显得臃肿(这个其实也不算是缺点吧,可以使用Lombok注解解决)。
综上,我们可以知道构造方法注入比其他两种更加适合依赖注入,同时也是官方推荐的方法,建议平时使用这种注入方式。
4、注入方式的先后顺序
如果一个Bean中同时出现这三种注入方式的时候,那么它们注入的先后顺序是怎样的呢?当然,我们可以首先知道构造方法注入必然是第一个注入的,因为在创建Bean对象完成后就马上调用构造方法来初始化对象状态信息,这一步其实就是构造方法注入。所以这里讨论的是属性注入和Setter注入的先后顺序,我们直接看一个测试例子就明白了:
运行结果:
综上,可以知道当三种注入方式同时存在时,注入顺序为构造方法注入 —> 属性注入 —> Setter注入,在实际开发中建议只使用一种注入方式。
为什么属性注入会比Setter注入先执行呢?可以看【Spring】关于Spring中Bean的生命周期填充Bean(依赖注入)说明。
5、Lombok注解与构造方法注入
前面说到构造方法注入的缺点就是过于臃肿的问题,我们可以使用Lombok的两个注解来解决这个问题,分别是@AllArgsConstructor和@RequiredArgsConstructor。
5.1 @AllArgsConstructor
自动生成一个包含所有实例成员属性的构造函数。代码如下:
反编译代码如下:
可以看到不管属性是什么修饰符的,只要是实例成员属性都会添加到构造方法中。
5.2 @AllArgsConstructor
为类中所有的final修饰或者@NonNull的实例成员属性生成一个构造方法。代码如下:
反编译代码如下:
注意:这两个注解都是只包含非staic的成员属性。
5.3 不要与@Value连用
这两个注解都不应该与@Value注解连用。因为@Value注解通常只用于基本数据类型或者String类型的属性上,那么生成的构造方法也会带上这些属性。如果我们生成的构造方法用于注入时,那么就会出现错误,因为很少情况会将基本数据类型和String类型交于IOC管理,如下所示:
其二,如果是@RequiredArgsConstructor注解,那么对应的属性应该是final修饰或者@NonNull修饰的,而 @Value注解是不能用在static属性和final属性上的。
所以,@AllArgsConstructor和@RequiredArgsConstructor都不要与@Value一起使用。