前提
你得知道Spring创建Bean的基本流程,我们这里解释的是Spring创建Bean时使用有参构造器去创建Bean的源码解析。
autowireConstructor方法
这个方法里面就是拿到类的构造器,然后选取到最合适的,然后进而通过构造器来进行初始化。
方法总览
没想到很好的表达方式,就把注释补充完整,可以直接看注释
/** * 根据给定的构造器列表(如果给的构造器参数是空的,那么则会去获取该类的构造器数组),Spring去找出一个最适合的构造器,然后通过这个构造器去实例化对象出来. * beanName: Bean的名称 * mbd: 该bean的BeanDefinition * chosenCtors: 该类的构造器数组 * explicitArgs:查看调用链你会发现,这个方法时通过getBean方法过来的,那么既然是getBea过来的,那么在我们getBean的时候除了传入beanName/beanClass以外,还可以传入其他参数,如果传入了其他参数,那么Spring * 则认为这些参数是构造器初始化对象时的构造方法参数列表,而这个其他参数就是此时的explicitArgs. * getBean带参数的参考:org.springframework.beans.factory.BeanFactory#getBean(java.lang.Class, java.lang.Object...) */ public BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] chosenCtors, @Nullable Object[] explicitArgs) { /** * 一句废话: * BeanWrapperImpl是BeanWrapper的实现类 * 并且这个类用来存储Bean的名称,class,实例对象等. * */ BeanWrapperImpl bw = new BeanWrapperImpl(); /** * 初始化这个BeanWrapper * */ this.beanFactory.initBeanWrapper(bw); /** * constructorToUse是我们实际上使用的构造器,因为传进来的构造器一看见就知道是个数组,那么Spring需要筛选出来最适合的构造器,那么筛选出来最适合的构造器就会赋值给这里的constructorToUse * */ Constructor<?> constructorToUse = null; /** * argsHolderToUse用来存储用到的构造器的参数,下面的argsToUse的值也是从这个argsHolderToUse中取出来的 */ ArgumentsHolder argsHolderToUse = null; /** * 构造方法中使用到的参数列表实际的值 * */ Object[] argsToUse = null; /** * 总结一下这个if和else: * 1、判断是否传入构造参数值列表,如果传入则赋值 * 2、没有传入则从缓存中去取 * */ /** * 如果我们getBean的时候传入了参数,那么Spring就认为我们希望按照指定的构造参数列表去寻找构造器并实例化对象. * 那么这里如果不为空则实际上需要使用的构造方法参数列表值就已经确定了 * */ if (explicitArgs != null) { argsToUse = explicitArgs; } else { /** * 这里干的事情很简单,就是如果这个bean是原型的,那么说明此方法肯定进入过,那么也肯定找到过合适的构造方法和构造参数值列表,在找到 * 合适的构造方法和构造参数值列表后会加入到缓存里面去,那么此处如果不是第一次进入的话,那么缓存里面已经有了不用再次去获取. * 此处做的工作就是从缓存中去获取已经找到过并存进来的构造方法和构造参数值列表. * 这个加入缓存的操作在下面的代码里面才有,此处不理解可以看到下面的代码后再去理解. * 还有注意,只有当我们参数中explicitArgs为空的时候,构造器才会被缓存,同样也是下面解释. * */ Object[] argsToResolve = null; synchronized (mbd.constructorArgumentLock) { constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod; if (constructorToUse != null && mbd.constructorArgumentsResolved) { // Found a cached constructor... argsToUse = mbd.resolvedConstructorArguments; if (argsToUse == null) { argsToResolve = mbd.preparedConstructorArguments; } } } /* 如果缓存中存在则去拿出来赋值.其实都没必要在这里加注释 */ if (argsToResolve != null) { argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve); } } /** * 这里判断constructorToUse是否为空是因为上面有可能从缓存里面已经拿到了,如果拿到了则不需要进if里面去寻找,直接去调用创建实例化操作了. * 这里重要的是if里面的代码. * */ if (constructorToUse == null) { // Need to resolve the constructor. boolean autowiring = (chosenCtors != null || mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR); /** * 构造器使用的参数 * */ ConstructorArgumentValues resolvedValues = null; /** * 最小参数个数,此值需要用来在循环寻找构造器时使用. * 如果当当前循环到的构造器参数值个数小于这个最小值的话,那么说明就是不合适的,没必要继续下去. * */ int minNrOfArgs; /** * 如果我们getBean的地方传入构造参数值列表,那么则最小参数个数就是我们传入的列表长度 * */ if (explicitArgs != null) { minNrOfArgs = explicitArgs.length; } else { /** * 如果我们没有传入构造器参数值列表,那么则去解析看有没有配置构造器参数列表,例如如下配置: * <bean class="com.dh.aop.package1.Demo2" id="demo2"> * <constructor-arg index="0" value="111"></constructor-arg> * <constructor-arg index="1" value="222"></constructor-arg> * </bean> * 这个时候,minNrOfArgs的值就是2 * 如果我们没有配置构造器参数的话,这个minNrOfArgs的值就是0 * */ ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues(); resolvedValues = new ConstructorArgumentValues(); minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues); } // Take specified constructors, if any. /** * 将该方法的参数构造器列表赋值给candidates * */ Constructor<?>[] candidates = chosenCtors; /** * 如果传入的构造器列表为空,则通过class对象去拿 * 如果bd中设置了允许访问非public的构造器,那么则获取所有的构造器,否则获取public的构造器. * 注意这里isNonPublicAccessAllowed的默认值为true. * 如果获取构造器的时候出错当然就要抛异常. * */ if (candidates == null) { Class<?> beanClass = mbd.getBeanClass(); try { candidates = (mbd.isNonPublicAccessAllowed() ? beanClass.getDeclaredConstructors() : beanClass.getConstructors()); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); } } /** * 对构造器进行排序: * public的大于其他的权限 * 如果都是public的,那么参数越多越靠前. * 可以看这个sort方法里面可以看到的 * */ AutowireUtils.sortConstructors(candidates); /** * 差异变量 * */ int minTypeDiffWeight = Integer.MAX_VALUE; /** * 有歧义的构造器,就是参数数量一致的,这种情况下的构造器就被列为有歧义的. * 正常情况下,如果出现有歧义的构造器,那么就使用第一个,这取决于spring设置的宽松模式. * 默认为宽松,如此的话就默认使用第一个构造器使用 * 如果设置为严格,则会报错 * 设置宽松/严格模式标志:beanDefinition.setLenientConstructorResolution * */ Set<Constructor<?>> ambiguousConstructors = null; LinkedList<UnsatisfiedDependencyException> causes = null; /** * 下面就是循环的拿构造器去校验判断选取一个合适的构造器了.在此之前我们总结一下上述代码做的事情. * 1、定义constructorToUse、argsHolderToUse、argsToUse,这些分别用来存后面实际上需要使用的构造器、构造器参数、值等 * 2、如果getBean调用的时候传入了构造器参数,那么argsToUse的值就被赋值为传入的构造器参数,否则就尝试从缓存里面去拿constructorToUse和argsToUse * 这个缓存就是当bean不是原型的时候实例化时找到的合适的构造器等参数,当然如果是第一次进来,或者bean是单例的,那么此缓存中肯定没有这个bean相关的构造器数据 * 3、如果缓存里面有,则直接实例化bean后放到wrapper中并return,如果不存在则需要再次进行一些操作 * 4、在不存在时,首先定义resolvedValues,这个是后续循环里面需要使用到的构造器使用的参数列表,定义minNrOfArgs,这个是最小参数个数,首先如果getBean传入了构造器参数 * 那么此值就是传入构造参数的长度,否则就尝试看我们有没有配置使用某个构造器,如果都没有,那么这个值就是0了,这个变量用来后面在循环构造器的时候筛选用的 * 5、然后定义candidates变量,然后将chosenCtors(是前面传入的构造器列表)赋值过去,如果它为空,那么则需要去通过class去拿构造器,拿的时候判断了一手 * 去拿BeanDefinition中的isNonPublicAccessAllowed,这个isNonPublicAccessAllowed意思为是否允许访问非public的构造器,如果为true,则去获取所有的构造器,否则只获取public的 * 6、然后对所有的构造器进行排序,规则为public>其他权限,参数个数多的>参数个数少的,至于为什么排序这个可能是spring认为参数越多的越科学吧 * 7、差异变量,这个看循环里面的代码才能理解 * 8、定义ambiguousConstructors为有歧义的构造器,意思就是如果两个构造器参数一致,那Spring就不知道该去用哪个,这时这两个构造器就被放入ambiguousConstructors集合中,他们两个就是有歧义的构造器 * ================================================================================ * 下面循环里面需要搞清楚的就是它具体是如何选取到合适的构造器来使用 * */ for (Constructor<?> candidate : candidates) { /** * 拿到当前构造方法的参数class数组 * */ Class<?>[] paramTypes = candidate.getParameterTypes(); /** * 前面说了[constructorToUse]这个变量是当前确定使用的构造器,如果它不为空,那么说明我们已经确定了使用哪个构造器,那么就没必要继续下去了. * 但[argsToUse.length > paramTypes.length]这个就比较难理解. * 注意每次循环以后argsToUse的值就会改变为那次循环的构造器的参数,如果当前拿到的argsToUse参数列表的长度大于当前这个构造器的长度,那么说明上一次拿到的这个argsToUse比当前的这个更合适(上面也说过,Spring认为参数越多的越科学) * 这里可以注意一下前面sort排序的时候,构造参数个数越多的越靠前,所以这里敢于用长度判断后直接break,因为如果上一次循环的构造器参数列表为2个,那么这一次(也就是下一次)的构造参数列表肯定不会比2大,那么说明对于参数个数而言,上一次的参数个数肯定不会比这一次少,那么肯定就更合适了呗 */ if (constructorToUse != null && argsToUse.length > paramTypes.length) { // Already found greedy constructor that can be satisfied -> // do not look any further, there are only less greedy constructors left. break; } /** * 如果当前构造器的参数数量比最小参数列表数量小的时候,那么跳过这个构造器. * minNrOfArgs的值有两个地方赋值了: * 1、如果我们getBean时传入了其他参数,那么其他参数的个数就是minNrOfArgs的值 * 2、如果我们getBean没有传参数,那么minNrOfArgs的值就是我们配置让Spring指定使用某些参数的构造器,那么我们配置的参数列表数量也就是当前的minNrOfArgs * 3、如果上述的情况都不存在,那么minNrOfArgs就是0了,大多数时候都是这种情况,如果都没配置,那么就得Spring自己慢慢而不存在此处的筛选了. * 所以总结来说此处就是做了一个根据我们自己定义的来筛选的操作 * */ if (paramTypes.length < minNrOfArgs) { continue; } /** * 存储构造器需要的参数 * */ ArgumentsHolder argsHolder; /** * 此处resolvedValues不为空则说明getBean时传入的参数explicitArgs为空的, * 因为上面的代码是如果explicitArgs不为空则不对resolvedValues赋值,否则就对resolvedValues赋值 * 此处先看else的代码,会更清晰. * 如果传入的参数为空,那么则会去拿参数了 * */ if (resolvedValues != null) { try { /** * 去拿到参数列表的名称,这里ConstructorPropertiesChecker.evaluate是这样做的: * 如果构造方法上加入了ConstructorProperties注解,那么说明我们参数名称数组,如果没有这个注解,那么次数paramNames为空的 * */ String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, paramTypes.length); /** 这里为空则代表我们没有通过注解去自定义参数名称,则通过ParameterNameDiscoverer去解析拿到构造器的参数名称列表 */ if (paramNames == null) { ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); if (pnd != null) { /** 解析拿到参数名称列表 */ paramNames = pnd.getParameterNames(candidate); } } /** * 此处会去获取这些参数名称的参数值,如果是自动注入的就会通过getBean获取,当前这种构造器注入的情况如果循环依赖则会报错的. * 这里我们只需要知道,此处将构造器需要的参数值拿出来后并封装到了argsHolder中去. * 当然如果你构造器里面给个Integer的参数,那肯定是会报错的,因为这里面会去Spring容器中拿这个Integer,结果呢,肯定是NoSuchBeanDefinitionException了 * 其余这里不用太过于细究,有兴趣可以详细看. * */ argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, getUserDeclaredConstructor(candidate), autowiring); } catch (UnsatisfiedDependencyException ex) { if (logger.isTraceEnabled()) { logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex); } // Swallow and try next constructor. if (causes == null) { causes = new LinkedList<>(); } causes.add(ex); continue; } } else { /** * 到了这个else里面来,说明getBean调用的时候传入了构造器参数,那么就说明我们希望按照指定的构造器去初始化Bean. * 那么这里就需要判断当前构造器的参数个数是否和我们希望的个数一样,如果不是,那么就循环去找下一个构造器, * 如果和我们希望的是一样的,那么就将我们给的参数封装到argsHolder里面去 * */ // Explicit arguments given -> arguments length must match exactly. if (paramTypes.length != explicitArgs.length) { continue; } argsHolder = new ArgumentsHolder(explicitArgs); } /** * 当到达这里的时候,至此我们拿到了: * 1、构造器 * 2、构造器需要的参数和值 * 那么这里就去结算前面定义的那个差异值. * 注意这里的:isLenientConstructorResolution意思是是否为宽松的模式,为true的时候是宽松,false的时候是严格,默认为true,这个东西前面已经说了. * 这个差异值越小越那就说明越合适. * 具体差异值如何计算出来的这个可以自行去看里面的代码,argsHolder.getTypeDifferenceWeight(paramTypes) * */ int typeDiffWeight = (mbd.isLenientConstructorResolution() ? argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes)); // Choose this constructor if it represents the closest match. /** * 如果本次计算到的差异值比上一次获取到的差异值小,那么就需要做这几件事: * 1、设置constructorToUse为当前的这个构造器 * 2、设置参数和参数值 * 3、给差异值赋值为当前计算出来的差异值 * 4、清空有歧义的集合(因为此时我们已经得到了更合适的构造器,所以有歧义的构造器里面保存的构造器已经没有存在的意义了) * */ if (typeDiffWeight < minTypeDiffWeight) { constructorToUse = candidate; argsHolderToUse = argsHolder; argsToUse = argsHolder.arguments; minTypeDiffWeight = typeDiffWeight; ambiguousConstructors = null; } /** * 如果已经找到过一次构造器,并且当前的差异值和上一次的差异值一致的话,那么说明这两个构造器是有歧义的, * 那么就将这两个构造器放到[有歧义的构造器]集合中去. * */ else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) { if (ambiguousConstructors == null) { ambiguousConstructors = new LinkedHashSet<>(); ambiguousConstructors.add(constructorToUse); } ambiguousConstructors.add(candidate); } } /** * 到达这里的时候,如果还没有找到合适的构造器,那么则直接抛出异常,这个类没法实例化 * */ if (constructorToUse == null) { if (causes != null) { UnsatisfiedDependencyException ex = causes.removeLast(); for (Exception cause : causes) { this.beanFactory.onSuppressedException(cause); } throw ex; } throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Could not resolve matching constructor " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)"); } /** * 如果存在歧义的构造器集合不为空,并且当前BeanDefinition为严格模式,那么则抛出异常,只有当BeanDefinition为宽松模式时,这种情况才不会抛异常 * */ else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Ambiguous constructor matches found in bean '" + beanName + "' " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " + ambiguousConstructors); } /** * 如果当前getBean没有传参数,那么则将当前的构造器和参数放到缓存里面去,可能Spring认为传入参数的情况下说不准我们准备怎么做,所以干脆我们自己传入参数的就不缓存了 * */ if (explicitArgs == null) { argsHolderToUse.storeCache(mbd, constructorToUse); } } /** * 以下没什么好说的,new示例出来,存入beanWrapper中,return回去 * */ try { final InstantiationStrategy strategy = beanFactory.getInstantiationStrategy(); Object beanInstance; if (System.getSecurityManager() != null) { final Constructor<?> ctorToUse = constructorToUse; final Object[] argumentsToUse = argsToUse; beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () -> strategy.instantiate(mbd, beanName, beanFactory, ctorToUse, argumentsToUse), beanFactory.getAccessControlContext()); } else { beanInstance = strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse); } bw.setBeanInstance(beanInstance); return bw; } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean instantiation via constructor failed", ex); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?