461°

SpringBoot启动流程--吐血整理

当被问道springboot启动流程时,我依稀记得看过但是忘了,其实是没真正理解。

SpringBoot启动 分为两大部分

1、启动类上注解:@SpringBootApplication,构造SpringApplication

2、调用SpringApplication 对象的 run() 方法:

SpringApplication.run(Application.class, args)


一、新建SpringApplication对象

启动类中的main方法就一句

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

但是重要的是 @SpringBootApplication 注解,点进这个注解

@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 {
...........
....
}

三个注解核心注解:@SpringBootConfiguration@ComponentScan@EnableAutoConfiguration

@SpringBootConfiguration

根据Javadoc可知,该注解作用就是将当前的类作为一个JavaConfig,然后触发注解@EnableAutoConfiguration和@ComponentScan的处理,本质上与@Configuration注解没有区别

@ComponentScan

扫描的 Spring 对应的组件,如 @Componet,@Repository。

我们可以通过 basePackages 等属性来细粒度的定制 @ComponentScan 自动扫描的范围,如果不指定,则默认Spring框架实现会从声明 @ComponentScan 所在类的package进行扫描,所以 SpringBoot 的启动类最好是放在根package下,我们自定义的类就放在对应的子package下,这样就可以不指定 basePackages

@EnableAutoConfiguration 重点

@EnableAutoConfiguration点进去上面又有这两个注解比较重要 @AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)

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

一旦加上此注解,那么将会开启自动装配功能,简单点讲,Spring会试图在你的classpath下找到所有配置的Bean然后进行装配。当然装配Bean时,会根据若干个(Conditional)定制规则来进行初始化

① 注册当前启动类的根 package

② 注册 org.springframework.boot.autoconfigure.AutoConfigurationPackages 的 BeanDefinition。

@Import(AutoConfigurationImportSelector.class)

① 实现了 DeferredImportSelector 接口,该接口继承自 ImportSelector,根据 Javadoc 可知,多用于导入被 @Conditional 注解的Bean,之后会进行 filter 操作

② AutoConfigurationImportSelector中的内部类AutoConfigurationGroupprocess 方法,SpringBoot 启动时会调用该方法,进行自动装配的处理,见 - 自动装配

AutoConfigurationImportSelector类中的selectImport方法,会先判断是否进行自动装配,而后会从META-INF/spring-autoconfigure-metadata.properties读取元数据与元数据的相关属性,紧接着会调用getCandidateConfigurations方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if(!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            try {
                AutoConfigurationMetadata ex = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
                AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
                  // 获取预先定义的应考虑的自动配置类名称
                List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
                configurations = this.removeDuplicates(configurations);
                configurations = this.sort(configurations, ex);
                Set exclusions = this.getExclusions(annotationMetadata, attributes);
                this.checkExcludedClasses(configurations, exclusions);
                configurations.removeAll(exclusions);
                 // 通过filter过滤掉当前环境不需要自动装配的类,比如没有集成RabbitMQ,或者不满足@Conditional也不需要自动装配
                configurations = this.filter(configurations, ex);
                this.fireAutoConfigurationImportEvents(configurations, exclusions);
                // 返回需要自动装配的全路径类名
                return (String[])configurations.toArray(new String[configurations.size()]);
            } catch (IOException var6) {
                throw new IllegalStateException(var6);
            }
        }
    }

④ getCandidateConfigurations方法中 调用了 SpringFactoryiesLoader 类中的 loadFactoryNames方法,它会读取META-INF/spring.factories下的EnableAutoConfiguration的配置,紧接着在进行排除与过滤,进而得到需要装配的类

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
    }

⑤ 最后让所有配置在META-INF/spring.factories下的AutoConfigurationImportListener执行AutoConfigurationImportEvent事件


private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
        List listeners = this.getAutoConfigurationImportListeners();
        if(!listeners.isEmpty()) {
            AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
            Iterator var5 = listeners.iterator();

            while(var5.hasNext()) {
                AutoConfigurationImportListener listener = (AutoConfigurationImportListener)var5.next();
                this.invokeAwareMethods(listener);
                listener.onAutoConfigurationImportEvent(event);
            }
        }

    }

 1)自动装配还是利用了SpringFactoriesLoader来加载META-INF/spring.factoires文件里所有配置的EnableAutoConfgruation,它会经过exclude和filter等操作,最终确定要装配的类

  2) 处理@Configuration的核心还是ConfigurationClassPostProcessor,这个类实现了BeanFactoryPostProcessor, 因此当AbstractApplicationContext执行refresh方法里的invokeBeanFactoryPostProcessors(beanFactory)方法时会执行自动装配

二、SpringApplication 执行 run 方法

实际执行的是 SpringApplication中同名的重载的run方法


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

1、首先会实例化SpringApplication一个对象 2、在构造方法里初始化一些属性,比如webApplicationType,比如"SERVLET",初始化一些listeners

	public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
		this.resourceLoader = resourceLoader;
		initialize(sources);
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void initialize(Object[] sources) {
		if (sources != null && sources.length > 0) {
			this.sources.addAll(Arrays.asList(sources));
		}
        // 初始化webApplicationType,比如"SERVLET"
		this.webEnvironment = deduceWebEnvironment();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
         // 初始化一些listeners
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}
	

最终执行的run方法


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);
            // 最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作
     		// 以及发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			listeners.finished(context, null);
            // stopWatch停止计时,日志打印总共启动的时间
			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);
		}
	}
    
    
    public ConfigurableApplicationContext run(String... args) {
    	//创建一个StopWatch实例,用来记录SpringBoot的启动时间
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        ArrayList exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        //通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
       
        listeners.starting();

        Collection exceptionReporters1;
        try {
            DefaultApplicationArguments ex = new DefaultApplicationArguments(args);
             // 创建和配置environment,发布事件:SpringApplicationRunListeners#environmentPrepared
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, ex);
            this.configureIgnoreBeanInfo(environment);
             // 打印SpringBoot的banner和版本
            Banner printedBanner = this.printBanner(environment);
             // 创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)
            context = this.createApplicationContext();
            exceptionReporters1 = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, new Object[]{context});
            // 准备ApplicationContext,Initializers设置到ApplicationContext后发布事件:ApplicationContextInitializedEvent
            // 打印启动日志,打印profile信息(如dev, test, prod)
            // 调用EventPublishingRunListener发布ApplicationContext加载完毕事件:ApplicationPreparedEvent
            this.prepareContext(context, environment, listeners, ex, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, ex);
            stopWatch.stop();
            if(this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
 			// 发布SprintBoot启动事件:ApplicationStartingEvent
            listeners.started(context);
            this.callRunners(context, ex);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
        	// 最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters1, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

经典的观察者模式,只要你把事件广播的顺序理解了,那整个流程就很容易串起来了:

1、创建一个StopWatch实例,用来记录SpringBoot的启动时间

2、通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener

3、发布SprintBoot开始启动事件(EventPublishingRunListener#starting())

4、创建和配置environment(environmentPrepared())

5、打印SpringBoot的banner和版本

6、创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)

7、repareContext

(1)准备ApplicationContext,Initializers设置到ApplicationContext(contextPrepared())

(2)打印启动日志,打印profile信息(如dev, test, prod)

(3)最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作,以及发布			ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成(contextLoaded())

8、afterRefresh hook方法

9、stopWatch停止计时,日志打印总共启动的时间

10、发布SpringBoot程序已启动事件(started())

11、调用ApplicationRunner和CommandLineRunner

12、最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了(running())

最后

SpringApplicationRunListeners的唯一实现是EventPublishingRunListener;

整个SpringBoot的启动,流程就是各种事件的发布,调用EventPublishingRunListener中的方法。

只要明白了EventPublishingRunListener中事件发布的流程,也就明白了SpringBoot启动的大体流程

本文只是粗略理解,后续会出一半详细流程图


已有 0 条评论

    我有话说: