JavaSpring高级进阶 2019-01-22 17:31:28
开篇
Spring IOC是面试常问的知识点。本文讲述了从自定义注册Bean开始,到解析IOC容器初始化Bean的判断的一系列过程,从现象看本质,分析了Spring中的构造器注入的原理,并且分析了各种情况,相信理解了的读者将来遇到这类的别的问题可以独立思考出答案。
1. 示例
先来看一个例子,看看什么是构造器注入。
这里我写了一个类,分别有三个构造器,一个是注入一个Bean的构造器,一个是注入两个Bean的构造器,还有一个无参构造器:
Model类User与Role我就不贴代码了,分别是有两个变量,一个id,一个name。
然后就是Spring的配置文件context.xml:
注意,如果需要使用构造器注入,需要 <context:annotation-config /> 此自定义标签开启(关于自定义标签,在本人往期的Spring系列中有详细介绍),具体作用后面再作分析。
那么,该类三个构造器,Spring会使用哪个构造器初始化ConstructorAutowiredTest这个Bean呢?写个测试便知:
执行test方法,控制台打印:
从这里我们可以看出来,此时三个构造器中Spring使用了无参构造器。
我们换一个方式,将无参构造器注释掉,看看Spring到底会使用哪个构造器呢?同样执行test方法测试,控制台打印:
此时控制台报错,大致意思是Bean的实例化失败了,没有无参的构造器方法调用。此时有个疑问,明明构造器中的参数都是Bean,为什么不能初始化,一定要使用无参的构造器呢?是否是因为有两个构造器的原因?此时我们再注释掉任意一个构造函数,使测试类只有一个带参构造函数:
再次运行测试类,控制台打印:
如果是注释掉第二个构造函数,则结果是两个对象都有。从这里我们可以看出,如果只有一个构造器,且参数为IOC容器中的Bean,将会执行自动注入。这里又有一个疑问,这也太不科学了吧,强制用户一定只能写一个构造器?这时我们猜想@Autowired注解是否能解决这种问题?来试试吧。我们将构造器全部解除注释,将第三个构造器打上@Autowired注解:
运行测试,控制台打印:
不出所料,@Autowired注解可以解决这种问题,此时Spring将使用有注解的构造函数进行Bean的初始化。那么,如果有两个@Autowired注解呢?结果肯定是报错,因为@Autowired的默认属性required是为true的,也就是说两个required=true的构造器,Spring不知道使用哪一个。但如果是这样写的话:
结果是怎样的呢?看看控制台打印:
使用参数最多的那一个构造器来初始化Bean。又如果两个有参构造器顺序调换又是怎样的呢?一个required为false一个为true,结果又是怎样的呢?这里直接给出答案,顺序调换依然使用多参数构造器,并且required只要有一个true就会报错。有兴趣的读者可以自己试试,下面将深入源码分析构造器注入的过程,相信上述所有疑问都能得到解答。
疑问点小结
从现象看本质,我们从上面的例子中,大致可以得到以下几个疑问:
- 为什么写三个构造器(含有无参构造器),并且没有@Autowired注解,Spring总是使用无参构造器实例化Bean?
- 为什么注释掉两个构造器,留下一个有参构造器,并且没有@Autowired注解,Spring将会使用构造器注入Bean的方式初始化Bean?
- 为什么写三个构造器,并且在其中一个构造器上打上**@Autowired注解,就可以正常注入构造器?并且两个@Autowired注解就会报错**,一定需要在所有@Autowired中的required都加上false即可正常初始化等等?
或许有一些没有提到的疑问点,但大致就这么多吧,举多了也没用,因为在下面深入源码的分析中读者若是可以理解,关于此类的一系列问题都将可以自己思考得出结果,得到可以举一反三的能力。
2. 依赖注入伊始
在开头,我们有提到,如果需要构造器注入的功能的话,我们需要在xml配置中写下这样一段代码:
如果有看过本人自定义标签或是有自定义标签基础的读者,第一反应应该是先看看自定义标签的context对应的命名空间是哪个:
我们全局搜索此命名空间(http后需要加一个斜杠符号)得到一个spring.handlers文件:
http://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
这里说明了此命名空间对应的NamespaceHandler,进入此类看看其init方法:
我们的自定义标签是叫作 “annotation-config" ,所以对应的解析器是AnnotationConfigBeanDefinitionParser这个类,进入这个类的parse方法:
这里只需要关注registerAnnotationConfigProcessors方法,看看到底需要注册哪些Bean:
到这里我们可以知道,此自定义标签注册了一个AutowiredAnnotationBeanPostProcessor类的Bean到IOC容器。那么此类是干什么用的呢?
3. 初始化Bean
我们将思路转到IOC容器初始化Bean的流程中来,在getBean方法获取一个Bean的时候,IOC容器才开始将Bean进行初始化,此时会先实例化一个Bean,然后再对Bean进行依赖注入,然后执行一系列初始化的方法,完成Bean的整个初始化过程。而本文讨论的构造器注入,则是在实例化Bean的过程中进行的。在AbstractAutowireCapableBeanFactory类中的doCreateBean方法获取Bean的开始,将调用createBeanInstance方法进行Bean的实例化(选择Bean使用哪个构造器实例化):
到这里我们可以知道,determineConstructorsFromBeanPostProcessors方法将选择是否有适合的自动注入构造器,如果没有,将使用无参构造器实例化,关键就在这个方法中,是如何判断哪些构造器使用自动注入的呢:
这段代码告诉我们,这里会使用SmartInstantiationAwareBeanPostProcessor类型的BeanPostProcessor进行判断,我们回顾一下上面的依赖注入伊始的时候我们说的自定义标签注册的类的结构:
有看过本人关于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", ex); } //最终适用的构造器集合 List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length); //存放依赖注入的required=true的构造器 Constructor<?> requiredConstructor = null; //存放默认构造器 Constructor<?> defaultConstructor = null; Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass); int nonSyntheticConstructors = 0; for (Constructor<?> candidate : rawCandidates) { if (!candidate.isSynthetic()) { nonSyntheticConstructors++; } else if (primaryConstructor != null) { continue; } //查找当前构造器上的注解 AnnotationAttributes ann = findAutowiredAnnotation(candidate); //若没有注解 if (ann == null) { Class<?> userClass = ClassUtils.getUserClass(beanClass); if (userClass != beanClass) { try { Constructor<?> superCtor = userClass.getDeclaredConstructor(candidate.getParameterTypes()); ann = findAutowiredAnnotation(superCtor); } catch (NoSuchMethodException ex) { // Simply proceed, no equivalent superclass constructor found... } } } //若有注解 if (ann != null) { //已经存在一个required=true的构造器了,抛出异常 if (requiredConstructor != null) { throw new BeanCreationException(beanName, "Invalid autowire-marked constructor: " + candidate + ". Found constructor with 'required' Autowired annotation already: " + requiredConstructor); } //判断此注解上的required属性 boolean required = determineRequiredStatus(ann); //若为true if (required) { if (!candidates.isEmpty()) { throw new BeanCreationException(beanName, "Invalid autowire-marked constructors: " + candidates + ". Found constructor with 'required' Autowired annotation: " + candidate); } //将当前构造器加入requiredConstructor集合 requiredConstructor = candidate; } //加入适用的构造器集合中 candidates.add(candidate); } //如果该构造函数上没有注解,再判断构造函数上的参数个数是否为0 else if (candidate.getParameterCount() == 0) { //如果没有参数,加入defaultConstructor集合 defaultConstructor = candidate; } } //适用的构造器集合若不为空 if (!candidates.isEmpty()) { // Add default constructor to list of optional constructors, as fallback. //若没有required=true的构造器 if (requiredConstructor == null) { if (defaultConstructor != null) { //将defaultConstructor集合的构造器加入适用构造器集合 candidates.add(defaultConstructor); } else if (candidates.size() == 1 && logger.isInfoEnabled()) { logger.info("Inconsistent constructor declaration on bean with name '" + beanName + "': single autowire-marked constructor flagged as optional - " + "this constructor is effectively required since there is no " + "default constructor to fall back to: " + candidates.get(0)); } } //将适用构造器集合赋值给将要返回的构造器集合 candidateConstructors = candidates.toArray(new Constructor<?>[0]); } //如果适用的构造器集合为空,且Bean只有一个构造器并且此构造器参数数量大于0 else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) { //就使用此构造器来初始化 candidateConstructors = new Constructor<?>[] {rawCandidates[0]}; } //如果构造器有两个,且默认构造器不为空 else if (nonSyntheticConstructors == 2 && primaryConstructor != null && defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) { //使用默认构造器返回 candidateConstructors = new Constructor<?>[] {primaryConstructor, defaultConstructor}; } else if (nonSyntheticConstructors == 1 && primaryConstructor != null) { candidateConstructors = new Constructor<?>[] {primaryConstructor}; } else { //上述都不符合的话,只能返回一个空集合了 candidateConstructors = new Constructor<?>[0]; } //放入缓存,方便下一次调用 this.candidateConstructorsCache.put(beanClass, candidateConstructors); } } } //返回candidateConstructors集合,若为空集合返回null return (candidateConstructors.length > 0 ? candidateConstructors : null); }
从这段核心代码我们可以看出几个要点:
- 在没有@Autowired注解的情况下:
- 无参构造器将直接加入defaultConstructor集合中。
- 在构造器数量只有一个且有参数时,此唯一有参构造器将加入candidateConstructors集合中。
- 在构造器数量有两个的时候,并且存在无参构造器,将defaultConstructor(第一条的无参构造器)放入candidateConstructors集合中。
- 在构造器数量大于两个,并且存在无参构造器的情况下,将返回一个空的candidateConstructors集合,也就是没有找到构造器。
- 在有@Autowired注解的情况下:
- 判断required属性:
- true:先判断requiredConstructor集合是否为空,若不为空则代表之前已经有一个required=true的构造器了,两个true将抛出异常,再判断candidates集合是否为空,若不为空则表示之前已经有一个打了注解的构造器,此时required又是true,抛出异常。若两者都不为空将放入requiredConstructor集合中,再放入candidates集合中。
- false:直接放入candidates集合中。
- 判断requiredConstructor集合是否为空(是否存在required=true的构造器),若没有,将默认构造器也放入candidates集合中。
- 最后将上述candidates赋值给最终返回的candidateConstructors集合。
4.总结
综上所述,我们可以回答开篇疑问点小结所总结的一系列问题了:
为什么写三个构造器(含有无参构造器),并且没有@Autowired注解,Spring总是使用无参构造器实例化Bean?
答:参照没有注解的处理方式: 若构造器只有两个,且存在无参构造器,将直接使用无参构造器初始化。若大于两个构造器,将返回一个空集合,也就是没有找到合适的构造器,那么参照第三节初始化Bean的第一段代码createBeanInstance方法的末尾,将会使用无参构造器进行实例化。这也就解答了为什么没有注解,Spring总是会使用无参的构造器进行实例化Bean,并且此时若没有无参构造器会抛出异常,实例化Bean失败。
为什么注释掉两个构造器,留下一个有参构造器,并且没有@Autowired注解,Spring将会使用构造器注入Bean的方式初始化Bean?
答:参照没有注解的处理方式: 构造器只有一个且有参数时,将会把此构造器作为适用的构造器返回出去,使用此构造器进行实例化,参数自然会从IOC中获取Bean进行注入。
为什么写三个构造器,并且在其中一个构造器上打上@Autowired注解,就可以正常注入构造器?
答:参照有注解的处理方式: 在最后判断candidates适用的构造器集合是否为空时,若有注解,此集合当然不为空,且required=true,也不会将默认构造器集合defaultConstructor加入candidates集合中,最终返回的是candidates集合的数据,也就是这唯一一个打了注解的构造器,所以最终使用此打了注解的构造器进行实例化。
两个@Autowired注解就会报错,一定需要在所有@Autowired中的required都加上false即可正常初始化?
答:参照有注解的处理方式: 当打了两个@Autowired注解,也就是两个required都为true,将会抛出异常,若是一个为true,一个为false,也将会抛出异常,无论顺序,因为有两层的判断,一个是requiredConstructor集合是否为空的判断,一个是candidates集合为空的判断,若两个构造器的required属性都为false,不会进行上述判断,直接放入candidates集合中,并且在下面的判断中会将defaultConstructor加入到candidates集合中,也就是candidates集合有三个构造器,作为结果返回。
至于第四条结论,返回的构造器若有三个,Spring将如何判断使用哪一个构造器呢?
在后面Spring会遍历三个构造器,依次判断参数是否是Spring的Bean(是否被IOC容器管理),若参数不是Bean,将跳过判断下一个构造器,也就是说,例如上述两个参数的构造器其中一个参数不是Bean,将判断一个参数的构造器,若此参数是Bean,使用一个参数的构造器实例化,若此参数不是Bean,将使用无参构造器实例化。
也就是说,若使用@Autowired注解进行构造器注入,required属性都设置为false的话,将避免无Bean注入的异常,使用无参构造器正常实例化。若两个参数都是Bean,则就直接使用两个参数的构造器进行实例化并获取对应Bean注入构造器。
在这里最后说一点,从上面可以看出,若想使用构造器注入功能,最好将要注入的构造器都打上@Autowired注解(若有多个需要注入的构造器,将所有@Autowired中required属性都设置为false),若有多个构造器,只有一个构造器需要注入,将这个构造器打上@Autowired注解即可,不用设置required属性。
如果不打注解也是可以使用构造器注入功能的,但构造器数量只能为1,且代码可读性较差,读代码的人并不知道你这里使用了构造器注入的方式,所以这里我建议若使用构造器注入打上@Autowired注解会比较好一点。
声明:本文来源于网络。
最后
如果你对技术提升很感兴趣,可以加入Java进阶之路来交流学习:878249276,里面都是同行,有资源分享包括但不限于(分布式架构、高可扩展、高性能、高并 发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、、Mycat、Netty、Kafka、Mysql 、Zookeeper、Tomcat、Docker、Dubbo、Nginx)。欢迎一到五年的工程师加入,合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!