依赖注入:
根据官网介绍,依赖注入主要分为两种方式
1.构造函数注入
2.Setter方法注入
官网:
我们分别对以上两种方式进行测试,官网上用的是XML的方式,我这边就采用注解的方式了:
测试代码如下,我们通过在Service中注入LuBanService这个过程来
public class Main02 { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new // config类主要完成对类的扫描 AnnotationConfigApplicationContext(Config.class); Service service = (Service) ac.getBean("service"); service.test(); } } @Component public class LuBanService { LuBanService(){ System.out.println("luBan create "); } }
测试setter方法注入
@Component public class Service { private LuBanService luBanService; public Service() { System.out.println("service create"); } public void test(){ System.out.println(luBanService); } // 通过autowired指定使用set方法完成注入 @Autowired public void setLuBanService(LuBanService luBanService) { System.out.println("注入luBanService by setter"); this.luBanService = luBanService; } }
输出如下:
luBan create service create 注入luBanService by setter // 验证了确实是通过setter注入的 com.dmz.official.service.LuBanService@5a01ccaa
测试构造函数注入
@Component public class Service { private LuBanService luBanService; public Service() { System.out.println("service create by no args constructor"); } // 通过Autowired指定使用这个构造函数,否则默认会使用无参 @Autowired public Service(LuBanService luBanService) { System.out.println("注入luBanService by constructor with arg"); this.luBanService = luBanService; System.out.println("service create by constructor with arg"); } public void test(){ System.out.println(luBanService); } }
输出如下:
luBan create 注入luBanService by constructor // 验证了确实是通过constructor注入的 service create by constructor com.dmz.official.service.LuBanService@1b40d5f0
疑问:
在上面的验证中,大家可能会有以下几个疑问:
1.@Autowired直接加到字段上跟加到set方法上有什么区别?为什么我们验证的时候需要将其添加到setter方法上?
- 首先我们明确一点,直接添加@Autowired注解到字段上,不需要提供setter方法也能完成注入。以上面的例子来说,Spring会通过反射获取到Service中luBanService这个字段,然后通过反射包的方法,Filed.set(Service,luBanService)这种方式来完成注入
- 我们将@Autowired添加到setter方法时,我们可以通过断点看一下方法的调用栈,如下:
对于这种方式来说,最终是通过Method.invoke(object,args)的方式来完成注入的,这里的method对象就是我们的setter方法
2.@Autowired为什么加到构造函数上可以指定使用这个构造函数?
- 我们先可以测试下,如果我们不加这个注解会怎么样呢?我把前文中的@Autowired注解注释,然后运行发现
luBan create service create by no args constructor // 可以看到执行的是空参构造 null
先不急得出结论,我们再进行一次测试,就是两个函数上都添加@Autowired注解呢?
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service': Invalid autowire-marked constructor: public com.dmz.official.service.Service(com.dmz.official.service.LuBanService). Found constructor with 'required' Autowired annotation already: public com.dmz.official.service.Service()
发现直接报错了,报错的大概意思是已经找到了一个被@Autowired注解标记的构造函数,同时这个注解中的required属性为true。后来我测试了将其中一个注解中的required属性改为false,发现还是报同样的错,最终将两个注解中的属性都改为false测试才通过,并且测试结果跟上面的一样,都是执行的无参构造。
要说清楚这一点,涉及到两个知识
- Spring中的注入模型,下篇文章专门讲这个
- Spring对构造函数的推断。这个到源码阶段我打算专门写一篇文章,现在我们暂且记得:
在***默认的注入模型***下,Spring如果同时找到了两个***符合要求的构造函数***,那么Spring会采用默认的无参构造进行实例化,如果这个时候没有无参构造,那么此时会报错java.lang.NoSuchMethodException。什么叫符合要求的构造函数呢?就是构造函数中的参数Spring能找到,参数被Spring所管理。
这里需要着重记得:一,默认注入模型;二,符合要求的构造函数
3.如果我们同时采用构造注入加属性注入会怎么样呢?
在没有进行测试前,我们可以大胆猜测下,Spring虽然能在构造函数里完成属性注入,但是这属于实例化对象阶段做的事情,那么在后面真正进行属性注入的时候,肯定会将其覆盖掉。现在我们来验证我们的结论
@Component public class Service { private LuBanService luBanService; public Service(LuBanService luBanService) { System.out.println("注入luBanService by constructor with arg"); this.luBanService = luBanService; System.out.println("service create by constructor with arg"); } public void test(){ System.out.println(luBanService); } @Autowired public void setLuBanService(LuBanService luBanService) { System.out.println("注入luBanService by setter"); this.luBanService = null; } }
运行结果:
注入luBanService by constructor with arg // 实例化时进行了一次注入 service create by constructor with arg // 完成了实例化 注入luBanService by setter // 属性注入时将实例化时注入的属性进行了覆盖 null
区别:
根据上图中官网所说,我们可以得出如下结论:
1.构造函数注入跟setter方法注入可以混用
对于一些强制的依赖,我们最好使用构造函数注入,对于一些可选依赖我们可以采用setter方法注入
2.Spring团队推荐使用构造函数的方式完成注入。但是对于一些参数过长的构造函数,Spring是不推荐的