万字长文!带你探索 Bean 加载流程

宏观地说,Bean 加载流程大致有三个阶段,分别是实例化 createBeanInstance() 、属性填充 populateBean() 和 初始化 initializeBean(),当 Bean 加载流程执行完毕,Bean 才具备使用条件!对 Bean 加载流程的探索是一段非常煎熬的旅程,你准备好了吗?

1 写在前面

为了让 Spring IoC 容器正确地完成 Bean 加载流程,必须要有一个数据模型来承载 Bean 的配置元数据,配置元数据可以告诉 Spring IoC 容器 如何创建 Bean 实例何时创建 Bean 实例Bean 的生命周期细节 等信息,这个数据模型就是BeanDefinition接口,在其众多实现类中,RootBeanDefinition 最为常用!

在 Spring Boot 中,BeanDefinition 可以归纳为两类:一种是由开发人员声明的,另一种是由官方及第三方起步依赖 starter 组件中声明的,当然这两种 BeanDefinition 一般都是通过注解声明的。为了加载这两种 BeanDefinition,Spring Boot 会率先完成一个名为 org.springframework.context.annotation.internalConfigurationAnnotationProcessorConfigurationClassPostProcessor类型的 Bean 加载流程。ConfigurationClassPostProcessor 实现了BeanDefinitionRegistryPostProcessor接口。

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry);
}
复制代码

BeanDefinitionRegistryPostProcessor 继承自BeanFactoryPostProcessor接口,后者是一个极其重要的 Spring IoC 拓展点。

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}
复制代码

BeanFactoryPostProcessor 常用于与 BeanDefinition 交互,而非与 Bean 实例交互,因为当执行到postProcessBeanFactory()方法时,只是完成了所有 BeanDefinition 的加载而已,Bean 还没有实例化呢。我们完全可以通过 BeanFactoryPostProcessor 将某一 BeanDefinition 实例的 beanClass 属性值替换为CGLIB所生成的 Class 实例,这样后期通过该 BeanDefinition 实例所生成的 Bean 实例就得到增强了。

当执行到 ConfigurationClassPostProcessor 中的postProcessBeanDefinitionRegistry()方法时,Spring Boot 已经提前将由@SpringBootApplication注解标注的启动类注册到了BeanDefinitionRegistry中去,那么 ConfigurationClassPostProcessor 会根据这个启动类来挖掘出所有的 BeanDefinition,接着将它们注册到 BeanDefinitionRegistry 中。

为什么一个由 @SpringBootApplication 注解标注的启动类可以衍生出全部的 BeanDefinition 呢?因为它头上有三个重要的元注解,分别是:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan。其中,@ComponentScan 注解用于衍生出 classpath 下特定包名内的 BeanDefinition,一般这个包名就是 Spring Boot 启动类所在的包名,这也正是为什么启动类总是处于顶级包名下的答案;而 @EnableAutoConfiguration 注解则用于衍生出官方及第三方起步依赖组件中的 BeanDefinition,如果没有这个注解,那么一切官方及第三方 starter 组件都会趴窝。

继续回到 ConfigurationClassPostProcessor 中来。限于篇幅这里仅放出其核心外围源码,如下所示。其中,postProcessBeanDefinitionRegistry() 方法是先于 postProcessBeanFactory() 方法执行的。

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
    /**
     * Derive further bean definitions from the configuration classes in the registry.
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        processConfigBeanDefinitions(registry);
    }

    /**
     * Prepare the Configuration classes for servicing bean requests at runtime
     * by replacing them with CGLIB-enhanced subclasses.
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        enhanceConfigurationClasses(beanFactory);
        beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
    }
}
复制代码

1.1 postProcessBeanDefinitionRegistry()

postProcessBeanDefinitionRegistry() 方法无疑是 ConfigurationClassPostProcessor 中最为核心的内容,它可以将那些由开发人员和起步依赖组件中所声明的 BeanDefinition 全部加载出来,并将其注册到 BeanDefinitionRegistry 中去。

Spring 提供了一个 ConfigurationClass类,用于抽象那些由 @Configuration 注解标记的配置类。metadata 属性用于标识配置类头上的注解元数据;beanMethods 属性用于收集配置类中由 @Bean 注解标识的方法;importedBy 用于收集当前配置类是由哪些配置类通过 @Import 注解引入的;importedResources 属性用于收集当前配置类通过 @ImportResource 注解所引入的包含若干 Bean 定义信息的 XML 文件;importBeanDefinitionRegistrars 属性用于收集当前配置类通过 @Import 注解引入的 ImportBeanDefinitionRegistrar。

public final class ConfigurationClass {
    private final AnnotationMetadata metadata;
    private final Resource resource;
    private String beanName;
    private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1);
    private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();
    private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
            new LinkedHashMap<>();
    private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
            new LinkedHashMap<>();
}			
复制代码

第一个 ConfigurationClass 实例就是 Spring Boot 的启动类!ConfigurationClassPostProcessor 会委托ConfigurationClassParser来解析 ConfigurationClass 实例以填充其属性,而一个 ConfigurationClass 实例可能衍生出若干个 ConfigurationClass 实例,最终这些 ConfigurationClass 实例会被保存在 ConfigurationClassParser 中的成员变量 configurationClasses 中,然后 ConfigurationClassPostProcessor 会委派ConfigurationClassBeanDefinitionReader 负责将每一个 ConfigurationClass 实例中所蕴含的 BeanDefinition 加载出来。

相较于 BeanDefinitionReader 对 BeanDefinition 的加载,ConfigurationClassParser 对 ConfigurationClass 的解析更为吸引人,这一过程涉及对nested member class@PropertySource@ComponentScan@Import@ImportResource@Beandefault methods on interfaces 这七个目标的解析。

  • nested member class 指的是当前 ConfigurationClass 的嵌套成员静态配置类,这些配置类的头上如果有 @Component、@ComponentScan、@Import、和 @ImportResource 这四种注解任意一种标记,或者其包含由 @Bean 注解标记的方法,那么 ConfigurationClassParser 会将其封装为一个 ConfigurationClass 实例,然后对其进行递归解析。

  • @PropertySource 注解常用于引入一个或多个外部配置源文件,ConfigurationClassParser 会将每一个外部配置源文件封装为一个PropertySource实例,然后直接将其塞进Environment中。

  • 对于 @ComponentScan 注解,ConfigurationClassParser 首先委派 ComponentScanAnnotationParser 进行扫描。具体地,ComponentScanAnnotationParser 会根据 @ComponentScan 注解中 basePackages 与 basePackageClasses 这俩属性来探测出所要扫描的包名,如果没有获取到,就根据 @ComponentScan 注解所依附宿主类的包名作为扫描目标;然后将目标包下所有由 @Component 注解标注的类扫描出来并封装为 BeanDefinition 实例,接着逐个将 BeanDefinition 实例注册到 BeanDefinitionRegistry 中。

  • @Import 注解用于引入外部配置类,外部配置类可以是由 @Configuration 注解标记的常规配置类,也可以是 ImportSelector 和 ImportBeanDefinitionRegistrar 接口的实现类。processImports() 方法正是 ConfigurationClassParser 处理这三类目标的核心逻辑所在。其主要逻辑为:1) 如果引入的是 ImportSelector,则递归调用 processImports() 方法;2) 如果引入的是 DeferredImportSelector,则延迟处理;3) 如果引入的是 ImportBeanDefinitionRegistrar,则填充当前 ConfigurationClass 实例中的 importBeanDefinitionRegistrars 属性;4) 如果引入的是普通 @Configuration 配置类,则构建一个新的 ConfigurationClass 实例然后递归解析该 ConfigurationClass 实例。

  • @ImportResource 注解常用于引入一个或多个包含若干 Bean 定义信息的 XML 文件。一般大家是用不到这个注解的,但有些老的模块可能依然是通过 XML 文件来声明 Bean 定义信息,那么 @ImportResource 注解恰好提供了这种兼容能力。ConfigurationClassParser 正是在这里填充当前 ConfigurationClass 实例的 importedResources 属性。

  • 对于方法级的 @Bean 注解,ConfigurationClassParser 会为其封装为 BeanMethod 实例,然后将其回填到当前 ConfigurationClass 实例的 beanMethods 属性中。

  • default methods on interfaces 指的是 Java 8 接口中由default关键字修饰的默认方法。ConfigurationClassParser 会获取当前 ConfigurationClass 所实现的接口,然后探测出接口中所有 @Bean 方法,依然是填充当前 ConfigurationClass 实例的 beanMethods 属性。

1.2 postProcessBeanFactory()

当执行到 postProcessBeanFactory() 方法,这意味着所有的 BeanDefinition 已经完成了加载,它们都安静地躺在 BeanDefinitionRegistry 中。 而enhanceConfigurationClasses()方法是 postProcessBeanFactory() 方法中的唯一逻辑,咱们一起来瞅瞅它干了啥吧。

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
    public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
        Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
            Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
            if ("full".equals(configClassAttr)) {
                configBeanDefs.put(beanName, abd);
            }
        }
        if (configBeanDefs.isEmpty()) {
            return;
        }
        ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
        for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
            AbstractBeanDefinition beanDef = entry.getValue();
            // Set enhanced subclass of the user-specified bean class
            Class<?> configClass = beanDef.getBeanClass();
            Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
            if (configClass != enhancedClass) {
                beanDef.setBeanClass(enhancedClass);
            }
        }
    }
}
复制代码

代码不多,理解起来很容易。遍历所有 BeanDefinition 实例,将所有包含 key 为 ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE、value 为 full 的 BeanDefinition 实例 收集到局部变量 configBeanDefs 中,然后使用 CGLIB 生成全新的 Class 实例并替换 BeanDefinition 实例的 beanClass 属性值,后续这些 Bean 均将会被代理类增强。

以 key 为 ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE、value 为 full 的键值对儿是啥时候塞到 BeanDefinition 实例的 attribute 中去的呢?当然是在 postProcessBeanDefinitionRegistry() 方法中塞进去的,主要内容如下。

public abstract class ConfigurationClassUtils {
    public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
        String className = beanDef.getBeanClassName();
        if (className == null || beanDef.getFactoryMethodName() != null) {
            return false;
        }
        AnnotationMetadata metadata;
        if (beanDef instanceof AnnotatedBeanDefinition
                && className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
            metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
        } else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
            Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
            if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
                    BeanPostProcessor.class.isAssignableFrom(beanClass) ||
                    AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
                    EventListenerFactory.class.isAssignableFrom(beanClass)) {
                return false;
            }
            metadata = AnnotationMetadata.introspect(beanClass);
        } else {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
            metadata = metadataReader.getAnnotationMetadata();
        }
        Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
        if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
        } else if (config != null || isConfigurationCandidate(metadata)) {
            beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
        } else {
            return false;
        }
        Integer order = getOrder(metadata);
        if (order != null) {
            beanDef.setAttribute(ORDER_ATTRIBUTE, order);
        }
        return true;
    }
}
复制代码

忽略 根据 BeanDefinition 获取其 AnnotationMetadata 的逻辑,从上面代码中,我们可以很轻松地总结出以下规律:

  1. 若 @Configuration 注解中 proxyBeanMethods 属性值为 true,则执行 beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "full");

  2. 若 @Configuration 注解中 proxyBeanMethods 属性值为 false,则执行 beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, "lite")。

经过第一章节的分析,我们大致了解了 BeanDefinition 的加载逻辑,接下来将会面临更大的挑战,就是对 Bean 加载流程的探索。

2 实例化

BeanFactory是 Spring 针对 IoC 容器提供的最顶级抽象接口,其主要用于加载 Bean。众所周知,相较于 BeanFactory,ApplicationContext拥有更多的拓展功能,因此后者在企业级应用中更为常用,但今天笔者想给大家介绍 BeanFactory 的另一个拓展,即AutowireCapableBeanFactory。AutowireCapableBeanFactory 提供了两个重要的方法,分别是:createBean()autowireBean()。createBean() 方法可以用于构建一个不受 IoC 容器纳管的 Bean 实例,而且在实例化 Bean 之后,也会进行 populateBean() 和 initializeBean() 操作;而 autowireBean() 方法就更为有用了,它可以为那些头上没有 stereotype 注解的普通类注入 IoC 容器中的单例 Bean,这很酷对不对?

回归正题!Bean 加载的统一入口位于AbstractBeanFactory中的getBean()方法处,熟悉 Spring 的朋友应该都知道:真正干活的往往是那些 doXXX 开头的方法。这里也不例外,doGetBean()方法源码内容如下。

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
    @Override
    public Object getBean(String name) throws BeansException {
        return doGetBean(name, null, null, false);
    }

    @Override
    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return doGetBean(name, requiredType, null, false);
    }

    @Override
    public Object getBean(String name, Object... args) throws BeansException {
        return doGetBean(name, null, args, false);
    }

    public <T> T getBean(String name, Class<T> requiredType, Object... args) throws BeansException {
        return doGetBean(name, requiredType, args, false);
    }

    protected <T> T doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly) throws BeansException {
        // § 2.1
        String beanName = transformedBeanName(name);
        Object beanInstance;

        // § 2.2
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            // § 2.3
            beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        } else {
            // § 2.4
            BeanFactory parentBeanFactory = getParentBeanFactory();
            if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                // Not found -> check parent.
                String nameToLookup = originalBeanName(name);
                if (parentBeanFactory instanceof AbstractBeanFactory abf) {
                    return abf.doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
                } else if (args != null) {
                    return (T) parentBeanFactory.getBean(nameToLookup, args);
                } else if (requiredType != null) {
                    return parentBeanFactory.getBean(nameToLookup, requiredType);
                } else {
                    return (T) parentBeanFactory.getBean(nameToLookup);
                }
            }

            // § 2.5
            if (!typeCheckOnly) {
                markBeanAsCreated(beanName);
            }

            try {
                RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

                // § 2.6
                String[] dependsOn = mbd.getDependsOn();
                if (dependsOn != null) {
                    for (String dep : dependsOn) {
                        if (isDependent(beanName, dep)) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                        }
                        registerDependentBean(dep, beanName);
                        try {
                            getBean(dep);
                        } catch (NoSuchBeanDefinitionException ex) {
                            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                    "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                        }
                    }
                }

                // § 2.7
                if (mbd.isSingleton()) {
                    sharedInstance = getSingleton(beanName, () -> {
                        try {
                            return createBean(beanName, mbd, args);
                        } catch (BeansException ex) {
                            destroySingleton(beanName);
                            throw ex;
                        }
                    });
                    beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
                }
            } catch (BeansException ex) {
                cleanupAfterBeanCreationFailure(beanName);
                throw ex;
            }
        }
        
        // § 2.8
        return adaptBeanInstance(name, beanInstance, requiredType);
    }
}
复制代码

代码很多,笔者已经在核心内容上打了标签 ( § 2.1 - § 2.8 ),下面就以这些标签作为独立的小节,逐一分析这些核心内容。

§ 2.1 转换 beanName

transformedBeanName() 方法会对传进来的 beanName 进行转换。如果传进来的 beanName 以 & 为前缀,那么这里会移除该前缀;如果传进来的 beanName 是某一个 Bean 的 aliasName,那么找到该 aliasName 真正所指向的 beanName。

一般情况下,Sp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值