您现在的位置是:主页 > news > 网站视觉设计/搜索网站的软件

网站视觉设计/搜索网站的软件

admin2025/6/3 20:35:12news

简介网站视觉设计,搜索网站的软件,做网站卖流量,最新的疫情动态第一篇:Spring Boot自动配置原理浅析 第二篇:Spring Boot构造流程浅析 第三篇:Spring Boot运行流程浅析 前面我们对SpringApplication对象的构造流程进行了浅析,SpringApplication对象构造完成后会调用其run方法进行Spring Boot的…

网站视觉设计,搜索网站的软件,做网站卖流量,最新的疫情动态第一篇: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方法的核心操作流程:

  1. 获取SpringApplicationRunListener监听器、启动监听器
  2. 创建ApplicationArguments对象、初始化ConfigurableEnvironment
  3. 忽略信息配置
  4. 打印Banner
  5. 创建容器
  6. 准备容器
  7. 刷新容器
  8. 调用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);}

可以看到虽然这个方法整体定义为准备上下文的过程,但分析其方法内部逻辑又可以细分为两个部分:应用上下文的准备和加载。我们将准备容器的核心流程整理如下:

准备容器应用上下文的准备
  1. 设置上下文的环境ConfigurableEnvironment
  2. 应用上下文后置处理
  3. ApplicationContextInitializer初始化context
应用上下文的加载
  1. 打印日志、启动Profile
  2. 通过context拿到ConfigurableListableBeanFactory并注册单例对象
  3. 判断并设置BeanFactory单例对象是否允许覆盖
  4. 获取全部配置源
  5. 将sources中的bean全部加载到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运行流程浅析