》通过构造函数实例化对象
如果上面你对使用factoryMethd进行实例化对象已经足够了解的话,那么下面的源码分析基本没有什么很大区别,我们接着看看代码。
首先,我们回到createBeanInstance方法中,
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // 上面的代码已经分析过了 // 1.使用supplier来得到一个对象 // 2.通过factotryMethod方法实例化一个对象 // 看起来是不是有点熟悉,在使用factotryMethod创建对象时也有差不多这样的一段代码,看起来就是使用缓存好的方法直接创建一个对象 boolean resolved = false; boolean autowireNecessary = false; // 不对这个参数进行讨论,就认为一直为null if (args == null) { synchronized (mbd.constructorArgumentLock) { // bd中的resolvedConstructorOrFactoryMethod不为空,说明已经解析过构造方法了 if (mbd.resolvedConstructorOrFactoryMethod != null) { // resolved标志是否解析过构造方法 resolved = true; autowireNecessary = mbd.constructorArgumentsResolved; } } } if (resolved) { // 构造函数已经解析过了,并且这个构造函数在调用时需要自动注入参数 if (autowireNecessary) { // 此时部分解析好的参数已经存在了beanDefinition中,并且构造函数也在bd中 // 那么在这里只会从缓存中去取构造函数以及参数然后反射调用 return autowireConstructor(beanName, mbd, null, null); } else { // 这里就是直接反射调用空参构造 return instantiateBean(beanName, mbd); } } // 推断出能够使用的需要参数的构造函数 Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); // 在推断出来的构造函数中选取一个合适的方法来进行Bean的实例化 // ctors不为null:说明存在1个或多个@Autowired标注的方法 // mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR:说明是自动注入 // mbd.hasConstructorArgumentValues():配置文件中配置了构造函数要使用的参数 // !ObjectUtils.isEmpty(args):外部传入的参数,必定为null,不多考虑 // 上面的条件只要满足一个就会进入到autowireConstructor方法 // 第一个条件满足,那么通过autowireConstructor在推断出来的构造函数中再进一步选择一个差异值最小的,参数最长的构造函数 // 第二个条件满足,说明没有@Autowired标注的方法,但是需要进行自动注入,那么通过autowireConstructor会去遍历类中申明的所有构造函数,并查找一个差异值最小的,参数最长的构造函数 // 第三个条件满足,说明不是自动注入,那么要通过配置中的参数去类中申明的所有构造函数中匹配 // 第四个必定为null,不考虑 if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } // 反射调用空参构造 return instantiateBean(beanName, mbd); }
因为autowireConstructor方法的执行逻辑跟instantiateUsingFactoryMethod方法的执行逻辑基本一致,只是将Method对象换成了Constructor对象,所以对这个方法我不再做详细的分析。
我们主要就看看determineConstructorsFromBeanPostProcessors这个方法吧,这个方法的主要目的就是推断出候选的构造方法。
determineConstructorsFromBeanPostProcessors方法做了什么?
// 实际调用的就是AutowiredAnnotationBeanPostProcessor中的determineCandidateConstructors方法 // 这个方法看起来很长,但实际确很简单,就是通过@Autowired注解确定哪些构造方法可以作为候选方法,其实在使用factoryMethod来实例化对象的时候也有这种逻辑在其中,后续在总结的时候我们对比一下 public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) throws BeanCreationException { // 这里做的事情很简单,就是将@Lookup注解标注的方法封装成LookupOverride添加到BeanDefinition中的methodOverrides属性中,如果这个属性不为空,在实例化对象的时候不能选用SimpleInstantiationStrateg,而要使用CglibSubclassingInstantiationStrategy,通过cglib代理给方法加一层拦截了逻辑 // 避免重复检查 if (!this.lookupMethodsChecked.contains(beanName)) { try { ReflectionUtils.doWithMethods(beanClass, method -> { Lookup lookup = method.getAnnotation(Lookup.class); if (lookup != null) { Assert.state(this.beanFactory != null, "No BeanFactory available"); // 将@Lookup注解标注的方法封装成LookupOverride LookupOverride override = new LookupOverride(method, lookup.value()); try { // 添加到BeanDefinition中的methodOverrides属性中 RootBeanDefinition mbd = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName); mbd.getMethodOverrides().addOverride(override); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(beanName, "Cannot apply @Lookup to beans without corresponding bean definition"); } } }); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Lookup method resolution failed", ex); } this.lookupMethodsChecked.add(beanName); } // 接下来要开始确定到底哪些构造函数能被作为候选者 // 先尝试从缓存中获取 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 { // 第一步:先查询这个类所有的构造函数,包括私有的 rawCandidates = beanClass.getDeclaredConstructors(); } catch (Throwable ex) { // 省略异常信息 } List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length); // 保存添加了Autowired注解并且required属性为true的构造方法 Constructor<?> requiredConstructor = null; // 空参构造 Constructor<?> defaultConstructor = null; // 看方法注释上说明的,这里除非是kotlin的类,否则必定为null,不做过多考虑,我们就将其当作null Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass); int nonSyntheticConstructors = 0; // 对类中的所有构造方法进行遍历 for (Constructor<?> candidate : rawCandidates) { // 非合成方法 if (!candidate.isSynthetic()) { nonSyntheticConstructors++; } else if (primaryConstructor != null) { continue; } // 查询方法上是否有Autowired注解 AnnotationAttributes ann = findAutowiredAnnotation(candidate); if (ann == null) { // userClass != beanClass说明这个类进行了cglib代理 Class<?> userClass = ClassUtils.getUserClass(beanClass); if (userClass != beanClass) { try { // 如果进行了cglib代理,那么在父类上再次查找Autowired注解 Constructor<?> superCtor = userClass.getDeclaredConstructor(candidate.getParameterTypes()); ann = findAutowiredAnnotation(superCtor); } catch (NoSuchMethodException ex) { // Simply proceed, no equivalent superclass constructor found... } } } // 说明当前的这个构造函数上有Autowired注解 if (ann != null) { if (requiredConstructor != null) { // 省略异常抛出 } // 获取Autowired注解中的required属性 boolean required = determineRequiredStatus(ann); if (required) { // 类中存在多个@Autowired标注的方法,并且某个方法的@Autowired注解上被申明了required属性要为true,那么直接报错 if (!candidates.isEmpty()) { // 省略异常抛出 } requiredConstructor = candidate; } // 添加到集合中,这个集合存储的都是被@Autowired注解标注的方法 candidates.add(candidate); } // 空参构造函数 else if (candidate.getParameterCount() == 0) { defaultConstructor = candidate; } } if (!candidates.isEmpty()) { // 存在多个被@Autowired标注的方法 // 并且所有的required属性被设置成了false (默认为true) if (requiredConstructor == null) { // 存在空参构造函数,注意,空参构造函数可以不被@Autowired注解标注 if (defaultConstructor != null) { // 将空参构造函数也加入到候选的方法中去 candidates.add(defaultConstructor); } // 省略日志打印 } candidateConstructors = candidates.toArray(new Constructor<?>[0]); } // 也就是说,类中只提供了一个构造函数,并且这个构造函数不是空参构造函数 else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) { candidateConstructors = new Constructor<?>[] {rawCandidates[0]}; } // 省略中间两个判断,primaryConstructor必定为null,不考虑 // ..... } else { // 说明无法推断出来 candidateConstructors = new Constructor<?>[0]; } this.candidateConstructorsCache.put(beanClass, candidateConstructors); } } } return (candidateConstructors.length > 0 ? candidateConstructors : null); }
这里我简单总结下这个方法的作用
1.获取到类中的所有构造函数
2.查找到被@Autowired注解标注的构造函数
如果存在多个被@Autowired标注的构造函数,并且其required属性没有被设置为true,那么返回这些被标注的函数的集合(空参构造即使没有添加@Autowired也会被添加到集合中)
如果存在多个被@Autowired标注的构造函数,并且其中一个的required属性被设置成了true,那么直接报错
如果只有一个构造函数被@Autowired标注,并且其required属性被设置成了true,那么直接返回这个构造函数
3.如果没有被@Autowired标注标注的构造函数,但是类中有且只有一个构造函数,并且这个构造函数不是空参构造函数,那么返回这个构造函数
4.上面的条件都不满足,那么determineCandidateConstructors这个方法就无法推断出合适的构造函数了
可以看到,通过AutowiredAnnotationBeanPostProcessor的determineCandidateConstructors方法可以处理构造函数上的@Autowired注解。
但是,请注意,这个方法并不能决定到底使用哪个构造函数来创建对象(即使它只推断出来一个,也不一定能够使用),它只是通过@Autowired注解来确定构造函数的候选者,在构造函数都没有添加@Autowired注解的情况下,这个方法推断不出来任何方法。真正确定到底使用哪个构造函数是交由autowireConstructor方法来决定的。前文已经分析过了instantiateUsingFactoryMethod方法,autowireConstructor的逻辑基本跟它一致,所以这里不再做详细的分析。
factoryMethod跟构造函数的比较
整体逻辑比较
从上图中可以看到,整体逻辑上它们并没有什么区别,只是查找的对象从factoryMethod换成了构造函数
执行细节比较
细节的差异主要体现在推断方法上
推断factoryMethod
推断构造函数
它们之间的差异我已经在图中标识出来了,主要就是两点
- 通过构造函数实例化对象,多了一层处理,就是要处理构造函数上的@Autowired注解以及方法上的@LookUp注解(要决定选取哪一种实例化策略,SimpleInstantiationStrategy/CglibSubclassingInstantiationStrategy)
- 在最终的选取也存在差异,对于facotyMehod而言,在宽松模式下(除ConfigurationClassBeanDefinition外,也就是扫描@Bean得到的BeanDefinition,都是宽松模式),会选取一个最精准的方法,在严格模式下,会选取一个参数最长的方法
- 对于构造函数而言,会必定会选取一个参数最长的方法
关于计算类型差异的补充内容
思考了很久,我还是决定再补充一些内容,就是关于上面两幅图的最后一步,对应的核心代码如下:
int typeDiffWeight = (mbd.isLenientConstructorResolution() ? argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes)); if (typeDiffWeight < minTypeDiffWeight) { factoryMethodToUse = candidate; argsHolderToUse = argsHolder; argsToUse = argsHolder.arguments; minTypeDiffWeight = typeDiffWeight; ambiguousFactoryMethods = null; }
1.判断bd是严格模式还是宽松模式,上面说过很多次了,bd默认就是宽松模式,只要在ConfigurationClassBeanDefinition中使用严格模式,也就是扫描@Bean标注的方法注册的bd(对应的代码可以参考:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod方法)
我们再看看严格模式跟宽松模式在计算差异值时的区别
宽松模式
public int getTypeDifferenceWeight(Class<?>[] paramTypes) { // 计算实际使用的参数跟方法申明的参数的差异值 int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments); // 计算没有经过类型转换的参数跟方法申明的参数的差异值 int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024; return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight); } public static int getTypeDifferenceWeight(Class<?>[] paramTypes, Object[] args) { int result = 0; for (int i = 0; i < paramTypes.length; i++) { // 在出现类型转换时,下面这个判断才会成立,也就是在比较rawArguments跟paramTypes的差异时才可能满足这个条件 if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) { return Integer.MAX_VALUE; } if (args[i] != null) { Class<?> paramType = paramTypes[i]; Class<?> superClass = args[i].getClass().getSuperclass(); while (superClass != null) { // 如果我们传入的值是方法上申明的参数的子类,那么每多一层继承关系,差异值加2 if (paramType.equals(superClass)) { result = result + 2; superClass = null; } else if (ClassUtils.isAssignable(paramType, superClass)) { result = result + 2; superClass = superClass.getSuperclass(); } else { superClass = null; } } // 判断方法的参数是不是一个接口,如果是,那么差异值加1 if (paramType.isInterface()) { result = result + 1; } } } return result; }
严格模式(主要应用于@Bean标注的方法对应的BeanDefinition)
public int getAssignabilityWeight(Class<?>[] paramTypes) { // 严格模式下,只有三种返回值 // 1.Integer.MAX_VALUE,经过类型转换后还是不符合要求,返回最大的类型差异 // 因为解析后的参数可能返回一个NullBean(创建对象的方法返回了null,Spring会将其包装成一个NullBean),不过一般不会出现这种情况,所以我们可以当这种情况不存在 for (int i = 0; i < paramTypes.length; i++) { if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) { return Integer.MAX_VALUE; } } // 2.Integer.MAX_VALUE - 512,进行过了类型转换才符合要求 for (int i = 0; i < paramTypes.length; i++) { if (!ClassUtils.isAssignableValue(paramTypes[i], this.rawArguments[i])) { return Integer.MAX_VALUE - 512; } } // 3.Integer.MAX_VALUE - 1024,没有经过类型转换就已经符合要求了,返回最小的类型差异 return Integer.MAX_VALUE - 1024; }
首先,不管是factoryMethod还是constructor,都是采用上面的两个方法来计算类型差异,但是正常来说,只有factoryMethod会采用到严格模式(除非程序员手动干预,比如通过Bean工厂后置处理器修改了bd中的属性,这样通常来说没有很大意义)
所以我们分为三种情况讨论
1、factoryMethod+宽松模式
这种情况下,会选取一个最精确的方法,同时方法的参数要尽量长
测试代码:
public class FactoryObject { public DmzService getDmz() { System.out.println(0); return new DmzService(); } public DmzService getDmz(OrderService indexService) { System.out.println(1); return new DmzService(); } public DmzService getDmz(OrderService orderService, IndexService indexService) { System.out.println(2); return new DmzService(); } public DmzService getDmz(OrderService orderService, IndexService indexService,IA ia) { System.out.println(3); return new DmzService(); } } public class ServiceImpl implements IService { } public class IAImpl implements IA { }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="constructor"><!--必须要开启自动注入,并且是通过构造函数进行自动注入,否则选用无参构造--> <!--factoryObject 提供了创建对象的方法--> <bean id="factoryObject" class="com.dmz.spring.instantiation.service.FactoryObject"/> <bean class="com.dmz.spring.instantiation.service.OrderService" id="orderService"/> <bean id="dmzService" factory-bean="factoryObject" factory-method="getDmz" /> <bean class="com.dmz.spring.instantiation.service.ServiceImpl" id="iService"/> <bean class="com.dmz.spring.instantiation.service.IndexService" id="indexService"/> </beans>
/** * @author 程序员DMZ * @Date Create in 23:59 2020/6/1 * @Blog https://daimingzhi.blog.csdn.net/ */ public class XMLMain { public static void main(String[] args) { ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("application.xml"); } }
运行程序发现,选用了第三个(getDmz(OrderService orderService, IndexService indexService))构造方法。虽然最后一个方法的参数更长,但是因为其方法申明的参数上存在接口,所以它的差异值会大于第三个方法,因为不会被选用
2、factoryMethod+严格模式
这种情况下,会选取一个参数尽量长的方法
测试代码:
/** * @author 程序员DMZ * @Date Create in 6:28 2020/6/1 * @Blog https://daimingzhi.blog.csdn.net/ */ @ComponentScan("com.dmz.spring.instantiation") @Configuration public class Config { @Bean public DmzService dmzService() { System.out.println(0); return new DmzService(); } @Bean public DmzService dmzService(OrderService indexService) { System.out.println(1); return new DmzService(); } @Bean public DmzService dmzService(OrderService orderService, IndexService indexService) { System.out.println(2); return new DmzService(); } @Bean public DmzService dmzService(OrderService orderService, IndexService indexService, IA ia) { System.out.println("config " +3); return new DmzService(); } @Bean public DmzService dmzService(OrderService orderService, IndexService indexService, IA ia, IService iService) { System.out.println("config " +4); return new DmzService(); } } /** * @author 程序员DMZ * @Date Create in 6:29 2020/6/1 * @Blog https://daimingzhi.blog.csdn.net/ */ public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); ac.register(Config.class); ac.refresh(); } }
运行程序,发现选用了最后一个构造函数,这是因为在遍历候选方法时,会先遍历参数最长的,而在计算类型差异时,因为严格模式下,上面所有方法的差异值都是一样的,都会返回Integer.MAX_VALUE - 1024。实际上,在不进行手动干预的情况下,都会返沪这个值。
3、构造函数+宽松模式
这种情况下,也会选取一个参数尽量长的方法
之所以会这样,主要是因为在autowireConstructor方法中进行了一次短路判断,如下所示
在上图中,如果已经找到了合适的方法,那么直接就不会再找了,而在遍历的时候是从参数最长的方法开始遍历的,测试代码如下:
@Component public class DmzService { // 没有添加@Autowired注解,也会被当作候选方法 public DmzService(){ System.out.println(0); } @Autowired(required = false) public DmzService(OrderService orderService) { System.out.println(1); } @Autowired(required = false) public DmzService(OrderService orderService, IService iService) { System.out.println(2); } @Autowired(required = false) public DmzService(OrderService orderService, IndexService indexService, IService iService,IA ia) { System.out.println("DmzService "+3); } } /** * @author 程序员DMZ * @Date Create in 6:29 2020/6/1 * @Blog https://daimingzhi.blog.csdn.net/ */ public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); ac.register(Config.class); ac.refresh(); } }