Spring 配置#EnableAsync启动报错_Java程序员笔记,Spring构造器注入原理细节分析

3f0883ca39cec28aac7636741dedc292.png

开篇

Spring IOC是面试常问的知识点。本文讲述了从自定义注册Bean开始,到解析IOC容器初始化Bean的判断的一系列过程,从现象看本质,分析了Spring中的构造器注入的原理,并且分析了各种情况,相信理解了的读者将来遇到这类的别的问题可以独立思考出答案。

1. 示例

先来看一个例子,看看什么是构造器注入

这里我写了一个类,分别有三个构造器,一个是注入一个Bean的构造器,一个是注入两个Bean的构造器,还有一个无参构造器:

3ef7f71a157510141d2bb15fcd8dfce1.png

Model类User与Role我就不贴代码了,分别是有两个变量,一个id,一个name。

然后就是Spring的配置文件context.xml

a2e4e86304eccc1cffee69287eabf5d3.png

注意,如果需要使用构造器注入,需要 此自定义标签开启(关于自定义标签,在本人往期的Spring系列中有详细介绍),具体作用后面再作分析。

那么,该类三个构造器,Spring会使用哪个构造器初始化ConstructorAutowiredTest这个Bean呢?写个测试便知:

e6b59507bde9162c49457a39027dbfee.png

执行test方法,控制台打印:

5f6b48794eefa4b43360bc3950b38802.png

从这里我们可以看出来,此时三个构造器中Spring使用了无参构造器

我们换一个方式,将无参构造器注释掉,看看Spring到底会使用哪个构造器呢?同样执行test方法测试,控制台打印:

217272d139d556bb2dc3755e72d409c5.png

此时控制台报错,大致意思是Bean的实例化失败了,没有无参的构造器方法调用。此时有个疑问,明明构造器中的参数都是Bean,为什么不能初始化,一定要使用无参的构造器呢?是否是因为有两个构造器的原因?此时我们再注释掉任意一个构造函数,使测试类只有一个带参构造函数:

95307e1aae08827fed27cc9c7c5b27af.png

再次运行测试类,控制台打印:

a465709debf9225dbf00e663e6c353d3.png

如果是注释掉第二个构造函数,则结果是两个对象都有。从这里我们可以看出,如果只有一个构造器,且参数为IOC容器中的Bean,将会执行自动注入。这里又有一个疑问,这也太不科学了吧,强制用户一定只能写一个构造器?这时我们猜想@Autowired注解是否能解决这种问题?来试试吧。我们将构造器全部解除注释,将第三个构造器打上@Autowired注解:

59f1083edcdafae9d0db29e3029a9b6f.png

运行测试,控制台打印:

f11fa43023a5314bbca259ab2c975b00.png

不出所料,@Autowired注解可以解决这种问题,此时Spring将使用有注解的构造函数进行Bean的初始化。那么,如果有两个@Autowired注解呢?结果肯定是报错,因为@Autowired的默认属性required是为true的,也就是说两个required=true的构造器,Spring不知道使用哪一个。但如果是这样写的话:

d2c66640f59d8730b75cce9c0f95e968.png

结果是怎样的呢?看看控制台打印:

7398c9070e6ff1db71da25c888f24ad4.png

使用参数最多的那一个构造器来初始化Bean。又如果两个有参构造器顺序调换又是怎样的呢?一个required为false一个为true,结果又是怎样的呢?这里直接给出答案,顺序调换依然使用多参数构造器,并且required只要有一个true就会报错。有兴趣的读者可以自己试试,下面将深入源码分析构造器注入的过程,相信上述所有疑问都能得到解答。

疑问点小结

从现象看本质,我们从上面的例子中,大致可以得到以下几个疑问:

  1. 为什么写三个构造器(含有无参构造器),并且没有@Autowired注解,Spring总是使用无参构造器实例化Bean?
  2. 为什么注释掉两个构造器,留下一个有参构造器,并且没有@Autowired注解,Spring将会使用构造器注入Bean的方式初始化Bean?
  3. 为什么写三个构造器,并且在其中一个构造器上打上**@Autowired注解,就可以正常注入构造器?并且两个@Autowired注解就会报错**,一定需要在所有@Autowired中的required都加上false即可正常初始化等等?

或许有一些没有提到的疑问点,但大致就这么多吧,举多了也没用,因为在下面深入源码的分析中读者若是可以理解,关于此类的一系列问题都将可以自己思考得出结果,得到可以举一反三的能力。

2. 依赖注入伊始

在开头,我们有提到,如果需要构造器注入的功能的话,我们需要在xml配置中写下这样一段代码:

1eb69f579ba124abeb3541189333e8d9.png

如果有看过本人自定义标签或是有自定义标签的基础的读者,第一反应应该是先看自定义标签的context对应的命名空间是哪个:

1ce78173fe892870cd2259afc74fd83c.png

我们全局搜索此命名空间(http后需要加一个斜杆符号)得到一个spring.handlers文件:

http://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler

这里说明了此命名空间对应的NamespaceHandler,进入此类看看其init方法:

90128d00010e8d054f1b55e98d622304.png

我们的自定义标签是叫作 “annotation-config" ,所以对应的解析器是AnnotationConfigBeanDefinitionParser这个类,进入这个类的parse方法:

a3d0611fc6e410dfc7751658f5964089.png

这里只需要关注registerAnnotationConfigProcessors方法,看看到底需要注册哪些Bean:

d36a2d01eeb2d0d4aa243e2c6689aeba.png

到这里我们可以知道,此自定义标签注册了一个AutowiredAnnotationBeanPostProcessor类的Bean到IOC容器。那么此类是干什么用的呢?

3. 初始化Bean

我们将思路转到IOC容器初始化Bean的流程中来,在getBean方法获取一个Bean的时候,IOC容器才开始将Bean进行初始化,此时会先实例化一个Bean,然后再对Bean进行依赖注入,然后执行一系列初始化的方法,完成Bean的整个初始化过程。而本文讨论的构造器注入,则是在实例化Bean的过程中进行的。在AbstractAutowireCapableBeanFactory类中的doCreateBean方法获取Bean的开始,将调用createBeanInstance方法进行Bean的实例化(选择Bean使用哪个构造器实例化):

e49e226d6d2d035ba16ae1ce45b7c509.png

到这里我们可以知道,determineConstructorsFromBeanPostProcessors方法将选择是否有适合的自动注入构造器,如果没有,将使用无参构造器实例化,关键就在这个方法中,是如何判断哪些构造器使用自动注入的呢:

93e343af93742ba203a27fc4ab4ee11d.png

这段代码告诉我们,这里会使用SmartInstantiationAwareBeanPostProcessor类型的BeanPostProcessor进行判断,我们回顾一下上面的依赖注入伊始的时候我们说的自定义标签注册的类的结构:

53ef5f89b5aadfaebcc1940142a7e77b.png

有看过本人关于Sprng扩展篇的文章或是有Spring扩展点基础的读者,应该可以知道,若是注册一个BeanPostProcessor到IOC容器中,在AbstractApplicationContext中的refresh方法会对这些类型的Bean进行处理,存放在一个集合,此时getBeanPostProcessors方法就可以获取到所有BeanPostProcessor集合,遍历集合,便可以调用到我们自定义标签中注册的这个类型的Bean。当然,SmartInstantiationAwareBeanPostProcessor类型的Bean有很多,但依赖注入是使用上述这个Bean来完成的。回到主线,下面会调用它的determineCandidateConstructors方法进行查找对应构造器(核心方法):

public Constructor>[] determineCandidateConstructors(Class> beanClass, final String beanName) throws BeanCreationException { //略.. // Quick check on the concurrent map first, with minimal locking. //先查找缓存 Constructor>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass); //若是缓存中没有 if (candidateConstructors == null) { // Fully synchronized resolution now... //同步此方法 synchronized (this.candidateConstructorsCache) { candidateConstructors = this.candidateConstructorsCache.get(beanClass); //双重判断,避免多线程并发问题 if (candidateConstructors == null) { Constructor>[] rawCandidates; try { //获取此Bean的所有构造器 rawCandidates = beanClass.getDeclaredConstructors(); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值