zoukankan      html  css  js  c++  java
  • SpringBoot自动装配源码

    前几天,面试的时候被问到了SpringBoot的自动装配的原理。趁着五一的假期,就来整理一下这个流程。

    我这里使用的是idea创建的最简单的SpringBoot项目。

    我们都知道,main方法是java的启动入口,我们在开发SpringBoot项目的时候,他的启动类如下所示:

    /**
     * @SpringBootApplication用来标注一个主程序类,说明这是一个springboot应用
     */
    @SpringBootApplication
    public class SpringbootdemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringbootdemoApplication.class, args);
        }
    }
    

    从上面代码可以看出,SpringBoot的启动类中最主要的就是:@SpringBootApplicationSpringApplication.run()方法。但是SpringBoot是如何找到当前程序的主类,并且获取类上的注解的呢?

    1.加载main主类

    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }
    

    从上面的代码可以看出,推断出主应用程序的类,通过跟踪执行过程中的堆栈信息,找到main函数之后,通过Class.forName的方式反射生成对应的对象(SpringbootdemoApplication)。

    2.执行run方法

    从run方法开始,一直到prepareContext方法之前,都是在做准备工作

    public ConfigurableApplicationContext run(String... args) {
       	// 创建StopWatch实例,用来记录SpringBoot的启动时间
    	StopWatch stopWatch = new StopWatch();
    	stopWatch.start();
        // 创建为null的上下文对象
    	ConfigurableApplicationContext context = null;
        // 创建异常报告器
    	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    	configureHeadlessProperty();
        // 获取监听器,读取META-INF/spring.factories文件中SpringApplicationRunListeners类型存入到集合中
    	SpringApplicationRunListeners listeners = getRunListeners(args);
        // 循环调用监听starting的方法
    	listeners.starting();
    	try {
            // 获取启动时传入的参数
    		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 构建当前环境
    		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            // 根据环境信息配置要忽略的bean
    		configureIgnoreBeanInfo(environment);
            // 打印SpringBoot的版本和banner,如果需要修改banner,可以在resources下面新建一个banner.txt文件,将内容拷贝进去
    		Banner printedBanner = printBanner(environment);
            // 使用策略方法创建上下文对象,分为以下三种类型的:
            // 1.SERVLET:基于servlet的web应用程序运行,并启动嵌入式的servlet web服务器
            // 2.REACTIVE:基于反应式web应用程序运行,并启动嵌入式的反应式web服务器
            // 3.NONE:不以web应用程序运行,也不以嵌入式的web应用程序运行
            // 这一步会创建5个bean实例,放在下图中的beanDefinitionMap中
    		context = createApplicationContext();
            // 获取异常报告器
    		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    				new Class[] { ConfigurableApplicationContext.class }, context);
            // 完成整个容器的创建和启动以及bean的注入功能,
    		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 最终会调用AbstractApplicationContext#refresh的方法,实际上就是SpringIOC容器的创建过程,并且会进行自动装配,在没有使用外部Tomcat项目中,还会在这里创建内置Tomcat WebServer 并启动
    		refreshContext(context);
            // 在上下文刷新后调用,定义一个空的模板方法,给其他子类实现重写
    		afterRefresh(context, applicationArguments);
            // StopWatch停止计时,打印springboot启动花费的时间
    		stopWatch.stop();
    		if (this.logStartupInfo) {
    			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    		}
            // 发布应用上下文启动完成时间,触发所有Listener监听器的start方法
    		listeners.started(context);
            // 执行所有的Runner运行器
    		callRunners(context, applicationArguments);
    	}
    	catch (Throwable ex) {
    		handleRunFailure(context, ex, exceptionReporters, listeners);
    		throw new IllegalStateException(ex);
    	}
    	try {
            // 发布应用上下文就绪事件,触发监听器的running方法
    		listeners.running(context);
    	}
    	catch (Throwable ex) {
    		handleRunFailure(context, ex, exceptionReporters, null);
    		throw new IllegalStateException(ex);
    	}
    	return context;
    }
    

    prepareContext这个方法前面也是在给应用程序的创建做准备工作(环境变量,初始化参数,发布监听事件,创建bean工厂);

    // Load the sources;sources可以是xml文件,也可以是配置类。在这里是使用的配置类(SpringbootDemoApplication)
    Set<Object> sources = getAllSources();
    // 加载各种bean对象到当前应用程序上下文,即 将SpringbootdemoApplication加载到ApplicationContext中
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
    

    如图所示,在load方法执行前后的beanDefinitionMap的对比,load方法执行后,将SpringbootdemoApplication加载到了bean工厂中了。

    3.自动装配

    到这里,SpringBoot已经将SpringbootdemoApplication这个启动类加载进bean容器中了。但是SpringBoot是如何引入项目需要的starter呢?

    接着前面run方法中的refreshContext(context);这个方法,一路往下找,会发现调用了ConfigurableApplicationContext.refresh()方法,而这个方法是一个模板方法(如下图),因为我们这里并不是使用的SERVLET和嵌入式的方式运行程序,所以调用的是第一个类中的方法,即 AbstractApplicationContext.refresh();在这个方法中,invokeBeanFactoryPostProcessors(beanFactory);这个方法就会执行自动装配的逻辑。

    顺着下面的过程:

    1.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); AbstractApplicationContext#746行

    2.invokeBeanDefinitionRegistryPostProcessors(); PostProcessorRegistrationDelegate#112行

    3.parser.parse(candidates); ConfigurationClassPostProcessor#331行

    4.因为主类是使用注解的方式注册的bean,所有会走ConfigurationClassParser的第175行的parse方法。

    5.processImports(configClass, sourceClass, getImports(sourceClass), filter, true); ConfigurationClassParser#311

    ​ 在这个方法中,通过getImports方法,其内部的 collectImports(sourceClass, imports, visited) 通过递归的方式收集所有声明的@Import导入的类。在这里有一下两个结果值:

    ​ 1. AutoConfigurationPackage注解导入的AutoConfigurationPackages.Registrar.class

    ​ 2. EnableAutoConfiguration注解导入的AutoConfigurationImportSelector.class

    ​ 这样就找到了我们常说的AutoConfigurationImportSelector这个类了。

    6.第5步解析configration完之后,回到ConfigurationClassParser#193行

    ​ this.deferredImportSelectorHandler.process();在这个方法里面实现自动导入的逻辑,点击往下,会找到下面的这一行代码,ConfigurationClassParser#809行,通过getImports()获取需要自动装配的类。

    7.在第6步的getImports()方法中,点击往下,会找到AutoConfigurationImportSelector#433行。

    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
    

    进入到getAutoConfigurationEntry()方法中找到123行的 getCandidateConfigurations():

    这个方法里面的 loadFactoryNames()方法,能发现它读取META-INF/spring.factories下的@EnableAutoConfiguration的配置类,这个方法在run方法之中多次调用,根据传参不一样,从META-INF/spring.factories中获取的内容也不一样。

    从文件中得到了133个配置类,紧接着在进行去重,排除与过滤(并不是所有的都需要)之后,就得到了需要装配的类,我这里最后还剩26个。

    到这里就将需要装配的类都已经识别到了。

    参考文章:

    https://blog.csdn.net/j080624/article/details/80764031

    本文版权归Charon和博客园共有,原创文章,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    Centos7 tomcat 启动权限
    Tomcat下post请求大小设置
    postgres安装时locale的选择
    flink 1.11.2 学习笔记(1)-wordCount
    prometheus学习笔记(3)-使用exporter监控mysql
    prometheus学习笔记(2)-利用java client写入数据
    mock测试及jacoco覆盖率
    shading-jdbc 4.1.1 + tk.mybatis + pagehelper 1.3.x +spring boot 2.x 使用注意事项
    prometheus学习笔记(1)-mac单机版环境搭建
    redis数据类型HyperLogLog的使用
  • 原文地址:https://www.cnblogs.com/pluto-charon/p/14730592.html
Copyright © 2011-2022 走看看