SpringBoot源码解析-自动配置原理

从SpringBoot主程序类入手,我们以debug的方式进行源码的解析。

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

启动流程

首先进入SpringApplication.class中,可以看到 run() 方法(注意与后面的run()方法区分)的作用是创建SpringApplication对象,之后调用对象的run()方法运行程序。

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return (new SpringApplication(sources)).run(args);
}
  • (new SpringApplication(sources)):a.创建SpringApplication对象。
  • run(args):b.运行run方法。

a.创建SpringApplication对象。

之后来看看对象是如何被创建出来的,这里主要关注 initialize() 这个初始化方法。

public SpringApplication(Object... sources) {
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    this.addCommandLineProperties = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.initialize(sources);
}

在 initialize() 方法中主要分以下几步

private void initialize(Object[] sources) {
      //1.保存多个配置类
      if (sources != null && sources.length > 0){
      	this.sources.addAll(Arrays.asList(sources));
      }
      
      //2.判断是否为Web环境应用
      this.webEnvironment = this.deduceWebEnvironment();
      
      //3.扫描并保存所有ApplicationContextInitializer
      this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));

      //4.扫描并保存所有ApplicationListener
      this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));

	  //5.查找主配置类
      this.mainApplicationClass = this.deduceMainApplicationClass();
 }

以下部分是对上面步骤的详解:

  1. 判断是否为Web环境应用:在 this.deduceWebEnvironment() 方法中,观察WEB_ENVIRONMENT_CLASSES 的定义
    ,它是通过判断当前环境是否存在 Servlet 和 ConfigurableWebApplicationContext,从而判断出当前的运行环境。
private static final String[] WEB_ENVIRONMENT_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};

    private boolean deduceWebEnvironment() {
        String[] var1 = WEB_ENVIRONMENT_CLASSES;
		...
    }
  1. 扫描并保存所有ApplicationContextInitializer :getSpringFactoriesInstances() 方法最终调用的是 SpringFactoriesLoader 类中 loadFactoryNames() 方法,可以看到它会扫描类路径下的所有 META-INF/ 目录下的 spring.factories 文件。
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
       String factoryClassName = factoryClass.getName();

       try {
           Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
           ArrayList result = new ArrayList();
           ...
  • 打开任意的autoconfigure包,观察其META-INF目录下的spring.factories 文件,在文件中可以看到声明的ApplicationContextInitializer的配置类路径。
#Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
...
  • this.setInitializers() 方法执行完成后,可以查看 initializers 的值,图中1就是我们上面代码所示的context.ApplicationContextInitializer。SpringBoot源码解析-自动配置原理_第1张图片
  1. 扫描并保存所有ApplicationListener:与步骤3过程类似,不过扫描和保存的是ApplicationListener,这里就不赘述了。
  2. 查找主配置类:从多个配置类中找到含有main方法的主配置类,之所以是从多个配置类中寻找是因为传入的是一个Object数组,这意味着可以运行多个配置类。同时在 deduceMainApplicationClass() 方法中可以看到递归判断,判断传入的所有类中哪个类存在main方法,从而获得主配置类。
   private Class<?> deduceMainApplicationClass() {
       try {
   		   ...
           for(int var4 = 0; var4 < var3; ++var4) {
               StackTraceElement stackTraceElement = var2[var4];
               if ("main".equals(stackTraceElement.getMethodName())) {
                   return Class.forName(stackTraceElement.getClassName());
               }
           }
       } catch (ClassNotFoundException var6) {
       }
       return null;
   }

b.运行run方法

SpringApplication对象创建完成后,我们来看看它是怎么被运行起来的。

public ConfigurableApplicationContext run(String... args) {
   ...
   //1. 扫描并保存所有SpringApplicationRunListeners
   SpringApplicationRunListeners listeners = this.getRunListeners(args);
   
   //回调所有的SpringApplicationRunListeners的starting()方法
   listeners.starting();

   try {
    	//封装命令行参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
   
   		//准备环境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        
        //打印Spring图标
        Banner printedBanner = this.printBanner(environment);
   	
   		//3.创建ApplicationContext(IOC容器)
        context = this.createApplicationContext();
       
        new FailureAnalyzers(context);
        //4.准备上下文环境
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        
        //5.刷新容器(扫描,创建,加载所有组件的地方:配置类,组件,自动配置)
        this.refreshContext(context);
        
        //6.完成容器刷新
        this.afterRefresh(context, applicationArguments);
   	
   	    //所有的pringApplicationRunListeners回调finished()方法
        listeners.finished(context, (Throwable)null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }
   
   		//整个SpringBoot应用启动完成,并返回启动的IOC容器
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
        throw new IllegalStateException(var9);
    }
   }

以下部分是对上面步骤的详解:

  1. 扫描并保存所有SpringApplicationRunListeners:这与创建SpringApplication对象时的步骤3与4过程类似,这里就不赘述了。
  2. 准备环境: 在 this.prepareEnvironment() 方法中主要关注 environmentPrepared() 方法,可以看到在创建环境完成后,会回调步骤1得到的所有SpringApplicationRunListeners的environmentPrepared()方法,完成环境的准备。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
		...
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
  1. 创建ApplicationContext(IOC容器):判断需要创建Web环境的IOC容器还是普通的IOC容器,之后利用BeanUtils工具的反射方法,完成IOC容器的创建。
protected ConfigurableApplicationContext createApplicationContext() {
	...
	contextClass = Class.forName(this.webEnvironment ? "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext" : "org.springframework.context.annotation.AnnotationConfigApplicationContext");
	...
    return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass);
}
  1. 准备上下文环境:回调之前创建SpringApplication对象时保存的所有 ApplicationContextInitializer 的 initialize() 方法,并回调运行run方法时创建SpringApplicationRunListeners的contextPrepared()方法,完成上下文环境的准备。
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	...
	this.applyInitializers(context);
	listeners.contextPrepared(context);
	...
  1. 刷新容器:这里主要关注refresh() 方法内的finishBeanFactoryInitialization(beanFactory)方法,主要是ioc容器初始化的过程,加载IOC容器中所有组件,扫描所有的配置类,单实例Bean等等,如果是Web应用,还会创建嵌入式Tomcat。
public void refresh() throws BeansException, IllegalStateException {
	...
	// Instantiate all remaining (non-lazy-init) singletons.
	finishBeanFactoryInitialization(beanFactory);

	// Last step: publish corresponding event.
	finishRefresh();
}
  1. 完成容器刷新:进入 afterRefresh() 方法,可以看到它调用了 callRunners 方法,目的是从IOC容器中获取所有 ApplicationRunner 和 CommandLineRunne r进行回调,注意ApplicationRunner先回调,CommandLineRunner后回调。
 private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		...

事件监听机制

配置在META-INF/spring.factories中

  • ApplicationContextInitializer
  • SpringApplicationRunListener

只需要放在IOC容器中

  • ApplicationRunner
  • CommandLineRunner

监听过程解析

  1. 编写一个HelloApplicationContextInitializer类,实现ApplicationContextInitializer接口。
public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("ApplicationContextInitializer...initialize"+applicationContext);
    }
}

可以看到它是一个泛型接口,SpringBoot已经提供了许多初始化器(如下图),这里我们监听IOC容器的初始化器,观察ApplicationContextInitializer运行过程。

SpringBoot源码解析-自动配置原理_第2张图片
  1. 编写一个HelloSpringApplicationRunListener类,实现SpringApplicationRunListener接口。
public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
    
    public HelloSpringApplicationRunListener(SpringApplication application, String[] args) {

    }

    @Override
    public void starting() {
        System.out.println("SpringApplicationRunListener...starting");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment configurableEnvironment) {
        Object o = configurableEnvironment.getSystemProperties().get("os.name");
        System.out.println("SpringApplicationRunListener...environmentPrepared"+o);	//测试准备环境,获取系统版本
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println("SpringApplicationRunListener...contextPrepared");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext configurableApplicationContext) {
        System.out.println("SpringApplicationRunListener...contextLoaded");
    }

    @Override
    public void finished(ConfigurableApplicationContext configurableApplicationContext, Throwable throwable) {
        System.out.println("SpringApplicationRunListener...finished");
    }
}

观察b.运行run方法时,SpringApplicationRunListener的各个方法的执行过程。

  1. 编写HelloApplicationRunner类,实现ApplicationRunner接口。
@Component
public class HelloApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        System.out.println(" ApplicationRunner...run");
    }
}
  1. 编写HelloCommandLineRunner类,实现CommandLineRunner接口。
@Component
public class HelloCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... strings) throws Exception {
        System.out.println("CommandLineRunner...run..."+ Arrays.asList(strings));
    }
}
  1. 将HelloApplicationContextInitializer和HelloSpringApplicationRunListener 类路径添加至spring.factories配置文件中。
org.springframework.context.ApplicationContextInitializer=\
com.atguigu.springboot.listener.HelloApplicationContextInitializer

org.springframework.boot.SpringApplicationRunListener=\
com.atguigu.springboot.listener.HelloSpringApplicationRunListener
  1. 将HelloApplicationRunner和HelloCommandLineRunner方法加上@Component注解,添加到容器中。
  2. 观察控制台输出的日志,即可看到SpringBoot事件监听的过程。
SpringApplicationRunListener...starting	<--
SpringApplicationRunListener...environmentPreparedWindows 10	<--

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v1.5.20.RELEASE)

ApplicationContextInitializer...initializeorg.springframework.context.annotation.AnnotationConfigApplicationContext@6c9f5c0d: startup date [Thu Jan 01 08:00:00 CST 1970]; root of context hierarchy
SpringApplicationRunListener...contextPrepared	<--
2019-05-02 23:50:36.471  INFO 2464 --- [           main] c.a.springboot.Springboot07Application   : Starting Springboot07Application on DESKTOP-IPI86R4 with PID 2464 (C:\Other\CodeWorkplace\java\SpringBoot-Examples\springboot-07\target\classes started by Kellen-PC in C:\Other\CodeWorkplace\java\SpringBoot-Examples\springboot-07)
2019-05-02 23:50:36.473  INFO 2464 --- [           main] c.a.springboot.Springboot07Application   : No active profile set, falling back to default profiles: default
SpringApplicationRunListener...contextLoaded	<--
2019-05-02 23:50:36.517  INFO 2464 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6c9f5c0d: startup date [Thu May 02 23:50:36 CST 2019]; root of context hierarchy
2019-05-02 23:50:36.961  INFO 2464 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
 ApplicationRunner...run	<--
CommandLineRunner...run...[]	<--
SpringApplicationRunListener...finished	<--
2019-05-02 23:50:36.969  INFO 2464 --- [           main] c.a.springboot.Springboot07Application   : Started Springboot07Application in 0.706 seconds (JVM running for 1.663)
2019-05-02 23:50:36.970  INFO 2464 --- [       Thread-3] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6c9f5c0d: startup date [Thu May 02 23:50:36 CST 2019]; root of context hierarchy
2019-05-02 23:50:36.971  INFO 2464 --- [       Thread-3] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

你可能感兴趣的:(SpringBoot)