Spring Boot启动流程简介

每个spring boot项目都有一个启动类,如下所示:

@SpringBootApplication
public class AppApplication {

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

从上面可以看出来,项目的启动主要与@SpringBootApplication注解和SpingAppliction.run方法有关,项目启动的主要流程在run方法中,先说一下@SpringBootApplication注解,之后再对启动流程做个简单介绍。

一、@SpringBootApplication

先点进去看一下它的代码,如下

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.annotation.AliasFor;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
	String[] excludeName() default {};

	/**
	 * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
	 * for a type-safe alternative to String-based package names.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	/**
	 * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
	 * scan for annotated components. The package of each class specified will be scanned.
	 * <p>
	 * Consider creating a special no-op marker class or interface in each package that
	 * serves no purpose other than being referenced by this attribute.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

}

可以看到,@SpringBootApplication注解主要由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个注解组成,如果我们不使用@SpringBootApplication,直接将这三个注解放在启动类之上,项目也可以直接运行。

1、@SpringBootConfiguration注解大家可以点进去看一下,它里面只有一个@Configuration注解,并没有什么特别,和@Configuration注解的功能是一样的,仅仅是表明这是一个配置类,服务启动时可以将其加载进上下文。其实@Configuration也仅是一个特殊的@Component,可以看下这个,我就不写了@Component和@Configuration作为配置类的差别_component和configuration的区别-CSDN博客

2、@EnableAutoConfiguration表示开启自动加载配置,它加载的是哪里的配置呢?可以点进去看一下它的代码,它的实现主要就是在@Import(EnableAutoConfigurationImportSelector.class)上。

@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}




//EnableAutoConfigurationImportSelector类

public class EnableAutoConfigurationImportSelector
		extends AutoConfigurationImportSelector {

	@Override
	protected boolean isEnabled(AnnotationMetadata metadata) {
		if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
			return getEnvironment().getProperty(
					EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
					true);
		}
		return true;
	}

}

@Import注解用于@Configuration配置类之上,可以导入bean、registrar、selector的实现类如@Import(xxx.class)、@Import(xxxxConfiguration.class)、@Import(xxxxselector.class),在@EnableAutoConfiguration中导入的是一个selector,就是用它来进行自动加载特定的配置。EnableAutoConfigurationImportSelector.class点进去可以看到它继承了AutoConfigurationImportSelector.class类,父类中有一个selectImports方法,它返回的就是由springboot自动加载的配置的完整类名,代码如下

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		try {
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
					.loadMetadata(this.beanClassLoader);
			AnnotationAttributes attributes = getAttributes(annotationMetadata);
			List<String> configurations = getCandidateConfigurations(annotationMetadata,
					attributes);//调用的getCandidateConfigurations获取需加载配置的完整类名
			configurations = removeDuplicates(configurations);
			configurations = sort(configurations, autoConfigurationMetadata);
			Set<String> exclusions = getExclusions(annotationMetadata, attributes);
			checkExcludedClasses(configurations, exclusions);
			configurations.removeAll(exclusions);
			configurations = filter(configurations, autoConfigurationMetadata);
			fireAutoConfigurationImportEvents(configurations, exclusions);
			return configurations.toArray(new String[configurations.size()]);
		}
		catch (IOException ex) {
			throw new IllegalStateException(ex);
		}
	}


protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

在selectImports方法中调用了getCandidateConfigurations方法,可以看到其中实际是利用了SpringFactoriesLoader中的loadFactoryNames方法,继续跟进去,其主要有loadFactoryNames和loadFactories两个方法,前者是获取需要加载的完整类名列表,后者是将其实例化并返回。那么是从何处获取这些类名呢?可以看到loadFactoryNames有两个参数一个是factoryClass,即需要加载的类在sping.factories文件中所对应的key,另一个是classloader,即它的类加载器。这里传入的factoryclass正是EnableAutoConfiguration.class。loadFactoryNames方法会扫描整个项目的所有META-INF/Sping.factories文件,大家可以打开看一下,(ps:如果自己项目中没有用到,可以搜一搜看下sping-boot-autoconfig包下的)可以发现它是一个key-value的形式,key可以是接口、注解、抽象类的全名称,value是key实现类,当有多个时用“,”分割。我们可以看到其中一个key正是org.springframework.boot.autoconfigure.EnableAutoConfiguration,所以上面selectImports方法所获取的list正是它所对应的value。还记得我们前面说过的@Import注解吗,它可以获取所有EnableAutoConfiguration对应的实现类的完整类名,在后面run方法的refreshContext过程的invokeBeanFactoryPostProcessors中正是通过它获取完整类名,反射拿到对应的beanDefinition放入beanFactory的beanDefinitionMap之中。spring.factories文件实例如下,截了一部分:

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration

3、@ComponentScan 开启自动扫描,默认情况下是扫描本目录及其子目录下的类,将符合条件的类的BeanDefinition放入上下文的beanFactory中

二、Spring-Boot启动流程

默认的启动方法只有SpringApplication.run(xxxConfiguration.class,args);这一句,我们可以点进去run方法看一下。它会先创建一个SpringApplication对象,然后再调用该对象的run方法,那么下面我会分两步,简单的介绍一下启动的流程。

1.创建SpringApplication对象

在创建SpringApplication对象时,它主要做了以下工作

(1)判断应用是否是web项目 ,通过ClassUtils.isPresent(String className, ClassLoader classLoader)方法,通过类加载器能否加载指定的类,去判断的:

servlet:(javax.servlet.Servlet, org.springframework.web.context.ConfigurableWebApplicationContext),基于servlet的web服务,并且会启动内置web服务器 tomcat,同步阻塞

reactive:org.springframework.web.reactive.DispatcherHandler,基于reactive的web服务,启动netty,异步非阻塞(spring-cloud-gateway就是)

servlet和reactive的区别:Spring Security 介绍中的 servlet 和 reactive - 知乎

(2)利用SpringFactoriesLoader中的loaderFactoryName方法获取Sping.factories文件中ApplicationContextInitializer.class application上下文初始化器及ApplicationListener.class application监听器对应的类名,然后通过BeanUtils.instantiateClass获取其实例对象(应该是通过反射),并将其赋值给SpringApplication对象中对应属性。在这一步是启动过程中第一次使用SpringFactoriesLoader的loadFacoryNames方法,此时会将所有key-value数据以LinkedMuliValueMap<String,List>的形式进行存储,然后以key为classloader存储在缓存中,之后就可以根据class取它对应的List。

(3)通过堆栈信息获取启动main方法所在类,并通过反射获取其对应的class ,并赋值到mainApplicationClass

this.webEnvironment = deduceWebEnvironment(); //判断项目的web类型
//利用SpringFactoryLoader.loadFactoryName和反射获取对应的ApplicationContextInitializer和ApplicationListener实例list并赋值给SpringApplication对象的属性
setInitializers((Collection) getSpringFactoriesInstances( 
				ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass(); //获取main方法所在类

至此SpringApplication对象创建完毕接下来就是调用它的run方法

2、run方法执行流程

(1)getRunListeners获取SpringApplicationRunListener所对应的实例,并发布start事件,大家还记得再new SpringApplication对象的时候我们已经使用过SpringFactoriesLoader的loadFactoryName方法了,那么所有META-INFO/spring.factory下的key value的对应关系均已记入内存,那么在getRunListeners再次使用的时候,是根据key SpringApplicationRunListener获取其对应的类名,然后再实例化为具体对象的。

不知道大家有没有疑惑,在new SpringApplication时,已经获取了ApplicationListener的对象list并赋值给了SpringApplication的一个属性,那么它与run方法中的SpringApplicationRunListener对象的list有什么区别呢?大家可以点进去这个listeners发布的start事件,可以发现SpringApplicationRunListener是一个接口,它的实现类EventPublishingRunListener在构造方法中获取了SpringApplication对象及其存储applicationListener的对象list的属性将其添加进了SimpleApplicationEventMulticaster initialMulticaster应用事件多播对象,其实发布start事件,调用的是initialMulticaster的mulitCastEvent多播事件方法,在该方法会根据事件类型从ApplicationListener接口实现类对象的list获取监听该事件的listener,然后调用他们的onApplicationEvenet方法进行事件发送,这就是一个典型的观察者模式。

(2)prepareEnvironment在该方法中会根据前面获取的应用web类型的不同使用不同的ConfigurableEnvironment接口实现类创建运行环境对象,并获取配置参数进行初始化。最后会发布environmentPrepared运行环境已准备事件

(3)createApplicationContext,在该方法中会根据应用web类型(就是在创建SpringApplication对象时获取的web type)的不同以不同的实现类创建ConfigurableApplicationContext对象。由于大部分项目都是web类型的,(spring-boot 2.x.x) applicationContext一般都是AnnotationConfigServletWebServerApplicationContext,此外在也会生成beanFactory实例,beanFactory默认为DefaultListableBeanFactory,以及AnnotatedBeanDefinitionReader reader和ClassPathBeanDefinitionScanner scanner对象,在这一步之后一些spring默认的processor(处理器)的beanDefinition加载进beanFactory,如对@Autowired注解和@Configuration注解相关的处理器

(4)prepareContext,在prepareEnviement中已经获取了的应用运行环境,在这里会将环境信息应用于context上下文;发布contextPrepare事件。在创建SpringApplication对象时,也获取了一些用于上下文初始化ApplicationContextInitializer接口的实现类,在这里会调用它们的initialize方法将其用于context初始化。之后会调用getAllSource()获取创建SpingApplication对象时传入的source参数,实际就是使用@SpingBootApplication注解修饰的启动类,调用load方法加载source,在该方法中会获取BeanDefinitionLoader这样一个bean定义加载器对象->loader,使用loader.load(),最后通过一个AnnotatedBeanDefinitionReader把启动类的BeanDefinition加载到应用上下文context的beanFactory的beanDefinitionMap中。BeanDefinitionLoader对象中主要是有3个对象,AnnotatedBeanDefinitionReader  annotatedReader、 ClassPathBeanDefinitionScanner scanner、XmlBeanDefinitionReader xmlReader,annotatedReader用于加载class类型的beanDefinition,scanner用于加载包路径上的beanDefinition,xmlReader用于加载source类型即配置类的beanDefinition。最后会发布contextLoaded事件。在load这一步仅会将启动类configuration的beanDefinition加载进beanFactory之中,大家可以看到它是从sources中中加载的,点进去getAllSources看一下,可以发现它里面是获取SpringApplication对象的primarySources属性,这个属性就是new SpringApplication方法传进去的主configuration

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		//省略了
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
	}

(5)refreshContext(这里讲得不好)

prepareRefresh()

beanFactory = obtainFreshBeanFactory()

parepareBeanFactory(beanFactory)

postProcessBeanFactory(beanFactory)

invokeBeanFactoryPostProcessors(beanFactory)

registerBeanPostProcessors(beanFactry)

initApplicationEventMulticaster

onRefresh

registerListeners

finishBeanFactoryInitialization(beanFactory)

finishRefresh()

在这个里面做的事比较多,

(a)在prepareRefresh中会设置刷新时间、标志位以及一些属性,简单来说就是对刷新做一些准备工作;

(b)调用obtainFreshBeanFactory获取上下文中的beanFactory,后续beanDefinition和object都是通过它存储的;

(c)prepareBeanFactory、postProcessBeanFactory两者都是对beanFactory中的一些属性进行设置

(d)invokeBeanFactoryPostProcessors,它是刷新过程中很重要的一步,在这一步会执行它会执行BeanFactoryPostProcessor的实现类,在这里需要重点指出的是有一个ConfigurationClassPostProcessor它实现了BeanDefinitionRegistryPostProcessor接口,而BeanDefinitionRegistryPostProcessor接口继承了BeanFactoryPostProcessor,因此ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPosrProcessor接口中的postProcessBeanDefinitionRegistry()方法,也实现了BeanFactoryPostProcessor中的postProcessBeanFactory方法,并且postProcessBeanDefinitionRegistry方法在postProcessBeanFactory之前执行,那么它的作用是做什么的呢?刚才在prepareContext阶段仅仅是把启动类的beanDefinition放入beanFactory的beanDefinitionMap中,那么在postProcessorBeanDefinitionRegistry方法中,会获取当前beanDefinitionMap中的@Configuration标识的beanDefinition,然后看它有没有@ComponentScan和@Import、@Bean注解,因为一开始configuration类型的beanDefinition只有我们的主类,且其中的@SpringBootApplication包含了ComponentScan注解和EnableAutoConfiguration注解,因此它会自动扫描包将其中使用@Component注解标记的类的beanDefinition放入beanDefinitionMap里去,也会装载配置类中@bean标注beanDefinition,同时@EnableAutoConfiguration注解中包含了@import注解(此时就和我们上面讲的串上了,会使用SpringFactoriesLoader.loadFactoryNames方法获取需要自动加载的完整类名通过反射将beanDifinition放入map中),并且也会加载这些自动配置类中使用@bean标识的方法,直到所有的bean定义被加载入beanFactory中。ConfigurationClassPostProcessor的postProcessBeanFactory会在postProcessBeanDefinitionRegistry方法之后执行,它会对我们的configuration bean进行enhancer代理增强,如果在某configuration中有两个使用@bean注解修饰方法,getA()和getB()返回A、B对象,且在getB()中使用了getA()那么,在getB中getA方法获取的a对象和外部的getA方法返回的a对象是同一个。它会保证每一个@bean注解生成的bean都是单例的,当在出现上面说的在一个getB中调用了getA,会首先去容器搜索@Bean修饰的这个对象是否在容器中存在,如果存在就直接返回容器中的对象,不存在就会调用@Bean对应的方法逻辑构建一个bean对象放入容器中并返回这个对象。以上这种机制是通过cglib代理中的拦截器实现的.

(e)registerBeanPostProcessors,注册beanPostProcessor接口实现类,用于在创建bean的过程中调用

(f)initApplicationEventMulticaster 初始化应用事件多播器,用于发布事件

(g)onRefresh,springBoot内置了webServer容器如tomcat,在这一步就会创建webserver对象并启动

(h)registerListeners向事件多播器注册监听器

(i)finishBeanFactoryInitialization对所有的非懒加载单例的beanDefinition进行初始化成为真正的bean,并存入beanFactory的一级缓存singletonObjects之中

(j)finishRefresh做一些刷新的收尾工作

(6)afterRefresh

这里流程是比较简单的,如果我们自己实现了ApplicationRun或CommanLineRun接口的话,在这里会调用实现类的run方法。我现在项目里面flyway模块继承ApplicationRun使用到了afterRefresh,它会在该步骤执行

@Deprecated
@Component
public class FlywayRunner implements ApplicationRunner {

  @Autowired
  private DataSource dataSource;

  @Override
  public void run(ApplicationArguments args) throws Exception {
    Flyway flyway = new Flyway();
    flyway.setDataSource(dataSource);
    flyway.setLocations("sql");
    flyway.setBaselineOnMigrate(true);
    flyway.setOutOfOrder(true);
    flyway.migrate();
  }
}

(7)最后的最后,发布finish事件

建议大家去看一下源码,我去,好复杂啊,有时间,我会把整个流程再完善一下!!!

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		FailureAnalyzers analyzers = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			analyzers = new FailureAnalyzers(context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			listeners.finished(context, null);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			return context;
		}
		catch (Throwable ex) {
			handleRunFailure(context, listeners, analyzers, ex);
			throw new IllegalStateException(ex);
		}
	}

下面说一下BeanFactoryPostProcessor中方法postProcessBeanFactory和BeanPostProcessor中的postProcessBeforeInitialization和postProcessAfterInitialization方法的执行顺序,我们知道beanDefinition生成bean的过程分三个步骤,根据构造方法初始化实例、填充内部属性/依赖、执行初始化方法(即若实现了InitializingBean接口,重写其中的afterPropertiesSet方法,或在xml <bean init-method>指出了初始化方法,afterPropertiesSet在init-method指定的方法之前执行)

1、BeanFactoryPostProcessor中方法postProcessBeanFactory是在beanFactory初始化完成,在refreshContext中调用的,在上述方法中最先执行

2、BeanPostProcessor中的postProcessBeforeInitialization是在bean填充属性完成,初始化之前执行

3.  postProcessAfterInitialization在初始化之后执行

@postConstruct也是依赖BeanPostProcessor的postProcessAfterInitialization方法实现的

static方法块在postProcessBeanFactory之后,构造方法之前执行,也很好理解,当创建bean时,刚加载了类还没有执行构造方法就已经执行了static方法块

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值