使用springboot也有1年了,对于Java开发者而言这确实是一个方便快捷的开发框架,但一值都没深入去了解过的,最近由于开发中出现了filter执行顺序问题导致系统报错(虽然通过设置setOrder解决了执行顺序问题),但觉得还是有必要去了解下springboot的启动初始化过程(下面是比较粗浅的过一遍启动流程,以后深入学习后会在更新 )
一、SpringApplication初始化
spring boot启动是从SpringApplication.run();方法开始的,这是SpringApplication的静态方法,那么先进入SpringApplication.class来看一下这个方法:
public static ConfigurableApplicationContext run(Class> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
这一步新建了一个SpringApplication的对象,然后再调用它的run方法,先去看下SpringApplication类的构造方法:
public SpringApplication(Class... primarySources) {
this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
},
可以看到SpringApplication的构造入参有两种类型:一个是ResourceLoader,一个是Class对象或数组,可以看出来,到了这里primarySources参数里面只有启动类一个元素。
在SpringApplication的构造方法内主要完成了以下几个事情:
1、确定Web应用的类型。
我们看到WebApplicationType.deduceFromClasspath()方法是根据判断当前的类的类型来判断web应用的类型的:
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
return REACTIVE;
} else {
String[] var0 = SERVLET_INDICATOR_CLASSES;
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
String className = var0[var2];
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return NONE;
}
}
return SERVLET;
}
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
try {
forName(className, classLoader);
return true;
} catch (IllegalAccessError var3) {
throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" + className + "]: " + var3.getMessage(), var3);
} catch (Throwable var4) {
return false;
}
}
这一块是比较容易看懂的(这个不说太细基本上),通过推断类路径这个方法,去判断你是属于哪种类型的web应用程序,基本上就是通过isPresent 方法中的forName使用反射去尝试,能否通过系统猜测的类名,去得到对应的类对象,来推测是什么类型。
2、initializers的初始化
通过set方法完成SpringApplication成员变量initializers,即一些初始化的相关操作。它是一个List
private
return this.getSpringFactoriesInstances(type, new Class[0]);
}
private
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Set
List
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
private
List
Iterator var7 = names.iterator();
while(var7.hasNext()) {
String name = (String)var7.next();
try {
Class> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
} catch (Throwable var12) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
}
}
return instances;
}
在getSpringFactoriesInstances方法里面根据当前线程获取到其类的加载器,然后调用下面的方法
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
这个方法会从META-INF/spring.factories配置文件中根"type"(就是ApplicationContextInitializer的全路径)作为key获取所有的值,通过DEBUG模式我们可以看到返回的names,见下图:
而在createSpringFactoriesInstances()方法中,通过反射的方式创建出各个ApplicationContextInitializer所有实现类的实例,然后添加到List中返回。setInitializers()方法最终完成SpringApplication类中initializers变量的注入。
public void setInitializers(Collection extends ApplicationContextInitializer>> initializers) {
this.initializers = new ArrayList();
this.initializers.addAll(initializers);
}
我个人理解这一步是把,为上下文的初始准备的类给安装到这个list参数里面,可能是后面run中的上下文创建有关(以后再看看)。
3、成员变量listeners的初始化
这一步其实和上面的setInitializers的创建过程是一样的,只是把上下文初始化实例换成了需要的监听器实例
4、获取当前应用的启动类的类对象,顺便看下方法吧,比较简单:
private Class> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
StackTraceElement[] var2 = stackTrace;
int var3 = stackTrace.length;
for(int var4 = 0; var4 < var3; ++var4) {
StackTraceElement stackTraceElement = var2[var4];
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException var6) {
;
}
return null;
}
先获取到所有的栈元素和正在执行的方法,如果是"main"方法则返回这个类的类对象(这个就不做深入了解了)
二、SpringApplication实例run方法执行
这个方法应该可以代表整个应用的启动过程了,方法里面内容很多,只说一些我认为比较重要的。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
listeners.started(context);
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
上面方法执行完返回的是一个ConfigurableApplicationContext对象,也就是一个可配置应用的上下文对象,方法返回后我们的程序也就启动完成了。(其实来我这里主要就是想看看它对各种bean类的加载顺序)
1、应用启动监听器执行监听
我们先看这行代码:
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
SpringApplicationRunListeners包含了一个SpringApplicationRunListener实现类的List集合。
getRunListeners()方法中,会先通过new的方法创建SpringApplicationRunListeners实例,它需要两个参数,一个是Log,一个是SpringApplicationRunListener实现类对象的List。
而SpringApplicationRunListener实现类的创建和上面的initializers和listeners初始化过程几乎完全相同,通过反射创建出其实现类的实例.
但是通过DEBUG发现SpringApplicationRunListener只有一个实现类就是EventPubulishingRunListener,
它实现了SpringApplicationRunListener和Ordered两个接口。因此执行listeners.starting()方法实际上调用的是EventPubulishingRunListener实例的starting()方法。另外在EventPubulishingRunListener实例化的过程中会注入前面的SpringApplication实例,和SpringApplication的成员变量listeners,其中listeners会被注入到EventPubulishingRunListener的成员变量initialMulticaster中。EventPubulishingRunListener的starting()方法:
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
根据上面代码可见创建了一个ApplicationStartingEvent事件,然后由initialMulticaster将这个事件广播给所有监听ApplicationStartingEvent事件类型的监听器,最后监听器执行监听。starting的代码量感觉量有点多,这里就不继续深入了
2、准备环境监听器执行(配置文件加载)
接下来看这行代码:
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared((ConfigurableEnvironment)environment);
this.bindToSpringApplication((ConfigurableEnvironment)environment);
if (!this.isCustomEnvironment) {
environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
} else {
switch(this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
}
根据名称我们应该就能猜测到这个应该是准备应用启动环境的。注意里面的listeners,可以直接认为就是一个EventPubulishingRunListener实例。prepareEnvironment()中会先根据应用的类型创建一个ConfigurableEnvironment实例,因为我们的应用类型是"SERVLET",因此创建出的实例实际上是StandardServletEnvironment。
然后执行configureEnvironment()方法,感觉这个是和配置相关的方法,看下面代码:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService)conversionService);
}
this.configurePropertySources(environment, args);
this.configureProfiles(environment, args);
}
根据名称应该是和应用配置的一些操作,它先判断是否添加添加转换服务(这个就看看现在也不知道它干啥用)
方法configurePropertySources()主要是查看有没有指定新的配置源(通过命令行指定),因为我们并没有通过命令行指定,这里就不往下继续看代码了。
第二个方法是configureProfiles(),看名字应该是和配置文件相关的,那么我们的application.properties是不是应该在这里面加载的呢??我们接着往下看configureProfiles()方法:
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
environment.getActiveProfiles();
Set
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
这里通过AbstractEnvironment的方法获取所有的"ActiveProfiles",然后添加的应用环境的"activeProfiles"变量中去。而AbstractEnvironment获取"ActiveProfiles"就是获取配置文件的"spring.profiles.active"属性来获取相应的文件名称的,但是这里并没有能获取到任何的"ActiveProfiles",按道理应该是可以得到对应得启动配置文件的名称,通过debug发现要在执行下面这个监听器的环境准备后才能在environment中找到对应的配置文件。
那只能继续往下执行下面的方法:
listeners.environmentPrepared((ConfigurableEnvironment)environment);
前文说到了,这个"listeners"其实就是EventPubulishingRunListener,是一个发布事件的监听器。这里它会创建一个ApplicationEnvironmentPreparedEvent实例,然后是通过SimpleApplicationEventMulticaster这一事件广播器拿到对应的监听器,并执行。这里最终拿到的监听器是ConfigFileApplicationListener,它会根据配置文扩展名以及默认配置文件名"application",在"classpath:/,classpath:/config/,file:./,file:./config/"下面寻找配置文件,这里面的方法感觉比较复杂,很多次的遍历(当时看的很晕),有兴趣可以去仔细看一下这部分代码。
到这里就完成我们应用配置文件的加载。
这个中间其实还是有个banner输出我就不说了,直接看看我们要了解的重点这个上下文的构建
3、创建应用上下文
context = this.createApplicationContext();
这个方法会根据我们应用的类型,完成应用ConfigurableApplicationContext的创建,上面也有提到,我们的应用是一个"servlet",这个方法里也是根据反射获取到对应"context"的类对象,然后进行实例化。最终创建出的是AnnotationConfigServletWebServerApplicationContext实例,根据名称就可以知道这是一个注解可配置的web应用的上下文对象。
4、创建各种bean对象
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
这三个方法放到一起说,根据方法名称可以看首先将之前创建出来的content容器进行准备处理(可以理解为初始),
然后刷新容器。自己跟踪了一下代码,但是感觉自己能力有限,有些内容并不太清楚,所以只能简单的介绍一下。
首先会将在"SpringApplication"中的initializers的几个对象作为注入到"context"容器中,这个过程中也会向"context"容器中注入beanFactoryProcessor,此外还会广播ApplicationPreparedEvent事件给监听器,启动相关的监听器,此外我们自定义的bean,比如controller、service等组件也会在refreshContext()方法执行后注入到"context"容器中(这个我通过debug看到bean名称猜测这个注册的顺序应该和bean的名称有关但具体是什么关系实在难以读下去),自己能力有限,跟踪代码把自己都跟丢了....有些方法又是云里雾里,看来还是自己对spring不熟悉的原因,等自己有时间再一点点的看吧。(不过如果有需要指点bean的执行顺序可以同过order,@AutoConfigureAfter
,@DependsOn
)来解决,这里给出一篇博客关于这3个的用法https://www.cnblogs.com/yihuihui/p/11761105.html
5、应用启动完成
listeners.started(context);
这个方法最终执行的也是EventPublishingRunListener的started(),即有"context"容器发布应用启动的事件,即:ApplicationStartedEvent,这里整个应用就已经启动成功了。
6、runner执行
this.callRunners(context, applicationArguments);
之所以说这个方法,是因为上次做spring boot项目的时候用到过,我觉得有必要说一下。spring boot提供了两个接口CommandLineRunner和ApplicationRunner,如果你想在项目启动后做一些操作的话,实现这两个接口重写run方法就可以了,我感觉也是非常的方便,比如添加一些数据到缓存中。这两个接口的实现类也会做为组件在refreshContext方法之后注入到"context"容器中。
以上就是spring boot的启动,自己也是第一次专门的看框架源码,不知从何处着手,所以就只能大概的分析下启动的过程,而且有很多地方自己看的还不够深入,还有各个监听器的作用也需要自己去了解,等自己有时间再来学习一下。