写点什么

解密 Spring 中的 Bean 实例化:推断构造方法(上)

  • 2024-03-05
    福建
  • 本文字数:6720 字

    阅读完需:约 22 分钟

在 Spring 中,一个 bean 需要通过实例化来获取一个对象,而实例化的过程涉及到构造方法的调用。本文将主要探讨简单的构造推断和实例化过程,让我们首先深入了解实例化的步骤。


实例化源码


protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {    // Make sure bean class is actually resolved at this point.    Class<?> beanClass = resolveBeanClass(mbd, beanName);
.....
// BeanDefinition中添加了Supplier,则调用Supplier来得到对象 Supplier<?> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); }
// @Bean对应的BeanDefinition if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); }
// Shortcut when re-creating the same bean... // 一个原型BeanDefinition,会多次来创建Bean,那么就可以把该BeanDefinition所要使用的构造方法缓存起来,避免每次都进行构造方法推断 boolean resolved = false; boolean autowireNecessary = false; if (args == null) { synchronized (mbd.constructorArgumentLock) { if (mbd.resolvedConstructorOrFactoryMethod != null) { resolved = true; // autowireNecessary表示有没有必要要进行注入,比如当前BeanDefinition用的是无参构造方法,那么autowireNecessary为false,否则为true,表示需要给构造方法参数注入值 autowireNecessary = mbd.constructorArgumentsResolved; } } } if (resolved) { // 如果确定了当前BeanDefinition的构造方法,那么看是否需要进行对构造方法进行参数的依赖注入(构造方法注入) if (autowireNecessary) { // 方法内会拿到缓存好的构造方法的入参 return autowireConstructor(beanName, mbd, null, null); } else { // 构造方法已经找到了,但是没有参数,那就表示是无参,直接进行实例化 return instantiateBean(beanName, mbd); } }
// 如果没有找过构造方法,那么就开始找了
// Candidate constructors for autowiring? // 提供一个扩展点,可以利用SmartInstantiationAwareBeanPostProcessor来控制用beanClass中的哪些构造方法 // 比如AutowiredAnnotationBeanPostProcessor会把加了@Autowired注解的构造方法找出来,具体看代码实现会更复杂一点 Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
// 如果推断出来了构造方法,则需要给构造方法赋值,也就是给构造方法参数赋值,也就是构造方法注入 // 如果没有推断出来构造方法,但是autowiremode为AUTOWIRE_CONSTRUCTOR,则也可能需要给构造方法赋值,因为不确定是用无参的还是有参的构造方法 // 如果通过BeanDefinition指定了构造方法参数值,那肯定就是要进行构造方法注入了 // 如果调用getBean的时候传入了构造方法参数值,那肯定就是要进行构造方法注入了 if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); }
// Preferred constructors for default construction? ctors = mbd.getPreferredConstructors(); if (ctors != null) { return autowireConstructor(beanName, mbd, ctors, null); }
// No special handling: simply use no-arg constructor. // 不匹配以上情况,则直接使用无参构造方法 return instantiateBean(beanName, mbd);}
复制代码


在 Spring 框架中的 AbstractAutowireCapableBeanFactory 类中的 createBeanInstance()方法是用来执行实例化 Bean 对象的操作,该方法会根据 Bean 定义信息和配置选项,经过一系列步骤和逻辑判断,最终创建一个全新的 Bean 实例。大致步骤如下:


  • 根据 BeanDefinition 加载类并获取对应的 Class 对象

  • 如果 BeanDefinition 绑定了一个 Supplier,那么应调用 Supplier 的 get 方法以获取一个对象,并将其直接返回。

  • 如果在 BeanDefinition 中存在 factoryMethodName 属性,则调用该工厂方法以获取一个 bean 对象,并将其返回。

  • 如果 BeanDefinition 已经自动构造过了,那么就调用 autowireConstructor()方法来自动构造一个对象。

  • 调用 SmartInstantiationAwareBeanPostProcessor 接口的 determineCandidateConstructors()方法,以确定哪些构造方法是可用的。

  • 如果存在可用的构造方法,或者当前 BeanDefinition 的 autowired 属性设置为 AUTOWIRE_CONSTRUCTOR,或者 BeanDefinition 中指定了构造方法参数值,或者在创建 Bean 时指定了构造方法参数值,那么将调用 autowireConstructor()方法来自动构造一个对象。

  • 最后,如果不符合前述情况,那么将根据不带参数的构造方法来实例化一个对象。


推断构造方法


我们看下源码:


public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)        throws BeanCreationException {  //前面跟@Lookup相关,我们不看    .....
// 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 { // 拿到所有的构造方法 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的构造方法,一个类中只能有一个required为true的构造方法 Constructor<?> requiredConstructor = null; // 用来记录默认无参的构造方法 Constructor<?> defaultConstructor = null; ...... int nonSyntheticConstructors = 0;
// 遍历每个构造方法 for (Constructor<?> candidate : rawCandidates) { if (!candidate.isSynthetic()) { // 记录一下普通的构造方法 nonSyntheticConstructors++; } else if (primaryConstructor != null) { continue; }
// 当前遍历的构造方法是否写了@Autowired MergedAnnotation<?> ann = findAutowiredAnnotation(candidate); if (ann == null) { // 如果beanClass是代理类,则得到被代理的类的类型,我们不看这种情况 ....... }
// 当前构造方法上加了@Autowired if (ann != null) { // 整个类中如果有一个required为true的构造方法,那就不能有其他的加了@Autowired的构造方法 if (requiredConstructor != null) { throw new BeanCreationException(beanName, "Invalid autowire-marked constructor: " + candidate + ". Found constructor with 'required' Autowired annotation already: " + requiredConstructor); }
boolean required = determineRequiredStatus(ann); if (required) { if (!candidates.isEmpty()) { throw new BeanCreationException(beanName, "Invalid autowire-marked constructors: " + candidates + ". Found constructor with 'required' Autowired annotation: " + candidate); } // 记录唯一一个required为true的构造方法 requiredConstructor = candidate; } // 记录所有加了@Autowired的构造方法,不管required是true还是false // 如果默认无参的构造方法上也加了@Autowired,那么也会加到candidates中 candidates.add(candidate);
// 从上面代码可以得到一个结论,在一个类中,要么只能有一个required为true的构造方法,要么只能有一个或多个required为false的方法 } else if (candidate.getParameterCount() == 0) { // 记录唯一一个无参的构造方法 defaultConstructor = candidate; }
// 有可能存在有参、并且没有添加@Autowired的构造方法 }

if (!candidates.isEmpty()) { // Add default constructor to list of optional constructors, as fallback. // 如果不存在一个required为true的构造方法,则所有required为false的构造方法和无参构造方法都是合格的 if (requiredConstructor == null) { if (defaultConstructor != null) { 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)); } } // 如果只存在一个required为true的构造方法,那就只有这一个是合格的 candidateConstructors = candidates.toArray(new Constructor<?>[0]); } // 没有添加了@Autowired注解的构造方法,并且类中只有一个构造方法,并且是有参的 else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) { candidateConstructors = new Constructor<?>[] {rawCandidates[0]}; } ...... else { // 如果有多个有参、并且没有添加@Autowired的构造方法,是会返回空的 candidateConstructors = new Constructor<?>[0]; } this.candidateConstructorsCache.put(beanClass, candidateConstructors); } } } return (candidateConstructors.length > 0 ? candidateConstructors : null);}
复制代码


接下来,让我们更深入地探讨推断构造方法,并且我还简单绘制了一张图示。



通常情况下,一个类通常只包含一个构造方法:

要么是无参的构造方法,要么是有参的构造方法。

如果一个类只有一个无参构造方法,那么在实例化时只能使用这个构造方法;而如果一个类只有一个有参构造方法,实例化时是否可以使用这个构造方法则取决于具体情况:


  • 使用 AnnotationConfigApplicationContext 进行实例化时,Spring 会根据构造方法的参数信息去寻找对应的 bean,并将找到的 bean 传递给构造方法。这样可以实现依赖注入,确保实例化的对象具有所需的依赖项。

  • 当使用 ClassPathXmlApplicationContext 时,表明使用 XML 配置文件的方式来定义 bean。在 XML 中,可以手动指定构造方法的参数值来实例化对象,也可以通过配置 autowire=constructor 属性,让 Spring 自动根据构造方法的参数类型去寻找对应的 bean 作为参数值,实现自动装配。


上面是只有一个构造方法的情况,那么如果一个类存在多个构造方法,那么 Spring 进行实例化之前,该如何去确定到底用哪个构造方法呢?


  • 如果开发者明确定义了他们想要使用的构造方法,那么程序将会优先使用这个构造方法。

  • 如果开发者没有明确指定他们想要使用的构造方法,系统会自动检查开发者是否已经配置了让 Spring 框架自动选择构造方法的选项。

  • 如果开发者没有显式地指定让 Spring 框架自动选择构造方法的情况下,Spring 将会默认尝试使用无参构造方法。如果目标类中没有无参构造方法,则系统将会抛出错误提示。


针对第一点,开发者可以通过什么方式来指定使用哪个构造方法呢?


  • 在 XML 中,标签用于表示构造方法的参数。开发者可以根据这个标签确定所需使用的构造方法的参数个数,从而精确指定想要使用的构造方法。

  • 通过 @Autowired 注解,我们可以在构造方法上使用 @Autowired 注解。因此,当我们在特定构造方法上使用 @Autowired 注解时,表示开发者希望使用该构造方法。与通过 xml 方式直接指定构造方法参数值的方式不同,@Autowired 注解方式需要 Spring 通过 byType+byName 的方式来查找符合条件的 bean 作为构造方法的参数值。

  • 当然,还有一种情况需要考虑,即当多个构造方法上都标注了 @Autowired 注解时,此时 Spring 会抛出错误。然而,由于 @Autowired 注解具有一个 required 属性,默认值为 true,因此在一个类中只能有一个构造方法标注了 @Autowired 或者 @Autowired(required=true),否则会导致错误。不过,可以有多个构造方法标注 @Autowired(required=false)。在这种情况下,Spring 会自动从这些构造方法中选择一个进行注入。


在第二种情况下,如果开发者没有明确指定要使用的构造方法,Spring 将尝试自动选择一个合适的构造方法进行注入。这种情况下,只能通过 ClassPathXmlApplicationContext 实现,因为使用 AnnotationConfigApplicationContext 无法指定让 Spring 自动选择构造方法。通过 ClassPathXmlApplicationContext,可以在 XML 配置文件中指定某个 bean 的 autowire 属性为 constructor,从而告诉 Spring 可以自动选择构造方法。


总结


在 Spring 中,实例化 Bean 对象涉及构造方法的调用。通过分析源码,我们了解到实例化的步骤和推断构造方法的过程。当一个类只有一个构造方法时,Spring 会根据具体情况决定是否使用该构造方法。如果一个类存在多个构造方法,就需要根据具体情况具体分析。本文简单判断了哪些构造方法是符合实例化的,但在存在多个符合条件的构造方法时,具体使用哪个构造方法尚未讨论。因此,我计划单独写一篇文章来详细讨论这个问题。毕竟是太复杂了。


文章转载自:努力的小雨

原文链接:https://www.cnblogs.com/guoxiaoyu/p/18045706

体验地址:http://www.jnpfsoft.com/?from=001

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
解密Spring中的Bean实例化:推断构造方法(上)_Java_不在线第一只蜗牛_InfoQ写作社区