1.简介
Spring Boot是由Pivotal团队提供的快速开发框架,基于SpringMVC通过注解+内置Http服务器如:tomcat-embed-core,简化了XML配置,快速将一些常用的第三方依赖整合(通过Maven继承依赖关系),最终实现以Java应用程序的方式进行执行。
1.1 SpringBoot起源
- Spring框架:Spring框架从早期的IOC与AOP衍生出了很多产品例如Spring (boot、security、jpa)等等
- SpringMVC框架:Spring MVC提供了一种轻度耦合的方式来开发web应用,它是Spring的一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等,是基于Spring的一个 MVC 框架。
- SpringBoot框架:Spring Boot实现了自动配置,降低了项目搭建的复杂度。它主要是为了解决使用Spring框架需要进行大量的配置太麻烦的问题,所以它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),是基于Spring4的条件注册的一套快速开发整合包。
1.2 便捷的starter poms (启动器)
starter包含了搭建项目快速运行所需的依赖。它是一个依赖关系描述符的集合。当应用需要一种spring的其它服务时,不需要粘贴拷贝大量的依赖关系描述符。例如想在spring中使用redis,只需要在项目中包含 spring-boot-starter-redis 依赖就可以使用了,所有的starters遵循一个相似的命名模式:spring-boot-starter-,在这里是一种特殊类型的应用程序。该命名结构可以帮你找到需要的starter。很多IDEs集成的Maven允许你通过名称搜索依赖。
1.3 demo
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
这是一个简单的SpringBoot的启动类实现,下文章将围绕这个demo进行扩展分析。
1.4 SpringBoot启动流程
2 @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 {
可以发现它是由众多注解组合而成的,下面具体分析下这里每个注解所起到的作用。
- @Target Target通过ElementType来指定注解可使用范围的枚举集合(FIELD/METHOD/PARAMETER...)
-
@Retention Retention(保留)注解说明,这种类型的注解会被保留到那个阶段. 有三个值:
- RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
- RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
- RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使
- @Documented 注解表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中
- @Inherited 允许子类继承父类的注解,仅限于类注解有用,对于方法和属性无效。
- @SpringBootConfiguration 注解实际上和@Configuration有相同的作用,配备了该注解的类就能够以JavaConfig的方式完成一些配置,可以不再使用XML配置。
- @ComponentScan 这个注解完成的是自动扫描的功能,相当于Spring XML配置文件中的:
<context:component-scan>
,可使用basePackages属性指定要扫描的包,及扫描的条件。如果不设置则默认扫描@ComponentScan注解所在类的同级类和同级目录下的所有类,所以我们的Spring Boot项目,一般会把入口类放在顶层目录中,这样就能够保证源码目录下的所有类都能够被扫描到。 -
@EnableAutoConfiguration 这个注解是让Spring Boot的配置能够如此简化的关键性注解。我把EnableAutoConfiguration的实现端上来了,大家来鉴赏一下!
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {
- @AutoConfigurationPackage 注解用于保存自动配置类以供之后的使用,比如给JPA entity扫描器,用来扫描开发人员通过注解@Entity定义的entity类。通俗的讲就是,注册bean定义到容器中。
- @Import(AutoConfigurationImportSelector.class)是EnableAutoConfiguration注解中最关键的来,它借助AutoConfigurationImportSelector,可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器中。关于@Import注解要说的内容还比较多,改天再聊。
关于注解的话题就先谈到这里,下面开启撸代码环节。
3 剖析代码
查看SpringApplication的源代码可以发现SpringApplication的启动由两部分组成:
- new SpringApplication(primarySources):创建SpringApplication对象
- run(args):调用run方法
3.1 实例化SpringApplication对象
源码如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;//1、初始化资源加载器
Assert.notNull(primarySources, "PrimarySources must not be null");//2、断言资源加载类不能为 null
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//3、初始化加载资源类集合并去重
this.webApplicationType = deduceWebApplicationType();//4、 推断应用类型是Standard还是Web
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));//5、设置应用上下文初始化器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//6、设置监听器
this.mainApplicationClass = deduceMainApplicationClass();//7、推断应用入口类
}
下面将针对源码中的重要实现进行详细的分析。
3.1.1 初始化资源加载器
ResourceLoader接口,在 Spring 中用于加载资源,通过它可以获取一个Resouce 对象。使用spring的朋友都知道它加载资源的方式由多种,下面就挑两个常用的继承ResourceLoader的接口与实现类说起。
- DefaultResourceLoader : 作为 ResourceLoader 接口的直接实现类,该类实现了基本的资源加载功能,可以实现对单个资源的加载。
- ResourcePatternResolver :该接口继承了 ResourceLoader,定义了加载多个资源的方法, 可以实现对多个资源的加载。
1、DefaultResourceLoader
上面介绍过该类通过实现 ResourceLoader 接口实现了加载单个资源的功能。它的子类通过继承它来实现具体的资源访问策略。下面来探究下该类如何加载单个资源:
public Resource getResource(String location) {//这里是三种识别location加载出Resource的方式。
Assert.notNull(location, "Location must not be null");
//1.先看有没有自定义的ProtocolResolver,如果有则先根据自定义的ProtocolResolver解析location得到Resource
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
//2.根据路径是否匹配"/"或"classpath:"来解析得到ClassPathResource
if (location.startsWith("/")) {
return getResourceByPath(location);
}else if (location.startsWith(CLASSPATH_URL_PREFIX)) {//classpath
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}else {
try {
//默认传入的location是一个URL路径,加载得到一个UrlResource
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// 如果以上三种情况都不满足,则按照“/”来处理
return getResourceByPath(location);
}
}
}
扫盲:ProtocolResolver是解析location的自定义拓展类,有了它我们才能随意传入不同格式的location,然后根据对应的格式去解析并获得我们的Resource即可。
扩展:在Spring容器初始化过程中,我们可以自定义一个类实现ProtocolResolver接口,然后实现该resolve方法,就可以解析特定的location得到Resoure。
2、ResourcePatternResolver
该接口继承了ResourceLoader接口,在其基础上增加了同时对多个资源的访问功能。
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
// 例如使用 ant 风格的路径,匹配路径下的多个资源
Resource[] getResources(String locationPattern) throws IOException;
}
PathMatchingResourcePatternResolver是 ResourcePatternResolver 接口的直接实现类,它是基于模式匹配的,默认使用AntPathMatcher 进行路径匹配,它除了支持 ResourceLoader 支持的前缀外,还额外支持 “classpath*” ,下面查看源码看看它如何实现对多个资源的访问。
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
//首先判断资源路径是否是类路径下的资源(以 “classpath*:” 开头)
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// 通过 getPathMatcher 方法取得 PathMatcher ,默认只有 AntPathMatcher 一个实现类
// 通过 isPattern 方法判断路径是否允许存在多个匹配的资源(路径中包含 “*” 或 “?”)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
//找到所有匹配路径(ant 风格)的资源
return findPathMatchingResources(locationPattern);
}
else {
//通过类加载器查找
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
// 判断资源路径 ":" 之后的部分是否包含 "*" 或 "?"
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// 若不存在表示是单个资源,则通过从构造函数传入的 ResourceLoader 取得
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
扩展:查看源码发现ApplicationContext接口也继承了 ResourcePatternResolver 接口,说明它也集成了对对单个或多个资源的访问功能。
- 当 Spring 需要进行资源访问时,实际上并不需要直接使用 Resource 实现类,而是调用 getResource 方法来获取资源。
- 当通过 ApplicationContext 实例获取 Resource 实例时,它将会负责选择具体的 Resource 的实现类。代码如下:
Resource res = ctx.getResource("some/resource/path/myTemplate.txt);
Spring采用和 ApplicationContext 相同的策略来访问资源。也就是说:
- 如果 ApplicationContext 是 FileSystemXmlApplicationContext,res 就是 FileSystemResource 实例;
- 如果 ApplicationContext 是 ClassPathXmlApplicationContext,res 就是 ClassPathResource 实例;
- 如果 ApplicationContext 是 XmlWebApplicationContext,res 是 ServletContextResource 实例。
也就是说 ApplicationContext 将会确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来,这就体现了策略模式的优势。
3.1.2 设置应用上下文初始化器setInitializers
- 介绍:initializers是SpringApplication中的一个实例属性:List<ApplicationContextInitializer<?>> initializers,每一个initailizer都是一个实现了ApplicationContextInitializer接口的实例。ApplicationContextInitializer是Spring IOC容器中提供的一个接口,源码如下:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext>{
void initialize(C applicationContext);
}
- 作用:ApplicationContextInitializer接口的作用就是在spring prepareContext的时候做一些初始化工作,在spring 初始化的过程中 执行prepareContext方法的时候里面会通过applyInitializers方法回调所有ApplicationContextInitializer接口的实现。所以在SpringApplication的构造方法中执行了setInitializers方法,该方法是把初始化的ApplicationContextInitializer实现类全部加载到SpringApplication内部的集合中。
- 实现:这些初始化器(initializers)是Spring Boot从本地的META-INF/spring.factories文件和jar文件中的META-INF/spring.factories 文件获取配置的初始化类(注意这边是获取了配置文件的全部配置类),然后通过loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}方法获得ApplicationContextInitializer接口全部实现类的完整名称,最后通过反射的机制获得ApplicationContextInitializer实现类。
- 使用:通过getSpringFactoriesInstances(
ApplicationContextInitializer.class)方法获得实现类
3.1.3 设置监听器(setListeners)
listeners成员变量,是一个ApplicationListener<?>类型对象的集合。可以看到获取该成员变量内容使用的是跟成员变量initializers一样的方法,只不过传入的类型从ApplicationContextInitializer.class变成了ApplicationListener.class。
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
这个接口基于JDK中的EventListener接口,实践了观察者模式。对于Spring框架的观察者模式实现,它限定感兴趣的事件类型需要是ApplicationEvent类型的子类,而这个类同样是继承自JDK中的EventObject类。
3.1.4 推断应用入口类
该方法通过构造一个运行时异常,通过异常栈中方法名为main的栈帧来得到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;
}
3.2 run方法
源码如下:
public ConfigurableApplicationContext run(String... args) {
//1、计时监控类
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2、初始化应用上下文和异常报告集合
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 3、设置系统属性java.awt.headless的值,默认值为:true(没有图形化界面)
configureHeadlessProperty();
// 4、创建所有 Spring 运行监听器并发出开始执行的事件
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 5、初始化默认应用参数类
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 6、根据SpringApplicationRunListeners和应用参数来准备 Spring 环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 7、准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
Banner printedBanner = printBanner(environment);
// 8、创建Spring上下文
context = createApplicationContext();
// 9、准备异常报告器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 10、Spring上下文前置处理
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 11、刷新Spring上下文
refreshContext(context);
// 12、Spring上下文后置处理
afterRefresh(context, applicationArguments);
// 13、停止计时监控类
stopWatch.stop();
// 14、输出日志记录执行主类名、时间信息
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 15、发布应用上下文启动完成事件
listeners.started(context);
// 16、执行所有 Runner 运行器
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 17、发布应用上下文就绪事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
// 18、返回应用上下文
return context;
}
下面针对run方法中提到的一些重要步骤进行阐述:
3.2.1 计时监控类
启动方法如下:
public void start() throws IllegalStateException {
start("");
}
public void start(String taskName) throws IllegalStateException {
if (this.currentTaskName != null) {
throw new IllegalStateException("Can't start StopWatch: it's already running");
}
this.currentTaskName = taskName;
this.startTimeMillis = System.currentTimeMillis();
}
你可以看到它传入了一个空字符串给当前任务作为任务名称,然后记录当前Spring Boot应用启动的开始时间。并且它会判断当前任务名是否存在,用于保证Spring Boot应用不重复启动。
3.2.2 初始化应用上下文和异常报告集合
这里仅初始化一个应用上下文对象context和一个空的异常报告集合,具体用途看下文。
3.2.3 设置系统属性
configureHeadlessProperty的方法实现如下:
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
该方法设置了一个名为java.awt.headless的系统属性,观其源码可以发现它给属性设值System.setProperty(),而它的值来源于System.getProperty(),这并不奇怪,因为System中getProperty()有2个重载方法,其中getProperty()有单参和双参两个方法,这里调用的是双参数方法,该方法在没有的时候会返回一个调用者指定的默认值,所以这里先获取后设置。这个设置的目的是保证即使没有检测到显示器(服务器不需要显示器),也允许程序启动。
3.2.4 创建所有 Spring 运行监听器并发出开始执行的事件
实现如下:
//根据args获取所有SpringApplicationRunListeners监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//这段代码大家应该已经熟悉了,获取SpringApplicationRunListeners扩展
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
通过Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};类型加载对应的监听器,并创建SpringApplicationRunListener实例
下面的内容和之前实例化初始化器的流程是一样,通过getSpringFactoriesInstances方法从spring.factories中获取与SpringApplicationRunListener.class相关的实例类名列表。
3.2.5 初始化默认应用参数类
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。
如:--server.port=9000
3.2.6 根据运行监听器和应用参数来准备 Spring 环境
创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile(其作用就是指定激活的配置文件,可以区分环境来加载不同的配置)),并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
源码如下:
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//获取或创建环境(存在就直接返回,不存在创建一个再返回)
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置环境:配置PropertySources和activeProfiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
//listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)。
listeners.environmentPrepared(environment);
//将环境绑定到SpringApplication
bindToSpringApplication(environment);
//如果非web环境,将环境转换成StandardEnvironment
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
//配置PropertySources对它自己的递归依赖
ConfigurationPropertySources.attach(environment);
return environment;
}
prepareEnvironment的作用:加载外部化配置资源到environment,包括命令行参数、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties)等;初始化日志系统。
3.2.7 准备Banner打印器
该功能仅供自娱自乐,不做过多解读,看源码便是。
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null ? this.resourceLoader
: new DefaultResourceLoader(getClassLoader()));
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
3.2.8 创建Spring上下文
源码如下:
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context.annotation.AnnotationConfigApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
// 先判断有没有指定的实现类
Class<?> contextClass = this.applicationContextClass;
// 如果没有,则根据应用类型选择
if (contextClass == null) {
try {
//根据webApplicationType的类型去反射创建ConfigurableApplicationContext的具体实例。
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_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);
}
}
// 通过反射获取对应类的实例
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
createApplicationContext()该方法的逻辑较为简单,共两个分支:
- 自定义ApplicationContext的实现类
- 根据当前应用的类型webApplicationType来匹配对应的ApplicationContext,是servlet、reactive或者非web应用
3.2.9 准备异常报告器
这一步的逻辑和实例化初始化器和监听器的一样,都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
3.2.10 Spring上下文前置处理
源码如下:
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置容器环境,包括各种变量
context.setEnvironment(environment);
//设置上下文的 bean 生成器和资源加载器
postProcessApplicationContext(context);
//执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
applyInitializers(context);
//触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
listeners.contextPrepared(context);
//记录启动日志
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 添加特定于引导的单例bean
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// 加载所有资源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载我们的启动类,将启动类注入容器
load(context, sources.toArray(new Object[0]));
//触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
listeners.contextLoaded(context);
}
这块会对整个上下文进行一个预处理,比如触发监听器的响应事件、加载资源、设置上下文环境等等。
3.2.11 刷新Spring上下文
源码如下:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
//向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
这块主要做了两件事:
- 通过refresh方法对整个IoC容器的初始化(包括Bean资源的定位、解析、注册等等)
- 通过context.registerShutdownHook()(向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文,除非它当时已经关闭。)
3.2.12 Spring上下文后置处理
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}
该方法没有实现,可以根据需要做一些定制化的操作。
3.2.13 停止计时监控类
源码如下:
public void stop() throws IllegalStateException {
if (this.currentTaskName == null) {
throw new IllegalStateException("Can't stop StopWatch: it's not running");
}
long lastTime = System.currentTimeMillis() - this.startTimeMillis;
this.totalTimeMillis += lastTime;
this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
if (this.keepTaskList) {
this.taskList.add(this.lastTaskInfo);
}
++this.taskCount;
this.currentTaskName = null;
}
该方法主要是做计时监听器停止操作,并统计一些任务执行信息。
3.2.14 输出日志记录执行主类名、时间信息
源码如下:
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
用于打印主类信息和时间信息。
3.2.15 发布应用上下文启动完成事件
源码如下:
public void started(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.started(context);
}
}
执行所有SpringApplicationRunListener实现的started方法。
3.2.16 执行所有 Runner 运行器
Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口CommandLineRunner、ApplicationRunner
对比:
-
相同点
- 两者均在服务启动完成后执行,并且只执行一次。
- 两者都能获取到应用的命令行参数。
- 两者在执行时机上是一致的(可以通过Ordered相关的接口或注解来实现自定义执行优先级。)。
-
不同点
- 虽然两者都是获取到应用的命令行参数,但是ApplicationRunner获取到的是封装后的ApplicationArguments对象,而CommandLine获取到的是ApplicationArguments中的sourceArgs属性(List<String>),即原始参数字符串列表(命令行参数列表)。
源码如下:
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
// 从Spring容器中获取ApplicationRunner实现类 runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 从Spring容器中获取CommandLineRunner实现类 runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
//排序
AnnotationAwareOrderComparator.sort(runners);
//回调
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
ApplicationRunner还是CommandLineRunner,都是在应用启动完成后执行一次业务初始化代码,达到的效果也类似,由于ApplicationRunner的方法参数是ApplicationArguments对象,使用起来更加方便,所以更推荐使用。
3.2.17 发布应用上下文就绪事件
源码如下
public void running(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.running(context);
}
}
触发所有 SpringApplicationRunListener 监听器的 running 事件方法。
总结
SpirngBoot启动流程到这里就分析完了、接下来的文章会针对SpringBoot的特定场景进行分析,希望每个看到这篇文章的朋友都能有所收获,同时也欢迎大家多提宝贵意见共同成长。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。