Spring Boot项目的启动流程

工作也有两年之余了,做过的Spring Boot项目也蛮多的,但是对他的启动流程之前没有系统的学习以及记录过,而且之前面试也遇到过问Spring Boot的启动流程。那么今天我们就整体的分析一下,Spring Boot的启动流程吧。


Spring Boot优点

在开始之前我们先介绍一下Spring Boot
在这里插入图片描述
用我的英语四级水平给大家翻译一下。
这个是Spring Boot官网对它自己的描述

Spring Boot可以很容易地创建独立的、基于Spring的生产级应用程序,您只需“运行”即可。
我们对Spring平台和第三方库持有固执(我怎么感觉是个贬义词?)的看法,所以您可以毫不费力地开始。大多数Spring引导应用程序只需要最少的Spring配置。

这个是Spring Boot官网对它特性的介绍。

特性

  • 创建独立的Spring应用程序
  • 直接嵌入Tomcat、Jetty或Undertow(不需要部署WAR文件)
  • 提供自以为是的“启动器”依赖项来简化构建配置
  • 尽可能地自动配置Spring和第三方库
  • 提供可用于生产的特性,如度量、运行状况检查和外部化配置
  • 完全不需要生成代码,也不需要XML配置

Spring Boot启动过程

我们的Spring Boot项目一般都会有一个启动类(如下),这就是整个应用的入口,只要运行这个main函数就能启动Spring Boot项目。

@SpringBootApplication
public class TrainingCenterPlatformApplication {

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

}

接下来我们就对这个类来做一些研究吧!

@SpringBootApplication 注解

通过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:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    	...
    }
    

    我们可以看到@SpringBootConfiguration 是来源于 @Configuration。所以他的功能与 @Configuration一样,都是将当前类标记为配置类,并且将当前类里以@Bean注解标记的方法返回的实例注入到Spring容器中,实例的名字即方法的名字(也可以在@Bean指定实例的名字)。

  • @EnableAutoConfiguration:

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

    @EnableAutoConfiguration 注解启用自动配置,其可以帮助 Spring Boot 应用将所有符合条件的 @Configuration 配置都加载到当前 IoC 容器之中,可以简要用图形示意如下
    EnableAutoConfiguration
    我们可以用文字描述一下这个过程:首先Spring Boot会扫描ClassPath下所有的 META-INF/spring.factories 配置文件,然后将spring.factories文件中的 EnableAutoConfiguration对应的配置项通过反射机制实例化为对应标注了 @Configuration 的形式的IoC配置类,然后注入到IoC容器。

  • @ComponentScan:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Repeatable(ComponentScans.class)
    public @interface ComponentScan {
    	...
    }
    

    它对应着我们之前基于XML配置的<context:component-scan>,用于将一些标注了特定注解的bean定义批量采集注册到Spring的IoC容器之中,这些特定的注解包括@Component以及继承了它的子注解。

SpringApplication类

SpringApplication里面封装了一套Spring应用的启动流程,然后这对用户完全透明,因此我们上手Spring Boot时感觉会很简单。

SpringApplication实例的初始化
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// 推断应用的类型:创建的是REACTIVE应用、SERVLET应用、NONE 三种中的某一种
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
		//使用SpringFactoriesLoader查找并加载classpath下META-INF/spring.factories文件中所有可用
		//的 ApplicationContextInitializer
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		//使用SpringFactoriesLoader查找并加载classpath下META-INF/spring.factories文件中的所有可用
		//的 ApplicationListener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		//推断并设置main方法的定义类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

我们可以看一下这里面几个比较重要的方法:

  • WebApplicationType.deduceFromClasspath():

    	static WebApplicationType deduceFromClasspath() {
    		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) 
    				&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
    			return WebApplicationType.REACTIVE;
    		}
    		for (String className : SERVLET_INDICATOR_CLASSES) {
    			if (!ClassUtils.isPresent(className, null)) {
    				return WebApplicationType.NONE;
    			}
    		}
    		return WebApplicationType.SERVLET;
    	}
    

    这个方法是用来判断应用创建的类型,如果我们用的是Spring MVC框架的话这个方法会返回WebApplicationType.SERVLET,如果我们用的是Spring webFlux框架而且项目里没有引入Spring MVC的框架时返回的是WebApplicationType.REACTIVE。

  • getSpringFactoriesInstances(ApplicationContextInitializer.class)、getSpringFactoriesInstances(ApplicationListener.class)):

    	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, 
    			Object... args) {
    		ClassLoader classLoader = getClassLoader();
    		// 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;
    	}
    

    这两个方法其实最后调用的都是这个方法,目的就是为了去获取其接口类类对应的配置类列表。
    ApplicationContextInitializer的作用是在Spring容器刷新之前执行的一个回调函数。通常用于需要对应用程序上下文进行编程初始化的web应用程序中。例如,根据上下文环境注册属性源或激活配置文件等。
    ApplicationListener是一个接口,里面只有一个onApplicationEvent方法。如果在上下文中部署一个实现了ApplicationListener接口的bean,那么每当在一个ApplicationEvent发布到 ApplicationContext时,调用ApplicationContext.publishEvent()方法,这个bean得到通知。类似于Oberver设计模式。

  • deduceMainApplicationClass():
    推断并设置main方法的定义类。

    	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;
    	}
    
SpringApplication的run()方法

talk is cheap, show you the code~

	/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		//对程序部分代码进行计时
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		//获取并创建SpringApplicationRunListener 这里用到了观察者模式,将Spring Boot的启动状态实时通知给观察者
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//通知starting,构建了一个ApplicationStartingEvent事件,并将其发布出去
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			//创建参数	
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			//配置Environment
			ConfigurableEnvironment environment = 
				prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			//打印banner
			Banner printedBanner = printBanner(environment);
			//根据webApplicationType创建ApplicationContext
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			//设置Environment,加载相关配置
			prepareContext(bootstrapContext, context, environment, listeners, 
				applicationArguments, printedBanner);
			//这个是最重要的一步,会调用Spring的refresh方法(ioc)
			refreshContext(context);
			//空方法
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			//打印程序启动耗时
			//Started TrainingCenterPlatformApplication in 6.776 seconds (JVM running for 23.098)
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			//通知started
			listeners.started(context);
			//完成程序的最终启动,执行ApplicationRunner和CommandLineRunner实现类的run方法
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			//SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

好了,我觉的最重点的还是refresh方法,下一篇文章就这个方法做一个源码解析吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值