欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Boot启动原理解析(执行流程篇)

程序员文章站 2022-05-19 15:56:27
...

SpringApplication.run

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



	public static ConfigurableApplicationContext run(Class<?> primarySource,
			String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}



	public static ConfigurableApplicationContext run(Class<?>[] primarySources,
			String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

    可以看到SpringApplication.run启动有两部分组成:

  • new SpringApplication(primarySources):实例化一个 SpringApplication 对象
  • run(args):执行

实例化SpringApplication对象

	public static ConfigurableApplicationContext run(Class<?>[] primarySources,
			String[] args) {
		return new SpringApplication(primarySources).run(args);
	}	



    public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}



	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        //1、将资源初始化加载器设置为null
		this.resourceLoader = resourceLoader;
        //2、断言资源加载类不能为 null,否则报错
		Assert.notNull(primarySources, "PrimarySources must not be null");
        // 3、初始化加载资源类集合并去重
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 4、推断当前 WEB 应用类型,WebApplicationType(三种)
		this.webApplicationType = deduceWebApplicationType();
        // 5、设置应用上下文初始化器
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
        // 6、设置监听器 
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 7、推断主应用类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

    实例化SpringApplication类主要有7个步骤:

  1. 将资源初始化加载器设置为null
  2. 断言资源加载类不能为 null,否则报错
  3. 初始化加载资源类集合并去重
  4. 推断当前 WEB 应用类型,WebApplicationType(三种)
  5. 设置应用上下文初始化器
  6. 设置监听器 
  7. 推断主应用类

1~4问题不大,下面主要看看5、6、7:  

设置应用上下文初始化器和监听器

	public void setInitializers(
			Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>();
		this.initializers.addAll(initializers);
	}


	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}


	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
        //获取当前线程上下文类加载器
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		//获取 ApplicationContextInitializer 的实例名称集合并去重,其实就是获取spring.factories中的配置类列表
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //根据返回的实例名称集合进行实例的创建并返回列表
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
        //实例列表排序
		AnnotationAwareOrderComparator.sort(instances);
        //实例列表返回
		return instances;
	}


    public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext>{
    
    	/**
    	 * Initialize the given application context.
    	 * @param applicationContext the application to configure
    	 */
	    void initialize(C applicationContext);

    }

可以看到设置初始化器实例列表有五个步骤:

  1. 获取当前线程上下文类加载器
  2. 获取 ApplicationContextInitializer 的实例名称集合并去重,其实就是获取spring.factories中的配置类列表(在注解篇已经研究过)
  3. 根据返回的实例名称集合进行实例的创建并返回列表
  4. 实例列表排序
  5. 实例列表返回

 Spring Boot启动原理解析(执行流程篇)

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

设置监听器的步骤与设置上下文初始化器的流程基本相同。

	public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
		this.listeners = new ArrayList<>();
		this.listeners.addAll(listeners);
	}


	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}


	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}


    @FunctionalInterface
    public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    	/**
    	 * Handle an application event.
    	 * @param event the event to respond to
    	 */
    	void onApplicationEvent(E event);

    }

    这个接口继承了 JDK 的EventListener 接口,实现了观察者模式,它会为实现了ApplicationListener的类做事件的监听。

org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

推断主应用类

	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;
	}

Spring Boot启动原理解析(执行流程篇)

    它通过获构建一个运行时的异常,然后遍历整个栈,通过匹配一个"main"方法来获取入口类的信息。

run 方法运行

public ConfigurableApplicationContext run(String... args) {
    // 1、创建并启动计时监控类
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    
    // 2、初始化应用上下文和异常报告集合
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    
    // 3、设置系统属性 `java.awt.headless` 的值,默认值为:true
    configureHeadlessProperty();
    
    // 4、创建所有 Spring 运行监听器并发布应用启动事件
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    
    try {
        // 5、初始化默认应用参数类
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
                
        // 6、根据运行监听器和应用参数来准备 Spring 环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        
        // 7、创建 Banner 打印类
        Banner printedBanner = printBanner(environment);
        
        // 8、创建应用上下文
        context = createApplicationContext();
        
        // 9、准备异常报告器
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
                
        // 10、准备应用上下文
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
                
        // 11、刷新应用上下文
        refreshContext(context);
        
        // 12、应用上下文刷新后置处理
        afterRefresh(context, applicationArguments);
        
        // 13、停止计时监控类
        stopWatch.stop();
        
        // 14、输出日志记录执行主类名、时间信息
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        
        // 15、发布应用上下文启动完成事件
        listeners.started(context);
        
        // 16、执行所有 Runner 运行器
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 17、发布应用上下文就绪事件
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    
    // 18、返回应用上下文
    return context;
}

    在创建完SpringApplication对象后会执行run方法,run方法可以由这几个部分组成:

  1. 创建并启动计时监控类
  2. 初始化应用上下文和异常报告集合
  3. 设置系统属性 ‘java.awt.headless’ 的值,默认值为:true
  4. 创建所有 Spring 运行监听器并发布应用启动事件
  5. 初始化默认应用参数类
  6. 根据运行监听器和应用参数来准备 Spring 环境
  7. 创建 Banner 打印类
  8. 创建应用上下文
  9. 准备异常报告器
  10. 准备应用上下文
  11. 刷新应用上下文
  12. 应用上下文刷新后置处理
  13. 停止计时监控类
  14. 输出日志记录执行主类名、时间信息
  15. 发布应用上下文启动完成事件
  16. 执行所有 Runner 运行器
  17. 发布应用上下文就绪事件
  18. 返回应用上下文

创建并启动计时监控类 

	StopWatch stopWatch = new StopWatch();
	stopWatch.start();


        public void start() throws IllegalStateException {
		start("");
	}


	public void start(String taskName) throws IllegalStateException {
		if (this.currentTaskName != null) {
			throw new IllegalStateException("Can't start StopWatch: it's already running");
		}
		this.currentTaskName = taskName;
		this.startTimeMillis = System.currentTimeMillis();
	}

    可以看到它会传入一个空字符串给当前任务作名称,然后记录当前Spring Boot应用启动的开始时间。并且它会判断当前任务名是否存在,保证Spring Boot应用不重复启动。

初始化应用上下文和异常报告集合

		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

设置系统属性

		configureHeadlessProperty();


	private void configureHeadlessProperty() {
		System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
				SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
	}


	private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

创建所有 Spring 运行监听器并发布应用启动事件

		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();


	private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
	}


	SpringApplicationRunListeners(Log log,
			Collection<? extends SpringApplicationRunListener> listeners) {
		this.log = log;
		this.listeners = new ArrayList<>(listeners);
	}


	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
        //获取当前线程上下文类加载器
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		//获取 ApplicationContextInitializer 的实例名称集合并去重,其实就是获取spring.factories中的配置类列表
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //根据返回的实例名称集合进行实例的创建并返回列表
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
        //实例列表排序
		AnnotationAwareOrderComparator.sort(instances);
        //实例列表返回
		return instances;
	}

    这块的内容和之前实例化初始化器的流程是一样的,通过getSpringFactoriesInstances方法从spring.factories中获取与SpringApplicationRunListener.class相关的实例类名列表。

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

Spring Boot启动原理解析(执行流程篇)

	public void starting() {
                //遍历并启动
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.starting();
		}
	}

初始化默认应用参数类

			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);

根据运行监听器和应用参数来准备 Spring 环境

			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);

    先看看prepareEnvironment方法:

	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// 获取(或者创建)应用环境
		ConfigurableEnvironment environment = getOrCreateEnvironment();
                //配置应用环境
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		listeners.environmentPrepared(environment);
                //绑定到SpringApplication
		bindToSpringApplication(environment);
		if (this.webApplicationType == WebApplicationType.NONE) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

        //根据创建SpringApplication中的类型创建环境类型
	private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		if (this.webApplicationType == WebApplicationType.SERVLET) {
			return new StandardServletEnvironment();
		}
		return new StandardEnvironment();
	}


        //配置应用环境
	protected void configureEnvironment(ConfigurableEnvironment environment,
			String[] args) {
		configurePropertySources(environment, args);//配置 property sources
		configureProfiles(environment, args);//配置 pofiles
	}

    这一步主要做创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource和Profile)。

创建 Banner 打印类

Banner printedBanner = printBanner(environment);


	private Banner printBanner(ConfigurableEnvironment environment) {
		if (this.bannerMode == Banner.Mode.OFF) {
			return null;
		}
		ResourceLoader resourceLoader = (this.resourceLoader != null ? this.resourceLoader
				: new DefaultResourceLoader(getClassLoader()));
		SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
				resourceLoader, this.banner);
		if (this.bannerMode == Mode.LOG) {
			return bannerPrinter.print(environment, this.mainApplicationClass, logger);
		}
		return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
	}

    这块就是创建一个Banner类没什么好说的。

创建应用上下文

    context = createApplicationContext();



	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

   在这一步中它会根据webApplicationType的类型去反射创建ConfigurableApplicationContext的具体实例。

Spring Boot启动原理解析(执行流程篇)

准备异常报告器

			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

    这一步的逻辑和实例化初始化器和监听器的一样,都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。

org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

准备应用上下文

			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);


	private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
                //设置环境到上下文
		context.setEnvironment(environment);

                //设置上下文的 bean 生成器和资源加载器
		postProcessApplicationContext(context);

                //将任何applicationContextInitialalizer应用于上下文
		applyInitializers(context);

                //触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
		listeners.contextPrepared(context);

                //记录启动日志
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}

		// 添加特定于引导的单例bean
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
		if (printedBanner != null) {
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		}

		// 加载所有资源
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));

                //触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
		listeners.contextLoaded(context);
	}

    这块会对整个上下文进行一个预处理,比如触发监听器的相应事件、加载资源、设置上下文环境等等。

刷新应用上下文

    refreshContext(context);


	private void refreshContext(ConfigurableApplicationContext context) {
		refresh(context);
		if (this.registerShutdownHook) {
			try {
                //向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文,
                //除非它当时已经关闭。
                //这个方法可以调用多次。每个上下文实例只注册一个关闭钩子(最大)。
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}


	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}

Spring Boot启动原理解析(执行流程篇)

    这块主要做了两件事:

  1. 通过refresh方法对整个IoC容器的初始化(包括Bean资源的定位、解析、注册等等)
  2. 通过context.registerShutdownHook()(向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文,除非它当时已经关闭。)

 应用上下文刷新后置处理

afterRefresh(context, applicationArguments);


	protected void afterRefresh(ConfigurableApplicationContext context,
			ApplicationArguments args) {
	}

    这块方法是空的,可以做一些自定义的后置处理操作。

停止计时监控类

    stopWatch.stop();



	public void stop() throws IllegalStateException {
		if (this.currentTaskName == null) {
			throw new IllegalStateException("Can't stop StopWatch: it's not running");
		}
		long lastTime = System.currentTimeMillis() - this.startTimeMillis;
		this.totalTimeMillis += lastTime;
		this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
		if (this.keepTaskList) {
			this.taskList.add(this.lastTaskInfo);
		}
		++this.taskCount;
		this.currentTaskName = null;
	}

    这块方法主要会做一个计时监听器停止操作,并统计一些任务执行信息。

输出日志记录执行主类名、时间信息

			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}


	protected Log getApplicationLog() {
		if (this.mainApplicationClass == null) {
			return logger;
		}
		return LogFactory.getLog(this.mainApplicationClass);
	}

    它会打印主类信息和时间信息等等……

发布应用上下文启动完成事件

    listeners.started(context);


	public void started(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.started(context);
		}
	}

    这块会执行所有SpringApplicationRunListener的实现的started方法。

执行所有 Runner 运行器

    callRunners(context, applicationArguments);


	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());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

    执行所有 ApplicationRunner 和 CommandLineRunner 这两种运行器。

发布应用上下文就绪事件

    listeners.running(context);


	public void running(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.running(context);
		}
	}

    这块会执行所有SpringApplicationRunListener的实现的running方法。

返回应用上下文

	return context;

总结

     SpirngBoot通过将对特定的场景事先进行了固化,然后通过对自动配置类的反射加载到IoC容器中,最后使得整个开发变得更加方便简介。

 

参考:

https://www.cnblogs.com/shamo89/p/8184960.html

https://www.jianshu.com/p/714f7b054041