写点什么

【深入浅出 Spring 原理及实战】「源码调试分析」结合 DataSourceRegister 深入分析 ImportBeanDefinitionRegistrar 的源码运作流程

作者:洛神灬殇
  • 2023-01-16
    江苏
  • 本文字数:13267 字

    阅读完需:约 44 分钟

【深入浅出Spring原理及实战】「源码调试分析」结合DataSourceRegister深入分析ImportBeanDefinitionRegistrar的源码运作流程

每日一句

人的一生中不可能会一帆风顺,总会遇到一些挫折,当你对生活失去了信心的时候,仔细的看一看、好好回想一下你所遇到的最美好的事情吧,那会让你感觉到生活的美好



注入案例代码

如何通过实现 SpringBoot 框架带有的 ImportBeanDefinitionRegistrar 注册器,注入我们想要注册的 bean 对象实例。只需要采用 @Import 的注解进行注入对应的一类相关的 bean 对象。


@Import({DataSourceRegister.class,A.class})@SpringBootApplication@ComponentScan("com.libo")public class LiboApplication {    public static void main(String[] args) {        SpringApplication sa = new SpringApplication(LiboApplication.class);        sa.run(args);    }}
复制代码

DataSourceRegister 的开发实现

  • 在 springboot 启动的时候,loader 模块会根据“清单文件”加载该 Application 类,并反射调用 psvm 入口函数 main,@Import 注解也可以导入一个常规类,并且创建注入很多对象实例

  • DataSourceRegister 类是用来进行初始化数据源和并提供了执行动态切换数据源的工具类。

DataSourceRegister 注入主数据源和从数据源

这里 DataSourceRegister 继承的 EnvironmentAware 接口,没有真正意义上去用它的用途,本身可以通过这个 setEnvironment 方法,进行注入 Environment 对象,从而可以读取其他的配置信息,目前主要用作一个 hook 方法。

读取对应的环境变量

public final void setEnvironment(Environment environment) {        DruidEntity druidEntity = FileUtil.      readYmlByClassPath("db_info", DruidEntity.class);        defaultTargetDataSource =      DataSourceUtil.createMainDataSource(druidEntity);    }
复制代码


主要用于读取 Druid 的数据源模型信息。进行创建对应的数据源对象 defaultTargetDataSource

注入 Bean 到 Spring 容器中

registerBeanDefinitions 注册 BeanDefinition 对象模型

    public final void registerBeanDefinitions(AnnotationMetadata  annotationMetadata, BeanDefinitionRegistry  beanDefinitionRegistry) {        // 0.将主数据源添加到数据源集合中        DataSourceSet.putTargetDataSourcesMap(MAINDATASOURCE,defaultTargetDataSource);        //1.创建DataSourceBean        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();        beanDefinition.setBeanClass(DataSource.class);        beanDefinition.setSynthetic(true);        MutablePropertyValues mpv = beanDefinition.getPropertyValues();        //spring名称约定为defaultTargetDataSource和targetDataSources        mpv.addPropertyValue("defaultTargetDataSource",defaultTargetDataSource);        mpv.addPropertyValue("targetDataSources",DataSourceSet.getTargetDataSourcesMap());        beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);    }
复制代码

完整的 DataSourceRegister 的案例

public class DataSourceRegister<T> implements EnvironmentAware, ImportBeanDefinitionRegistrar {
private javax.sql.DataSource defaultTargetDataSource;
static final String MAINDATASOURCE = "mainDataSource";
public final void setEnvironment(Environment environment) { DruidEntity druidEntity = FileUtil. readYmlByClassPath("db_info", DruidEntity.class); defaultTargetDataSource = DataSourceUtil.createMainDataSource(druidEntity); }
public final void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { // 0.将主数据源添加到数据源集合中 DataSourceSet.putTargetDataSourcesMap(MAINDATASOURCE, defaultTargetDataSource); //1.创建DataSourceBean GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); //spring名称约定为defaultTargetDataSource和targetDataSources mpv.addPropertyValue("defaultTargetDataSource", defaultTargetDataSource); mpv.addPropertyValue("targetDataSources", DataSourceSet.getTargetDataSourcesMap()); beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition); }}
复制代码

执行流程解读

  • 动态数据源注册器类实现了 ImportBeanDefinitionRegistrar 接口,没错就是这个原因,由于实现了该接口让该类成为了拥有注册 bean 的能力。

  • 原理上也能说得通作为一个 Bean 的注册类是没有必要被注册为 Spring 容器的 Bean 对象

  • 虽然这样解释也不为过但我仍然想一探究竟,本来想大概找找 spring 涉及关键类如:ConfigurationClass,ConfigurationClassParser 等,接下来我们需要看一下 SpringBoot 的总体加载流程。

SpringApplication 类的 run 方法

SpringBoot 启动时使用了 SpringApplication 类的 run 方法来牵引整个 spring 的初始化过程,源码如下。


public ConfigurableApplicationContext run(String... args) {        StopWatch stopWatch = new StopWatch();        stopWatch.start();        ConfigurableApplicationContext context = null;     // 错误原因分析器        FailureAnalyzers analyzers = null;        this.configureHeadlessProperty();     // 重点分析:启动所有的运行的监听器        SpringApplicationRunListeners listeners = this.getRunListeners(args);        listeners.starting();        try {      // 解析ApplicationArgument数据            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);      // 通过监听器和传递的参数,实现相关的配置环境对象信息,预先      // 进行配置Environment对象,设置全局环境变量容器            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);      // 输出相关的Banner控制,根据配置以及相关的Environment            Banner printedBanner = this.printBanner(environment);      // 创建Spring容器上下文。            context = this.createApplicationContext();      //创建和赋值错误解析器            analyzers = new FailureAnalyzers(context);      // 准备环境上下文进行配置相关的容器的上下文的参数。            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);      // 刷新上下文容器            this.refreshContext(context);      // 后置执行上下文操作            this.afterRefresh(context, applicationArguments);      // 完成监听器的后置完成处理操作            listeners.finished(context, (Throwable)null);            stopWatch.stop();            if(this.logStartupInfo) {                (new StartupInfoLogger(this.mainApplicationClass)).          logStarted(this.getApplicationLog(), stopWatch);            }            return context;        } catch (Throwable var9) {            this.handleRunFailure(context, listeners,                   (FailureAnalyzers)analyzers,                   var9);            throw new IllegalStateException(var9);        }    }
复制代码


根据上面的源码流程可以分析重点的加载过程


  1. 重点分析:启动所有的运行的监听器,获取 SpringApplicationRunListeners; 从类路径下 META-INF/spring.factories。回调所有的获取 SpringApplicationRunListener.starting() 方法。


 SpringApplicationRunListeners listeners = this.getRunListeners(args); listeners.starting();
复制代码


  1. 解析 ApplicationArgument 数据以及封装命令行参数,通过 program 参数的部分进行解析,并且加载到 PropertiesSourcePlaceHolder 中的环境变量容器内部。


ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
复制代码


通过上面的监听器和传递的参数,实现相关的配置环境对象信息,预先进行配置 Environment 对象,设置全局环境变量容器


  1. 根据上面的全局变量容器进行配置以及初始化相关的 Environment 对象。


ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
复制代码


  1. 创建 Spring 容器上下文,在 createApplicationContext 当中,由于我们是 web 项目,则 spring 默认给我们创建了一个


context = this.createApplicationContext();
复制代码



AnnotationConfigEmbeddedWebApplicationContext,当然它也是继承 GenericWebApplicationContext 类和 GenericApplicationContext 类的,那么他默认会持有一个 DefaultListableBeanFactory 对象,这个对象可以用来创建 Bean。


  1. 准备环境上下文进行配置相关的容器的上下文的参数


this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
复制代码


  1. 刷新上下文容器,refreshContext 就是在做 spring 运行后的初始化工作。


this.refreshContext(context);
复制代码


接着往下走,进入 refreshContext 中会调用一系列的 refresh 方法,最终进入 AbstractApplicationContext 中,主要将 SpringBoot 的容器对象数据和原本基础的 Spring Framework 的框架对象进行加载到容器中。


   @Override    public void refresh() throws BeansException, IllegalStateException {    synchronized (this.startupShutdownMonitor) {      // Prepare this context for refreshing.            prepareRefresh();            // Tell the subclass to refresh the internal bean factory.            ConfigurableListableBeanFactory beanFactory =        obtainFreshBeanFactory();            // Prepare the bean factory for use in this context.            prepareBeanFactory(beanFactory);            try {                // Allows post-processing of the bean factory in context subclasses.                postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory);
// Initialize message source for this context. initMessageSource();
// Initialize event multicaster for this context. initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses. onRefresh();
// Check for listener beans and register them. registerListeners();
// Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event. finishRefresh(); }
catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex); }
// Destroy already created singletons to avoid dangling resources. destroyBeans();
// Reset 'active' flag. cancelRefresh(ex);
// Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
复制代码


invokeBeanFactoryPostProcessors() 方法就是 Bean 在注册前期做的一系列数据收集工作,BeanDefinitionRegistry 的容器注册 BeanDefinition 之前,调用相关的


跟着堆栈继续深入,会进入到这个方法中,这个方法就是初始化 bean 前的所有轨迹:



在 invokeBeanFactoryPostProcessors 方法中继续跟进一系列方法就会看到在一开始的时候 spring 会初始化几个系统固有的 Bean



继续调试后的关键点出现在这个方法中:


public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {        List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();        String[] candidateNames = registry.getBeanDefinitionNames();        for (String beanName : candidateNames) {            BeanDefinition beanDef = registry.getBeanDefinition(beanName);            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||                    ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {                if (logger.isDebugEnabled()) {                    logger.debug("Bean definition has already been processed                  as a configuration class: " + beanDef);                }            }            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));            }        }        // Return immediately if no @Configuration classes were found        if (configCandidates.isEmpty()) {            return;        }        // Sort by previously determined @Order value, if applicable        Collections.sort(configCandidates, new              Comparator<BeanDefinitionHolder>() {            @Override            public int compare(BeanDefinitionHolder bd1,                  BeanDefinitionHolder bd2) {                int i1 =           ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());                int i2 =           ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());                return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;            }        });        // Detect any custom bean name generation strategy supplied through the enclosing application context        SingletonBeanRegistry sbr = null;        if (registry instanceof SingletonBeanRegistry) {            sbr = (SingletonBeanRegistry) registry;            if (!this.localBeanNameGeneratorSet && sbr.containsSingleton(CONFIGURATION_BEAN_NAME_GENERATOR)) {                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);                this.componentScanBeanNameGenerator = generator;                this.importBeanNameGenerator = generator;            }        }        // Parse each @Configuration class        ConfigurationClassParser parser = new ConfigurationClassParser(                this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);        Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());        do {            parser.parse(candidates);            parser.validate();            Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());            configClasses.removeAll(alreadyParsed);            // Read the model and create bean definitions based on its content            if (this.reader == null) {                this.reader = new ConfigurationClassBeanDefinitionReader(                        registry, this.sourceExtractor, this.resourceLoader, this.environment,                        this.importBeanNameGenerator, parser.getImportRegistry());            }            this.reader.loadBeanDefinitions(configClasses);            alreadyParsed.addAll(configClasses);            candidates.clear();            if (registry.getBeanDefinitionCount() > candidateNames.length) {                String[] newCandidateNames = registry.getBeanDefinitionNames();                Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));                Set<String> alreadyParsedClasses = new HashSet<String>();                for (ConfigurationClass configurationClass : alreadyParsed) {                  alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());                }                for (String candidateName : newCandidateNames) {                    if (!oldCandidateNames.contains(candidateName)) {                        BeanDefinition bd = registry.getBeanDefinition(candidateName);                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {                            candidates.add(new BeanDefinitionHolder(bd, candidateName));                        }                    }                }                candidateNames = newCandidateNames;            }        }        while (!candidates.isEmpty());
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (sbr != null) { if (!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } }
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } }
复制代码


而通过不断重复调试确定获得注册 Bean 的列表应该发生在配置的“剖析阶段”,也就是 parser.parse(candidates);这个方法的内部,到了这里基本问题的答案已经要浮出水面了,我也不再粘贴无用的代码,如果你真的对这个问题比骄傲好奇可以自己跟踪并练习调试的源码技巧!


当然在 ConfigurationClassParser 这个类中 parse 方法也是不少,只要静下心来逐渐分析,马上就能准确的找到 Override 的 parse 方法。


protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {            return;        }
ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } // Otherwise ignore new imported config class; existing non-imported class overrides it. return; } else { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) { if (configClass.equals(it.next())) { it.remove(); } } } }
// Recursively process the configuration class and its superclass hierarchy. SourceClass sourceClass = asSourceClass(configClass); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass);//处理定义的配置类 } while (sourceClass != null);
this.configurationClasses.put(configClass, configClass); }
复制代码


protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)            throws IOException {
// Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } }
// Process any @ComponentScan annotations Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { if (ConfigurationClassUtils.checkConfigurationClassCandidate( holder.getBeanDefinition(), this.metadataReaderFactory)) { parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName()); } } } }
// Process any @Import annotations processImports(configClass, sourceClass, getImports(sourceClass), true);//处理注解导入的类型
// Process any @ImportResource annotations if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) { AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } }
// Process individual @Bean methods Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); }
// Process default methods on interfaces processInterfaces(configClass, sourceClass);
// Process superclass, if any if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } }
// No superclass -> processing is complete return null; }
复制代码


以上两个方法中标红的就是关键点。而且 spring 的大师们也把注释写的十分明显:”//Process any @Import annotations“,到这里已经彻底豁然开朗!


spring 会先去处理 scan,将你程序内部的所有要注册的 Bean 全部获得(自然包括那些 configuration),这里统称为 ConfigurationClass,scan 全部整理完毕后才会去处理 @Import 注解时导入的类!


我们回到最初的问题 DataSourceRegister 和 A 两个类为什么 A 成为了 Bean 但 DataSourceRegister 却未成为 Bean 呢?


在 processImports 方法中,很明显 candidate.isAssignable(ImportBeanDefinitionRegistrar.class)时操作为:configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());而普通的类通过 processConfigurationClass(candidate.asConfigClass(configClass));方法,最终会被放在 ConfigurationClassParser 类的成员变量 configurationClasses 中,最终被初始化为 Bean。


private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,            Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
if (importCandidates.isEmpty()) { return; }
if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { for (SourceClass candidate : importCandidates) { if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<?> candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); ParserStrategyUtils.invokeAwareMethods( selector, this.environment, this.resourceLoader, this.registry); if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) { this.deferredImportSelectors.add( new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector)); } else { String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames); processImports(configClass, currentSourceClass, importSourceClasses, false); } } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class<?> candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar, this.environment, this.resourceLoader, this.registry); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass)); } } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } finally { this.importStack.pop(); } } }
复制代码


后置执行上下文操作


this.afterRefresh(context, applicationArguments);
复制代码


完成监听器的后置完成处理操作


listeners.finished(context, (Throwable)null);
复制代码


至此,总体的 mportBeanDefinitionRegistrar 的对象注入体系就基本介绍完了

发布于: 2023-01-16阅读数: 26
用户头像

洛神灬殇

关注

🏆 InfoQ写作平台-签约作者 🏆 2020-03-25 加入

【个人简介】酷爱计算机科学、醉心编程技术、喜爱健身运动、热衷悬疑推理的“极客达人” 【技术格言】任何足够先进的技术都与魔法无异 【技术范畴】Java领域、Spring生态、MySQL专项、微服务/分布式体系和算法设计等

评论

发布
暂无评论
【深入浅出Spring原理及实战】「源码调试分析」结合DataSourceRegister深入分析ImportBeanDefinitionRegistrar的源码运作流程_spring_洛神灬殇_InfoQ写作社区