草庐IT

SpringBoot的自动装配

99Savage 2023-04-11 原文

SpringBoot的自动装配

大致流程

  1. 当启动SpringBoot应用程序的时候,会先创建SpringApplication对象,在构造方法中会进行一些参数的初始化工作,比如会加载Spring.factories文件,将文件的内容放到缓存对象中,方便后续获取

  2. SpringApplication对象创建完成后,开始调用run方法,启动过程中最主要有两个方法,第一个叫prepareContext(),第二个叫refreshContext()方法

  3. 在prepareContext()方法主要是对上下文对象ConfigurableApplicationContext的初始化操作,在整个过程中有个非常重要的方法就是load()方法,它会将当前启动类作为一个BeanDefinition注册到BeanDefinitionMap中,方便后续在进行BeanFactoryPostProcessor调用执行的时候,找到对应的启动类,来完成对应注解的解析工作

  4. 在refreshContext()方法会进行整个Spring容器的刷新refresh操作,会调用spring的refresh()方法,自动装配过程是在invokeBeanFactoryPostProcessor方法()中进行(也就是执行BeanFactory的后置处理器),在此方法主要是针对ConfigurationClassPostProcessor类的处理

  5. 在执行BeanFactory后置处理器的时候会调用ConfigurationClassPostProcessor类中的parse()方法去解析处理各种注解比如@CompomentScan、@Import等等

  6. 在解析@Import注解的时候比较特别,会有一个collectImports()方法,从主类开始递归解析注解,把所有包含@Import的注解都解析到BeanDefinitionMap中

  7. 调用AutoConfigurationImportSelector类(相当于一个处理器)中的process()方法进而触发getCandidateConfigurations()方法获取Spring.factories文件下的key为EnableAutoConfiguration的所有value,所以这就是为什么很多人的文章中都说Springboot的自动装配就是调用@EnableAutoConfiguration注解下的@Import中的AutoConfigurationImportSelector类,主要就是通过这种不断解析注解的方法去调用的

  1. 将所有解析到的注解的类都注册到BeanDifinitionMap中

  2. 至此就完成了SpringBoot的自动装配

完整流程(结合源码)

启动类
@SpringBootApplication
public class Test {
    public static void main(String[] args) {
        SpringApplication.run(Test.class, args);
        System.out.println("启动成功");
    }
}
SpringApplication的构造方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 初始化
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 加载Spring.factories文件,将文件的内容放到缓存对象中,方便后续获取
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 将mainApplicationClass属性设置为当前类Test的Clas对象
    this.mainApplicationClass = deduceMainApplicationClass();
}
run方法
public ConfigurableApplicationContext run(String... args) {
    // 省略代码
    try {
        // 准备环境,这里的environment环境包括系统环境,比如jvm的参数等等
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 准备Banner,即每次启动项目的时候控制台都会出现一个很大的图像
        Banner printedBanner = printBanner(environment);
        // 创建上下文对象
        context = createApplicationContext();
        // 准备上下文对象
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 刷新上下文对象
        refreshContext(context);
        // 省略代码
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }
}
prepareContext()方法

准备上下文对象,将springApplicationArguments对象和springBootBanner对象放入Spring一级缓存singletonObjects中

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
                            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    // 省略代码
    
    // 获取Beanfactory对象
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    // 将springApplicationArgusments放到一级缓存中
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    // 将springBootBanner放到一级缓存中
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    // 加载资源,也就是将当前的启动类Test Class对象放到Set中
    Set<Object> sources = getAllSources();
    // 见下
    load(context, sources.toArray(new Object[0]));
    // 将上下文加载到监听器中
    listeners.contextLoaded(context);
}
load()方法

将当前启动类作为一个BeanDefinition加载到BeanDefinitionMap中,方便后续在进行BeanFactoryPostProcessor调用执行的时候,找到对应的启动类,来完成对应注解的解析工作

private int load(Class<?> source) {
    // 判断启动类上有没有@Component注解
    if (isComponent(source)) {
        // 若有则将当前启动类作为一个BeanDefinition注册到BeanDifinitionMap中
        this.annotatedReader.register(source);
        return 1;
    }
    return 0;
}
refreshContext()方法

执行完prepareContext()方法后,调用Spring的刷新容器方法refresh()

private void refreshContext(ConfigurableApplicationContext context) {
    // 调用Spring的刷新容器方法
    refresh(context);
}
refresh()方法

这个方法相信大家非常熟悉了吧,那完成Springboot自动装配过程是在哪个阶段发生的呢?其实就是在invokeBeanFactoryPostProcessors()方法实现的

// 伪代码,方法在AbstractApplicationContext类中
public void refresh() throws BeansException, IllegalStateException {
    // 执行BeanFactory的后置处理器,自动装配中此方法主要是针对ConfigurationClassPostProcessor类的处理
    invokeBeanFactoryPostProcessors(beanFactory);
}
invokeBeanFactoryPostProcessors()方法

这里主要就是处理BeanFactory后置处理器,也就是直接或者间接实现BeanFactoryPostProcessor的接口的类,主要调用的是ConfigurationClassPostProcessor中的postProcessBeanDefinitionRegistry()方法

public static void invokeBeanFactoryPostProcessors(
    ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

    if (beanFactory instanceof BeanDefinitionRegistry) {
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

        for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
            if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                BeanDefinitionRegistryPostProcessor registryProcessor =
                    (BeanDefinitionRegistryPostProcessor) postProcessor;
                // 主要就是这个方法
                registryProcessor.postProcessBeanDefinitionRegistry(registry);
            }
        }
    }
}
ConfigurationClassPostProcessor类中的processConfigBeanDefinitions()方法

此方法作用就是去解析处理各种注解比如@CompomentScan、@Import等等注解

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // ...
    
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    // 获取对应的BeanDifinitionMap中的BeanDefinitionName
    String[] candidateNames = registry.getBeanDefinitionNames();

    // 创建注解解析器
    ConfigurationClassParser parser = new ConfigurationClassParser(
        this.metadataReaderFactory, this.problemReporter, this.environment,
        this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
        // 进行注解解析
        parser.parse(candidates);
    } while (!candidates.isEmpty());
    
    // ...
}
ConfigurationClassParser类中的parse()方法

解析注解的入口

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        if (bd instanceof AnnotatedBeanDefinition) {
            // 具体的解析注解的方法
            parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
        }
    }

    this.deferredImportSelectorHandler.process();
}
ConfigurationClassParser类中的doProcessConfigurationClass()方法

到达解析注解的最终方法,将解析到的Bean注册到BeanDifinitionMap中

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
    throws IOException {

    // 解析@PropertySource注解
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), PropertySources.class,
        org.springframework.context.annotation.PropertySource.class)) {
        if (this.environment instanceof ConfigurableEnvironment) {
            processPropertySource(propertySource);
        }
        else {
            logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
        }
    }

    // 解析@ComponentScan注解
    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) {
                BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                if (bdCand == null) {
                    bdCand = holder.getBeanDefinition();
                }
                if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                    parse(bdCand.getBeanClassName(), holder.getBeanName());
                }
            }
        }
    }

    // 解析@Import注解
    processImports(configClass, sourceClass, getImports(sourceClass), true);

    // 解析@ImportResource注解
    AnnotationAttributes importResource =
        AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    if (importResource != null) {
        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);
        }
    }

    // 解析@Bean注解
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    return null;
}
AutoConfigurationImportSelector类中的process()方法

这里的作用是读取Spring.factories文件中的内容,将所有的第三方的starter装载到BeanDefinitionMap中

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    
    // getAutoConfigurationEntry()方法读取Spring.factories文件中的内容入口
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
    
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}
至此,springboot的自动装配就完成了

创作不易,希望大家能够点个赞,也希望大家能帮忙指出问题,一起进步!!!谢谢大家~~

有关SpringBoot的自动装配的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  3. ruby-on-rails - 从应用程序中自定义文件夹内的命名空间自动加载 - 2

    我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty

  4. ruby-on-rails - 有没有一种工具可以在编码时自动保存对文件的增量更改? - 2

    我最喜欢的Google文档功能之一是它会在我工作时不断自动保存我的文档版本。这意味着即使我在进行关键更改之前忘记在某个点进行保存,也很有可能会自动创建一个保存点。至少,我可以将文档恢复到错误更改之前的状态,并从该点继续工作。对于在MacOS(或UNIX)上运行的Ruby编码器,是否有具有等效功能的工具?例如,一个工具会每隔几分钟自动将Gitcheckin我的本地存储库以获取我正在处理的文件。也许我有点偏执,但这点小保险可以让我在日常工作中安心。 最佳答案 虚拟机有些人可能讨厌我对此的回应,但我在编码时经常使用VIM,它具有自动保存功

  5. ruby - 在 ruby​​ 中使用自动创建插入数组 - 2

    我想知道是否可以通过自动创建数组来插入数组,如果数组不存在的话,就像在PHP中一样:$toto[]='titi';如果尚未定义$toto,它将创建数组并将“titi”压入。如果已经存在,它只会推送。在Ruby中我必须这样做:toto||=[]toto.push('titi')可以一行完成吗?因为如果我有一个循环,它会测试“||=”,除了第一次:Person.all.eachdo|person|toto||=[]#with1billionofperson,thislineisuseless999999999times...toto.push(person.name)你有更好的解决方案吗?

  6. 【自动驾驶环境感知项目】——基于Paddle3D的点云障碍物检测 - 2

    文章目录1.自动驾驶实战:基于Paddle3D的点云障碍物检测1.1环境信息1.2准备点云数据1.3安装Paddle3D1.4模型训练1.5模型评估1.6模型导出1.7模型部署效果附录show_lidar_pred_on_image.py1.自动驾驶实战:基于Paddle3D的点云障碍物检测项目地址——自动驾驶实战:基于Paddle3D的点云障碍物检测课程地址——自动驾驶感知系统揭秘1.1环境信息硬件信息CPU:2核AI加速卡:v100总显存:16GB总内存:16GB总硬盘:100GB环境配置Python:3.7.4框架信息框架版本:PaddlePaddle2.4.0(项目默认框架版本为2.3

  7. ruby-on-rails - 自动完成搜索的 Rails 实现 - 2

    我不确定如何为我的搜索功能添加自动完成表单。"get"do%>nil%>我有一个具有自定义操作的Controllerdefquery@users=Search.user(params[:query])@article=Search.article(params[:query])end模型如下:defself.user(search)ifsearchUser.find(:all,:conditions=>['first_nameLIKE?',"%#{search}%"])elseUser.find(:all)endenddefself.article(search)ifsearchArt

  8. ruby - 如何设置自动测试以仅重新运行失败的 rspec 示例 - 2

    我对自动测试的工作方式的印象(基于cucumbergithubwiki和其他在线内容)是它应该重新运行红色示例,直到它们通过。我的问题是它会重新运行规范文件中找到失败示例的所有示例,包括通过的示例。我不想浪费时间在修复失败示例的同时重新运行通过的示例。是否可以配置自动测试以便仅运行失败的示例? 最佳答案 您需要rspec-retrygem。以下是文档中有关如何实现它的一些示例:将它应用到覆盖整个测试套件的configureblock中...RSpec.configuredo|config|config.verbose_retry=t

  9. ruby - 在多个线程中引用类方法会导致自动加载循环依赖崩溃 - 2

    代码:threads=[]Thread.abort_on_exception=truebegin#throwexceptionsinthreadssowecanseethemthreadseputs"EXCEPTION:#{e.inspect}"puts"MESSAGE:#{e.message}"end崩溃:.rvm/gems/ruby-2.1.3@req/gems/activesupport-4.1.5/lib/active_support/dependencies.rb:478:inload_missing_constant':自动加载常量MyClass时检测到循环依赖稍加研究后,

  10. ruby - 自动将院子文档框架添加到现有的 Rails 遗留代码中 - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭4年前。Improvethisquestion我希望能够将模板化的YARD文档样式注释插入到我现有的Rails遗留应用程序中。目前它的评论很少。我想要具有指定参数的类header和方法header(通过从我假定的方法签名中提取)和返回值的占位符。在PHP代码中,我有一些工具可以检查代码并在适当的位置创建插入到代码中的文档header注释。在带有Ducktyping等的Ruby中,我确信诸如@params等类型之类

随机推荐