Spring注解装配方式

目录

1. 实例注入方式
2. @Autowired, @Resource, @Inject 三个注解的区别
3. @Autowired Field injection is not recommended
4. 总结

1. 实例注入方式

spring注入的方式有三种:

  1. 属性field注入
  2. setter方法注入
  3. 构造方法constructor注入

下面就分别看下这三种方式注入的区别

1.1 属性field注入

属性注入是大家最为常见也是使用最多的一种注入方式,就是在bean的变量上使用注解进行依赖注入,本质上是通过反射的方式直接注入到field。

1
2
3
4
5
6
7
@Service
public class BService{

@Autowired
AService aService;
//......
}

这里使用@Autowired注解注入,另外也有Resource以及@Inject等注解,都可以实现注入。

但是当我们使用@Autowired注解注入时,却出现Field injection is not recommended的警告,这是为什么?且看下文。

1.2 setter方法注入

set 方法注入太过于臃肿,实际上很少使用

1
2
3
4
5
6
7
8
9
@Service
public class BService{
AService aService;

@Autowired
public void setaService(AService aService){
this.aService = aService;
}
}

注:在 Spring 4.3 及以后的版本中,setter 上面的 @Autowired 注解是可以不写的。

1.3 构造方法注入

构造方法注入方式如下:

1
2
3
4
5
6
7
8
9
@Service
public class BService{
AService aService;

@Autowired
public BService(AService aService){
this.aService = aService;
}
}

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

1.4 属性field注入优缺点

好处:方式简洁,代码简单;

坏处:

  • 容易违背了单一职责原则,使用这种基于field注入的方式,添加依赖是很简单的,就算类中有十几个依赖你可能都觉得没有什么问题,但是拥有太多的依赖通常意味着你的类要承担更多的责任,明显违背了单一职责原则(SRP:Single responsibility principle)

  • 依赖注入与容器本身耦合:依赖注入框架的核心思想之一就是受容器管理的类不应该去依赖容器所使用的依赖。换句话说,这个类应该是一个简单的POJO能够被单独实例化并且能为它提供所需的依赖;这个问题具体可以表现在

    • 类和依赖容器强耦合,不能在容器外使用
    • 类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化
  • 不能使用属性注入的方式构建不可变对象(final修饰的变量)

1.5 Spring 开发团队的建议

Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.

简单的说,就是

  • 强制依赖就用构造器凡事

  • 可选、可变的依赖就用setter注入

    当然你可以在同一个类中使用这两种方法。构造器注入更适合强制性的注入旨在不变性,Setter注入更适合可变性的注入

  1. 基于构造方法注入

    The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.

    Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:final 修饰的变量) ,另一方面也可以保证这些变量的值不会是 null 。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service),在被调用时可以保证它们都完全准备好了 。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码异味,这个类可能承担了过多的责任 。
  1. 基于setter注入

    Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later.

    基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。使用 setter 注入的一个优点是,这个依赖可以很方便的被改变或者重新注入 。

2. @Autowired, @Resource, @Inject 三个注解的区别

Spring 支持使用@Autowired@Resource@Inject三个注解进行依赖注入,下面就来介绍一下这三个注解的区别。

2.1 @Autowired

@Autowired为Spring框架提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired

1
2
3
4
5
6
7
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
trueboolean required() default true;

}

从Autowired注解源码上看,可以作用在:

1
2
3
4
5
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.ANNOTATION_TYPE) //注解

@Autowired是Spring自带的注解,通过AutowiredAnnotationBeanPostProcessor类实现的依赖注入

下面就通过一段代码来了解它。

1
2
3
4
5
6
7
8
9
10
11
12
public interface Svc {
void sayHello();
}

@Service
public class SvcA implements Svc {
@Override
public void sayHello() {
System.out.println("hello, this is service A");
}

}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest
public class SimpleTest {

@Autowired
// @Qualifier("svcA")
Svc svc;

@Test
void rc() {
Assertions.assertNotNull(svc);
svc.sayHello();
}

}

@Qualifier(“XXX”) 中的 XX是 Bean 的名称,所以 @Autowired 和 @Qualifier 结合使用时,自动注入的策略就从 byType 转变成 byName 了
注意:使用@Qualifier 时候,如何设置的指定名称的Bean不存在,则会抛出异常,如果防止抛出异常,可以使用:

1
2
3
@Qualifier("xxxxyyyy")
@Autowired(required = false)
private Svc svc;

装配顺序:

  1. 按照type在上下文查看匹配的bean

    1
    查找type为Svc的bean
  2. 如果有多个bean, 则按照name进行匹配

  1. 判断是否有@Qualifier注解 a. 如果有@Qualifier注解,则按照@Qualifier指定的name进行匹配
    1
    查找name为svcA的bean
    b. 如果没有,则按照变量名进行匹配
    1
    查找name为svc的bean
  2. 匹配不到,则报错。(@Autowired(required=false),如果设置requiredfalse(默认为true),则注入失败时不会抛出异常)

2.2 @Inject

@Inject是JSR330 (Dependency Injection for Java)中的规范,需要导入javax.inject.Inject jar包,才能实现注入

@Inject是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named

1
2
3
4
5
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inject {
}

@Inject注解源码上看,可以使用在以下地方:

1
2
3
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.METHOD) //方法
@Target(ElementType.FIELD) //字段、枚举的常量

在Spring 的环境下,@Inject@Autowired 是相同的 ,因为它们的依赖注入都是使用AutowiredAnnotationBeanPostProcessor来处理的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {

//........省略........
truepublic AutowiredAnnotationBeanPostProcessor() {
truetruethis.autowiredAnnotationTypes.add(Autowired.class);
truetruethis.autowiredAnnotationTypes.add(Value.class);
truetruetry {
truetruetruethis.autowiredAnnotationTypes.add((Class<? extends Annotation>)
truetruetruetruetrueClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
truetruetruelogger.info("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
truetrue}
truetruecatch (ClassNotFoundException ex) {
truetruetrue// JSR-330 API not available - simply skip.
truetrue}
true}
//........省略........
}

简单使用代码:

1
2
3
@Inject
@Named("svcA")
private SvcA asvc;

@Named的作用类似 @Qualifier

区别:@Inject是Java EE包里的,在SE环境需要单独引入。另一个区别在于@Autowired可以设置required=false而@Inject并没有这个属性

2.3 @Resource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
String name() default "";
String lookup() default "";
Class<?> type() default java.lang.Object.class;
enum AuthenticationType {
CONTAINER,
APPLICATION
}
AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
boolean shareable() default true;
String mappedName() default "";
String description() default "";
}

@Resource注解源码上看,可以使用在以下地方:

1
2
3
@Target(ElementType.TYPE) //Class, interface (including annotation type), or enum declaration
@Target(ElementType.METHOD) //方法
@Target(ElementType.FIELD) //字段、枚举的常量

@Resource是JSR-250定义的注解。Spring 在 CommonAnnotationBeanPostProcessor实现了对JSR-250的注解的处理,其中就包括@Resource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {

//........省略........
@Override
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) {

InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
}
return pvs;
}

//........省略........
}

@Resource有两个重要的属性:nametype,而Spring 将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。

装配顺序:

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
  4. 如果既没有指定name,又没有指定type,则默认按照byName方式进行装配;如果没有匹配,按照byType进行装配。

在使用IDEA进行Spring开发时,使用@Autowired注解的时候,会发现IDEA会有警告显示:

1
2
3
Field injection is not recommended

Inspection info: Spring Team Recommends: "Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies".

大致的意思就是:

不建议使用基于 field 的注入方式。

Spring 开发团队建议:在你的Spring Bean 永远使用基于constructor 的方式进行依赖注入。对于必须的依赖,永远使用断言来确认。

比如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class HelpService {
@Autowired
@Qualifier("svcB")
private Svc svc;

public void sayHello() {
svc.sayHello();
}
}

public interface Svc {
void sayHello();
}

@Service
public class SvcB implements Svc {
@Override
public void sayHello() {
System.out.println("hello, this is service B");
}
}

修改为Constructor的注入方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class HelpService {
private final Svc svc;

@Autowired
public HelpService(@Qualifier("svcB") Svc svc) {
Assert.notNull(svc, "svc must not be null");
this.svc = svc;
}
public void sayHello() {
svc.sayHello();
}
}

如果按照Spring团段的建议,如果svc是必须的依赖,应该使用Assert.notNull(svc, "svc must not be null");来确认

4. 总结

4.1 @Autowired总结

@Autowired注解总结

  1. 可用于构造函数,成员变量以及set方法

  2. 从Spring 4.3开始,如果目标Bean只有一个构造函数,则在该构造函数上可以省略@Autowired注解;如果目标Bean有多个构造函数则不可省略

@Autowired注入方式:

  • 按照type查找bean,如果使用@Qualifier注解声明了name,则从结果集中取出与该name相匹配的bean返回(此时可以视为通过name和type获取bean,但实质是先通过type获取所有bean,然后通过name筛选,详情见后文findAutowireCandidates()方法源码分析)

  • 如果没有使用@Qualifier注解,且找到多个bean,则判断这些bean中是否有使用@Primary注解和@Priority注解,有就返回优先级最高的哪一个bean,没有就按照字段名称去匹配bean,匹配成功返回,不成功抛出异常。(详情见后文determineAutowireCandidate()方法源码解析)

4.2 @Resource总结

  1. 可用于成员变量以及set方法

  2. 若不指定name属性,则会把name属性值处理为字段名或set方法标识的字段名称

  3. 若指定type属性,则type属性值必须与字段类型或set方法返回值类型为父子关系(type属性值可以是子类,也可以是超类),否则会抛出异常

  4. @Resource先按照name属性值注入,若未找到,则按type属性值注入。即默认的name或指定的name找不到 bean ,就会按 type 注入

4.3 @Inject总结

  1. @Inject是JSR330 (Dependency Injection for Java)中的规范,需要导入javax.inject.Inject jar包 ,才能实现注入

  2. @Inject可以作用CONSTRUCTOR、METHOD、FIELD上

  3. @Inject是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named;

4.4 总结

1、@Autowired是Spring自带的,@Resource是JSR250规范实现的,@Inject是JSR330规范实现的

2、@Autowired、@Inject用法基本一样,不同的是@Inject没有一个request属性

3、@Autowired、@Inject是默认按照类型匹配的,@Resource是按照名称匹配的,@Autowired默认是byType可以使用@Qualifier指定Name,@Resource默认ByName如果找不到则ByType

4、@Autowired如果需要按照名称匹配需要和@Qualifier一起使用,@Inject和@Name一起使用,@Resource则通过name进行指定

5、@Autowired可以对构造器、方法、参数、字段使用,@Resource只能对方法、字段使用, @Inject可以对构造函数、方法、字段、枚举的常量使用