背景
在IDEA中,如果使用@Autowired注入的时候会出现以下警告: Field injection is not recommended (字段注入是不被推荐的) 
但是使用@Resource却不会出现此提示,我现在的公司也是全都使用构造器注入的方式,而不是@Autowired的方式。
Spring常见的注入方式
setter方式注入 调用Setter的方法注入依赖
构造器注入 利用构造方法的参数注入依赖
属性注入/字段注入 在字段上使用@Autowired/Resource注解
演示@Autowired 所有装配方式
@Autowired字段注入
@Data
@AllArgsConstructor
public class TestAutowiredFieldInject {
private String desc;
}
@Data
@AllArgsConstructor
public class TestAutowiredNoClearNameFieldInject {
private String desc;
}
@Data
@AllArgsConstructor
public class TestAutowiredNotRequiredFieldInject {
private String desc;
}
@Slf4j
@Service
public class TestFieldInjectService {
@Autowired
private TestAutowiredFieldInject testAutowiredFieldInject;
@Autowired
private TestAutowiredNoClearNameFieldInject testAutowiredNoClearNameFieldInject;
@Autowired(required = false)
private TestAutowiredNotRequiredFieldInject testAutowiredNotRequiredFieldInject;
public void printObject() {
log.info("对象:{},是否为空:{},对象信息:{}", "testAutowiredFieldInject", Objects.isNull(testAutowiredFieldInject), testAutowiredFieldInject);
log.info("对象:{},是否为空:{},对象信息:{}", "testAutowiredNoClearNameFieldInject", Objects.isNull(testAutowiredNoClearNameFieldInject), testAutowiredNoClearNameFieldInject);
log.info("对象:{},是否为空:{},对象信息:{}", "testAutowiredFieldInject", Objects.isNull(testAutowiredNotRequiredFieldInject), testAutowiredNotRequiredFieldInject);
}
}
结果: 
@Autowired自动装配是通过byType方式(变量名修改不会影响注入结果,由此可以证明是byType而不是byName) @Autowired正常情况下是强依赖型的,也就是说如果不存在TestAutowiredFieldInject 的依赖,那么将会启动应用不成功。 此时可以添加属性:@Autowired(required = false),表示非必须,可以启动成功。(找不到bean的话是null)
@Autowired通过@Qualifier设置为byName注入
配置类:
@Bean
public TestQualifierNameInject testQualifierNameInjectx() {
return new TestQualifierNameInject("TestQualifierNameInject x");
}
@Bean
public TestQualifierNameInject testQualifierNameInjecty() {
return new TestQualifierNameInject("TestQualifierNameInject y");
}
测试程序:
@Slf4j
@Service
public class TestFieldInjectService {
@Autowired
@Qualifier("testQualifierNameInjecty")
private TestQualifierNameInject testQualifierNameInject;
@Autowired(required = false)
@Qualifier("testQualifierNameInjectz")
private TestQualifierNameInject testQualifierNameInjectz;
public void printObjectQualifier() {
log.info("对象:{},是否为空:{},对象信息:{}", "testQualifierNameInject", Objects.isNull(testQualifierNameInject), testQualifierNameInject);
log.info("对象:{},是否为空:{},对象信息:{}", "testQualifierNameInjectz", Objects.isNull(testQualifierNameInjectz), testQualifierNameInjectz);
}
}
结果:  @Qualifier 作用:指定注入bean的名称
通过@Qualifier(“testQualifierNameInjecty”) 指定只能注入名称为"testQualifierNameInjecty"的bean,如果这个名称的bean不存在则无法注入,而且找不到bean时不会自动使用byType注入,直接提示报错
同时可以解决存在多个相同类型的bean时启动不成功的问题。没有加@Qualifier注解的话会提示: 
@Autowired通过@Primary解决多个同类型bean的问题
在上面的一个小节结尾疏说到可以通过@Qualifier解决多个同类型bean,接下来介绍一下通过@Primary解决这个问题。 首先,要说明一点的是,@Primary如果同时使用在同一个类型的多个Bean上,就会出现下面的报错,所以基本上只能在同一个类型的一个bean上使用
No qualifying bean of type ‘XXX’ available: more than one ‘primary’ bean found among candidates: [XXX]
config:
@Configuration
public class CustomConfig {
@Bean
public TestPrimaryInject testPrimaryInjectx() {
return new TestPrimaryInject("testPrimaryInject x");
}
@Bean
@Primary
public TestPrimaryInject testPrimaryInjecty() {
return new TestPrimaryInject("testPrimaryInject y");
}
测试类:
@Service
public class AutowiredTestService {
@Autowired
private TestPrimaryInject testPrimaryInject;
public void printObjectPrimary() {
log.info("对象:{},是否为空:{},对象信息:{}", "testPrimaryInject", Objects.isNull(testPrimaryInject), testPrimaryInject);
}
}
结果: 
@Primary作用:当我们使用自动配置的方式装配Bean时,如果这个Bean有多个候选者,假如其中一个候选者具有@Primary注解修饰,该候选者会被选中,作为自动配置的值。
我通过@Primary指定优先使用testPrimaryInjecty这个bean,所以AutowiredTestService 能够注入成功,且注入的是testPrimaryInjecty这个bean。
@Autowired应用到方法上
被@Autowried注解的方法,是不需要我们调用,Spring会自己调用。 这里的方法同时演示了静态方法和非静态方法,setter方法的调用。
注册Spring对象:
@Configuration
public class CustomConfig {
@Bean
public TestAutowiredFirst testAutowiredFirst() {
return new TestAutowiredFirst("对象1");
}
}
测试:
@Slf4j
@Service
public class TestMethodInjectService {
private TestSetterMethodInject testSetterMethodInject;
@Autowired
public void setTestAutowiredFirst1(TestSetterMethodInject testSetterMethodInject) {
this.testSetterMethodInject = testSetterMethodInject;
}
@Autowired
private void tsstGetObjectByParam(TestNormalMethodInject testNormalMethodInject) {
log.info("普通方法注入对象,对象:{},是否为空:{},对象信息:{}", "testNormalMethodInject", Objects.isNull(testNormalMethodInject), testNormalMethodInject);
}
@Autowired
public static void testStaticObject(TestStaticMethodInject testStaticMethodInject) {
log.info("静态方法注入对象,对象:{},是否为空:{},对象信息:{}", "testStaticMethodInject", Objects.isNull(testStaticMethodInject), testStaticMethodInject);
}
public void printObject() {
log.info("对象:{},是否为空:{},对象信息:{}", "testSetterMethodInject", Objects.isNull(testSetterMethodInject), testSetterMethodInject);
}
}
结果: 

根据提示,@Autowired其实是不能应用到static方法上的。
@Autowired应用到构造器上
配置类:
@Bean
public TestConstructInject testConstructInject() {
return new TestConstructInject("Construct对象1");
}
测试程序:
@Slf4j
@Service
public class TestConstructInjectService {
private TestConstructInject testConstructInject;
@Autowired
public TestConstructInjectService(TestConstructInject testConstructInject) {
this.testConstructInject = testConstructInject;
}
public void printObject() {
log.info("对象:{},是否为空:{},对象信息:{}", "testAutowiredFirst", Objects.isNull(testConstructInject), testConstructInject);
}
}
结果: 
@Autowired应用到构造器参数上
配置类:
@Bean
public TestConstructParamInject testConstructParamInject() {
return new TestConstructParamInject("ConstructParam对象1");
}
测试程序:
@Slf4j
@Service
public class TestContructParamInjectService {
private TestConstructParamInject testConstructParamInject;
public TestContructParamInjectService(@Autowired TestConstructParamInject testConstructParamInject) {
this.testConstructParamInject = testConstructParamInject;
}
public void printObject() {
log.info("对象:{},是否为空:{},对象信息:{}", "testConstructParamInject", Objects.isNull(testConstructParamInject), testConstructParamInject);
}
}
结果: 
@Autowired应用到方法参数上
配置类:
@Bean
public TestMethodParamInject testMethodParamInject() {
return new TestMethodParamInject("方法参数对象1");
}
@Bean
public TestMethodParamInject testMethodParamInject2(@Autowired TestMethodParamInject testMethodParamInject) {
log.info("配置方法注入对象,对象:{},是否为空:{},对象信息:{}", "testMethodParamInject", Objects.isNull(testMethodParamInject), testMethodParamInject);
return testMethodParamInject;
}
@Bean
public TestMethodParamInject testMethodParamInject3(TestMethodParamInject testMethodParamInject) {
log.info("配置方法【不加@Autowired】注入对象,对象:{},是否为空:{},对象信息:{}", "testMethodParamInject", Objects.isNull(testMethodParamInject), testMethodParamInject);
return testMethodParamInject;
}
测试程序:
@Slf4j
@Service
public class TestMethodParamInjectService {
private TestMethodParamInject testMethodParamInject;
public void setTestMethodParamInject(@Autowired TestMethodParamInject testMethodParamInject) {
this.testMethodParamInject = testMethodParamInject;
}
private void tsstGetObjectByParam(@Autowired TestMethodParamInject testMethodParamInject) {
log.info("普通方法注入对象,对象:{},是否为空:{},对象信息:{}", "testMethodParamInject", Objects.isNull(testMethodParamInject), testMethodParamInject);
}
public static void testStaticObject(@Autowired TestMethodParamInject testMethodParamInject) {
log.info("静态方法注入对象,对象:{},是否为空:{},对象信息:{}", "testMethodParamInject", Objects.isNull(testMethodParamInject), testMethodParamInject);
}
public void printObject() {
log.info("对象:{},是否为空:{},对象信息:{}", "testMethodParamInject", Objects.isNull(testMethodParamInject), testMethodParamInject);
}
}
结果:  
这里通过结果可以知道,当@Autowired标记在方法参数时, 1)静态方法无效 2)非静态方法也无效 3)配置方法加不加无所谓,反正都会注入
我通过网上资料查询,@Autowired对于普通方法参数确实是无效的,但是在测试方法里面是有效。
@Test
void testMethodParam(@Autowired TestMethodParamInject TestMethodParamInject) {
log.info("测试方法对象:{},是否为空:{},对象信息:{}", "TestMethodParamInject", Objects.isNull(TestMethodParamInject), TestMethodParamInject);
}
结果: 
演示@Resource 所有装配方式
@Resource 字段注入
配置类:
@Configuration
public class CustomConfig {
@Bean
public TestResourceFieldInject testResourceFieldInject() {
return new TestResourceFieldInject("Resource对象1");
}
@Bean
public TestResourceByNameInject testResourceByNameInjectX() {
return new TestResourceByNameInject("testResourceByNameInject对象X");
}
@Bean
public TestResourceByNameInject testResourceByNameInjectY() {
return new TestResourceByNameInject("testResourceByNameInject对象Y");
}
}
测试类:
@Slf4j
@Service
public class TestResourceFieldInjectService {
@Resource
private TestResourceByNameInject testResourceByNameInjectX;
@Resource
private TestResourceFieldInject testResourceAnotherName;
public void printObject() {
log.info("对象:{},是否为空:{},对象信息:{}", "testResourceByNameInjectX", Objects.isNull(testResourceByNameInjectX), testResourceByNameInjectX);
log.info("对象:{},是否为空:{},对象信息:{}", "testResourceAnotherName", Objects.isNull(testResourceAnotherName), testResourceAnotherName);
}
}
结果:  @Resource:默认使用byName注入,如果成员变量名不同于bean名称且同类型的bean只有一个实例,则会使用byType注入
@Resource对于多个同类型且无法匹配名称的Bean
如果存在多个同类型的bean,且名字都没办法匹配成员变量名,则会提示报错,无法启动。
@Configuration
public class CustomConfig {
@Bean
public TestResourceByNameInject testResourceByNameInjectX() {
return new TestResourceByNameInject("testResourceByNameInject对象X");
}
@Bean
public TestResourceByNameInject testResourceByNameInjectY() {
return new TestResourceByNameInject("testResourceByNameInject对象Y");
}
}
测试类:
@Slf4j
@Service
public class TestResourceFieldInjectService {
@Resource
private TestResourceByNameInject testResourceByNameInjectZ;
}
结果 
@Resource应用到方法上
配置类:
@Bean
public TestResourceSetterMethodInject testResourceSetterMethodInject() {
return new TestResourceSetterMethodInject("ResourceSetterMethod对象1");
}
@Bean
public TestResourceNormalMethodInject testResourceNormalMethodInject() {
return new TestResourceNormalMethodInject("ResourceNormalMethod对象1");
}
@Bean
public TestResourceStaticMethodInject testResourceStaticMethodInject() {
return new TestResourceStaticMethodInject("ResourceStaticMethod对象1");
}
测试程序:
@Slf4j
@Service
public class TestResourceMethodInjectService {
private TestResourceSetterMethodInject testResourceSetterMethodInject;
@Resource
public void setTestResourceSetterMethodInject(TestResourceSetterMethodInject testResourceSetterMethodInject) {
this.testResourceSetterMethodInject = testResourceSetterMethodInject;
}
@Resource
private void tsstGetObjectByParam(TestResourceNormalMethodInject testResourceNormalMethodInject) {
log.info("@Resource普通方法注入对象,对象:{},是否为空:{},对象信息:{}", "testResourceNormalMethodInject", Objects.isNull(testResourceNormalMethodInject), testResourceNormalMethodInject);
}
public static void testStaticObject(TestResourceStaticMethodInject testResourceStaticMethodInject) {
log.info("@Resource静态方法注入对象,对象:{},是否为空:{},对象信息:{}", "testResourceStaticMethodInject", Objects.isNull(testResourceStaticMethodInject), testResourceStaticMethodInject);
}
public void printObject() {
log.info("对象:{},是否为空:{},对象信息:{}", "testResourceSetterMethodInject", Objects.isNull(testResourceSetterMethodInject), testResourceSetterMethodInject);
}
}
结果: 根据测试,@Resource并不能用在静态方法上,且会导致应用失败,只能应用在普通方法和setter方法上。  
@AutowiredVS@Resource
其实,简单来说,他们的功能都是依赖注入。 不同点在于:
-
提供方 @Autowired 是由Spring提供的,包名是:org.springframework.beans.factory.annotation @Resource 是由Java提供的,包名是:javax.annotation -
依赖识别方式 @Autowired 默认是以byType方式,可以使用@Qualifier指定bean名称,如果找不到Bean不会自动使用byName方式。 @Resource 默认是以byName方式,当byName方式无法匹配时,会使用byType方式。(仅适用于仅注册了一个Bean对象的类型) -
适用对象 @Autowired 可以使用在方法,方法参数,构造器,构造器参数,字段上 @Resource只能使用在方法,字段上(经过实测,无法注解在构造器和参数上) -
强依赖型 @Autowired和@Resource都是具有强依赖性,也就是必须要有这个bean才能启动,不过@Autowired可以设置属性required=false变成非强制注入
各种注入方式优缺点
构造器注入:强依赖性(即必须使用此依赖),不变性(各依赖不会经常变动) Setter注入:可选(没有此依赖也可以工作),可变(依赖会经常变动) Field注入:大多数情况下尽量少使用字段注入,一定要使用的话, @Resource相对@Autowired对IoC容器的耦合更低,因为如果脱离了Spring容器的话,就不得不提供setter方法或者构造器方法去重写设置成员变量的逻辑,改动大。
Field注入的缺点
-
不能像构造器那样注入不可变的对象(程序会提示:java: 变量 XXX未在默认构造器中初始化,@Autowired和@Reousrce都是) -
依赖对外部不可见,外界可以看到构造器和setter,但无法看到私有字段,自然无法了解所需依赖 -
会导致组件与IoC容器紧耦合(这是最重要的原因,离开了IoC容器去使用组件,在注入依赖时就会十分困难) -
导致单元测试也必须使用IoC容器,原因同上 -
依赖过多时不够明显,比如我需要10个依赖,用构造器注入就会显得庞大,这时候应该考虑一下此组件是不是违反了单一职责原则
为什么idea不建议使用@Autowired
因为@Autowired太方便了,有了@Autowired基本不需要提供setter和构造器,省去了很多代码,但是也造成了业务程序与Spring框架的高度耦合,如果脱离Spring框架去使用其他的IOC框架,就不得不改动原来的业务程序。 而 @Resource是JSR-250提供的,它是Java标准,我们使用的IoC容器应当去兼容它,这样即使更换容器,也可以正常工作。
而且,@Autowired不能应用到final修饰的成员,因为final类型的变量在调用class的构造函数的这个过程当中就得初始化完成,基于字段的注入是使用set形式注入的
Spring5其实更加推荐使用构造器注入,原因是构造器注入可以保证依赖不变,因为依赖都需要使用final修饰。 构造器注入的使用条件:
- 使用private final修饰成员变量
- 提供包含private final成员变量在内的构造器(用Lombok的注解也可以)
- 不能和@Autowired的注解方式一起使用,只能使用其中一种
|