您现在的位置是:主页 > news > 网站视觉设计/搜索网站的软件
网站视觉设计/搜索网站的软件
admin2025/6/3 20:35:12【news】
简介网站视觉设计,搜索网站的软件,做网站卖流量,最新的疫情动态第一篇:Spring Boot自动配置原理浅析 第二篇:Spring Boot构造流程浅析 第三篇:Spring Boot运行流程浅析 前面我们对SpringApplication对象的构造流程进行了浅析,SpringApplication对象构造完成后会调用其run方法进行Spring Boot的…
第一篇:Spring Boot自动配置原理浅析
第二篇:Spring Boot构造流程浅析
第三篇:Spring Boot运行流程浅析
前面我们对SpringApplication对象的构造流程进行了浅析,SpringApplication对象构造完成后会调用其run方法进行Spring Boot的启动和运行,本文开始重点分析Spring Boot是如何通过run方法完成整个项目的启动和运行的。
目录
run方法总览
一、获取SpringApplicationRunListener监听器、启动监听器
二、创建ApplicationArguments对象、初始化ConfigurableEnvironment
三、忽略信息配置
四、打印Banner
五、创建容器
六、准备容器
七、刷新容器
七、调用ApplicationRunner和CommandLineRunner
run方法总览
首先回顾一下本文要剖析的run方法是在哪里出现的(并非启动类中的run方法):在Spring Boot构造流程浅析中我们跟进启动类中的run方法后发现SpringApplication.run(xxx.class)主要进行SpringApplication类的实例化操作,然后这个实例化对象再去调用了另外一个更牛逼的run方法来完成整个项目的初始化和启动,而这个更牛逼的run方法就是本文的主角。
public static ConfigurableApplicationContext run(Class<?>[] primarySources,String[] args) {return new SpringApplication(primarySources).run(args);}
进入这个run方法,源码及注释如下。
public ConfigurableApplicationContext run(String... args) {//用于统计run方法启动时长StopWatch stopWatch = new StopWatch();//启动统计stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();configureHeadlessProperty();//获取SpringApplicationRunListener监听器SpringApplicationRunListeners listeners = getRunListeners(args);//启动监听器listeners.starting();try {//创建ApplicationArguments对象ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//初始化ConfigurableEnvironmentConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);//忽略信息配置configureIgnoreBeanInfo(environment);//打印Banner Banner printedBanner = printBanner(environment);//创建容器context = createApplicationContext();//异常报告器exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);//准备容器prepareContext(context, environment, listeners, applicationArguments,printedBanner);//刷新化容器refreshContext(context);//初始化之后执行afterRefresh(context, applicationArguments);//停止时长统计stopWatch.stop();//打印启动日志if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}//通知监听器:容器启动完成listeners.started(context);//调用ApplicationRunner和CommandLineRunner的运行方法callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {//通知监听器:容器正在运行listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}return context;}
忽略调一些非核心操作现在我们来总结一下run方法的核心操作流程:
- 获取SpringApplicationRunListener监听器、启动监听器
- 创建ApplicationArguments对象、初始化ConfigurableEnvironment
- 忽略信息配置
- 打印Banner
- 创建容器
- 准备容器
- 刷新容器
- 调用ApplicationRunner和CommandLineRunner
下面将逐步浅析上述流程。
一、获取SpringApplicationRunListener监听器、启动监听器
现在开始对run方法的核心流程一步一步的分析,首先是通过getRunListeners方法获取到SpringApplicationRunListeners,可以把SpringApplicationRunListeners理解为一个容器,它里面存放了很多SpringApplicationRunListener相关的监听器,下面是该方法源码:
private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));}
发现getRunListeners也只是调用了SpringApplicationRunListeners的构造方法而已,注意看该构造方法的第二个参数是去调用了getSpringFactoriesInstances方法,这个方法的主要作用是去获取META-INF/spring.factories中对应的监听器配置,并进行实例化操作,源码如下:
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//加载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;}
可以看到这里依然是通过SpringFactoriesLoader.loadFactoryNames来加载spring.factories中的监听器配置信息,该方法在之前Spring Boot自动配置原理浅析和Spring Boot构造流程浅析已经多次提到,这里就不再重复说明了。最后是通过createSpringFactoriesInstances方法来实例化刚获取到的监听器,再将这些实例经过排序后返回,返回的结果是一个SpringApplicationRunListener集合。
现在我们进入SpringApplicationRunListeners的构造方法来看一下:
SpringApplicationRunListeners(Log log,Collection<? extends SpringApplicationRunListener> listeners) {this.log = log;this.listeners = new ArrayList<>(listeners);}
发现原来刚才获取到的集合被传入SpringApplicationRunListeners的构造方法后,只是简单的赋值给了listeners成员变量而已。在获取到监听器后,将会调用listeners.starting()来启动监听器。
在构造方法下面还能看到很多关于listeners成员变量的各种遍历操作方法: starting、environmentPrepared、contextPrepared、contextLoaded、started、running、failed。在run方法执行期间将会调用这些方法,通过下面的图来了解下这些方法具体将会在什么时候被调用。
可以结合图片和后面的源码内容做对比,加深理解。
二、创建ApplicationArguments对象、初始化ConfigurableEnvironment
在初始化ConfigurableEnvironment之前还需要先初始化ApplicationArguments,我们只需要知道ApplicationArguments的作用是提供访问运行SpringApplication的参数几即可。接着便是初始化ConfigurableEnvironment。ConfigurableEnvironment的作用是对“环境”服务的,是提供当前运行环境的接口。
run方法中初始化ConfigurableEnvironment的相关代码如下,可以看到传入的参数是之前获取到的监听器以及ApplicationArguments。
//初始化ConfigurableEnvironmentConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
进入prepareEnvironment方法,源码及注释如下:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {//获取或创建环境ConfigurableEnvironment environment = getOrCreateEnvironment();//配制环境configureEnvironment(environment, applicationArguments.getSourceArgs());//将ConfigurationPropertySources附加到指定环境的第一位ConfigurationPropertySources.attach(environment);listeners.environmentPrepared(environment);//将环境绑定到SpringApplicationbindToSpringApplication(environment);//判断是否是定制的环境,如果不是,则将将环境转换为StandardEnvironmentif (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());}//将ConfigurationPropertySources附加到指定环境的第一位ConfigurationPropertySources.attach(environment);return environment;}
大致流程:首先通过getOrCreateEnvironment方法获取或创建环境;然后配置环境;接着将ConfigurationPropertySources附加到指定环境的第一位,动态跟踪环境的添加和删除;调用listeners的environmentPrepared方法通知环境准备(对比之前的图片理解);将环境绑定到SpringApplication;判断是否是定制的环境,如果不是,则将将环境转换为标准环境;最后一步也是将ConfigurationPropertySources附加到指定环境的第一位,动态跟踪环境的添加和删除。
三、忽略信息配置
在初始化ConfigurableEnvironment以后,还需要通过configureIgnoreBeanInfo方法进行忽略信息配置,该方法源码如下:
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {//若系统中spring.beainfo.ignore的值为nullif (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {//获取环境中spring.beainfo.ignore配置的值Boolean ignore = environment.getProperty("spring.beaninfo.ignore",Boolean.class, Boolean.TRUE);//将系统中spring.beainfo.ignore的值设为环境中的值 System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,ignore.toString());}}
可以明白这一过程大概就是:如果系统中“spring.beaninfo.ignore”的值为null,则把它设置为环境中“spring.beaninfo.ignore”配置的值,spring.beaninfo.ignore的作用是用来决定是否跳过BeanInfo类的扫描。
四、打印Banner
打印Banner其实没什么可说的,我们启动SpringBoot项目时能看到如下图的打印信息。这主要是通过printBanner方法来实现的,由于这个流程并不是很重要,我们甚至可以关闭Banner的打印,因此这个步骤就不再此过多说明。
五、创建容器
接着是创建容器ConfigurableAoolicationContext,即Spring应用上下文的创建,这个时候如果没有指定要创建的类,则会根据之前推断出来的类型进行上下文类的创建。
还记得在Spring Boot构造流程浅析中进行的web应用类型推断吗?回顾代码片段:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {……//推断Web应用类型this.webApplicationType = WebApplicationType.deduceFromClasspath();……}
现在就要根据这个被推断出来的应用类型进行上下文类的创建,即容器的创建。来看一下创建容器的方法:createApplicationContext()
protected ConfigurableApplicationContext createApplicationContext() {//首先获取容器的变量Class<?> contextClass = this.applicationContextClass;//如果容器的变量还为null,则根据web应用类型创建容器if (contextClass == null) {try {switch (this.webApplicationType) {case SERVLET:contextClass = Class.forName(DEFAULT_SERVLET_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);}}//最后通过BeanUtils进行实例化return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);}
用一句话总结就是先获取容器的变量,如果此时容器的变量还为null,那么就根据web应用类型去创建容器,最终通过BeanUtils进行实例化后返回。
六、准备容器
在创建完容器之后,接下来就是通过prepareContext方法做准备容器的工作,即Spring应用上下文的准备。来看一下prepareContext方法的源码及注释:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {//设置环境context.setEnvironment(environment);//应用上下文后置处理postProcessApplicationContext(context);//初始化contextapplyInitializers(context);//通知监听器context准备完成listeners.contextPrepared(context);//打印日志、启动Profileif (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}//通过context拿到ConfigurableListableBeanFactory 并注册单例对象ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {//注册打印日志对象beanFactory.registerSingleton("springBootBanner", printedBanner);}if (beanFactory instanceof DefaultListableBeanFactory) {//设置是否允许覆盖注册((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}//获取全部配置源Set<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");//将sources中的bean加载到context中load(context, sources.toArray(new Object[0]));//通知监听器context加载完成listeners.contextLoaded(context);}
可以看到虽然这个方法整体定义为准备上下文的过程,但分析其方法内部逻辑又可以细分为两个部分:应用上下文的准备和加载。我们将准备容器的核心流程整理如下:
准备容器 | 应用上下文的准备 |
|
应用上下文的加载 |
|
七、刷新容器
容器准备工作做完之后接下来是刷新容器,即Spring应用上下文的刷新。
private void refreshContext(ConfigurableApplicationContext context) {refresh(context);if (this.registerShutdownHook) {try {context.registerShutdownHook();}catch (AccessControlException ex) {// Not allowed in some environments.}}}
protected void refresh(ApplicationContext applicationContext) {Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);((AbstractApplicationContext) applicationContext).refresh();}
Spring应用上下文的刷新是通过refreshContext方法来完成,追溯其源码可知核心方法是其内部的refresh方法,而该方法又调用了AbstractApplicationContext中的refresh方法,我们可以通过源码来了解一下这个refresh方法内部都做了些什么。
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {//准备刷新prepareRefresh();//通知子类刷新内部bean工厂ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//为context准备bean工厂prepareBeanFactory(beanFactory);try {// 允许context的子类对bean工厂进行后置处理postProcessBeanFactory(beanFactory);// 调用context中注册为bean的工厂处理器invokeBeanFactoryPostProcessors(beanFactory);// 注册bean处理器registerBeanPostProcessors(beanFactory);// 初始化context的信息源initMessageSource();// 初始化context的事件传播器initApplicationEventMulticaster();// 初始化其他子类特殊的beanonRefresh();// 注册事件监听器registerListeners();// 实例化所有非懒加载单例finishBeanFactoryInitialization(beanFactory);// 发布对应事件finishRefresh();}
七、调用ApplicationRunner和CommandLineRunner
最后,run方法会通过执行callRunners方法来调用ApplicationRunner和CommandLineRunner,目的通过他们来实现在容器启动时执行一些操作,如果有多个实现类,可以通过@Order注解或实现Order接口来控制执行顺序。来看callRunners方法源码:
private void callRunners(ApplicationContext context, ApplicationArguments args) {//准备一个list集合List<Object> runners = new ArrayList<>();//从context中获取ApplicationRunner和CommandLineRunner的bean,放入集合runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());//排序AnnotationAwareOrderComparator.sort(runners);//遍历集合将ApplicationArguments 参数传入for (Object runner : new LinkedHashSet<>(runners)) {if (runner instanceof ApplicationRunner) {callRunner((ApplicationRunner) runner, args);}if (runner instanceof CommandLineRunner) {callRunner((CommandLineRunner) runner, args);}}}
大致流程是通过context拿到ApplicationRunner和CommandLineRunner的bean,放入集合然后排序,然后遍历集合将ApplicationArguments 参数传入执行callRunner方法。
至此,Spring Boot运行流程执行结束。
第一篇:Spring Boot自动配置原理浅析
第二篇:Spring Boot构造流程浅析
第三篇:Spring Boot运行流程浅析