spring boot确实对java开发者来说是一个很好的选择,自己也非常愿意使用spring boot开发(想想spring的配置文件就有点头疼),今天准备好好的学习一下spring boot启动到底是怎样一个流程!
一、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对象,其中Class可以是多个。
在SpringApplication的构造方法内主要完成了以下几个事情:
1、确定Web应用的类型。
我们看到WebApplicationType.deduceFromClasspath()方法是根据判断当前的类的类型来判断web应用的类型的:
static WebApplicationType deduceFromClasspath() {
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)) {
// webFlux
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;
}
}
//servlet
return SERVLET;
}
}
"org.springframework.web.reactive.DispatcherHandler"应该是WebFlux模块类似与MVC前端控制器这样的一个角色,关于WebFlux知识点因为自己也不了解这里就不介绍了。
2、initializers的初始化
通过set方法完成SpringApplication成员变量initializers,即一些初始化的相关操作。它是一个List<ApplicationContextInitializer>,这些初始化的initializer实例创建是通过反射创建的。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return this.getSpringFactoriesInstances(type, new Class[0]);
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList(names.size());
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);
}
3、成员变量listeners的初始化
通过set方法完成SpringApplication成员变量listeners,即一些监听器的初始化操作。
这个和上面的方法基本相同,唯一不同的是监听器接口类型是ApplicationListener,下面是通过DEBUG模式获取到ApplicationListener所有实现类名称,即names变量的值:
这里完成了一系列listener的初始化。
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.<init>这个方法,这个<init>方法代表什么意思?而且根据行数也看不出什么,有空要好好找下资料或者网上问问,但是根据方法名称的意思就是初始化,在考虑执行顺序在deduceMainApplicationClass()方法前,两个run()方法后。是不是代表着我正在执行的SpringApplication的两个构造方法??第二个构造方法还没有完成,自然第一个构造方法也还在执行中,所以就有两个<init>方法。有点迷惑,如果有了解的希望能够指点一下。
//第一个构造方法
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类的实例。其实上面这些代码做的内容主要就2点:一是完成initializers的初始化,二是一些监听器的初始化,不过要深入下去的话内容还是很多的,今天就不继续追踪了。
二、SpringApplication实例run方法执行
这个方法应该可以代表整个应用的启动过程了,方法里面内容很多,只说一些我认为比较重要的。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> 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对象,也就是一个可配置应用的上下文对象,方法返回后我们的程序也就启动完成了。
1、应用启动监听器执行监听
我们先看这行代码:
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
SpringApplicationRunListeners包含了一个SpringApplicationRunListener实现类的List集合。getRunListeners()方法中,会先通过new的方法创建SpringApplicationRunListeners实例,它需要两个参数,一个是Log,一个是SpringApplicationRunListener实现类对象的List(变量名也是listeners,注意和上面的listeners区分)。而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事件类型的监听器,最后监听器执行监听。代码量感觉量有点多,这里就不再继续跟踪了。
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;
}
根据名称我们应该就能猜测到这个应该是准备应用启动环境的。注意里面的listeners,可以直接认为就是一个EventPubulishingRunListener实例。prepareEnvironment()中会先根据应用的类型创建一个ConfigurableEnvironment实例,因为我们的应用类型是"SERVLET",因此创建出的实例实际上是StandardServletEnvironment。
然后执行configureEnvironment()方法,感觉这个是和配置相关的方法,看下面代码:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
this.configurePropertySources(environment, args);
this.configureProfiles(environment, args);
}
根据名称应该是和应用配置的一些操作,第一个方法configurePropertySources()主要是查看有没有指定新的配置源(通过命令行指定),因为我们并没有通过commandLine指定,这里就不往下继续看代码了。
第二个方法是configureProfiles(),看名字应该是和配置文件相关的,那么我们的application.properties是不是应该在这里面加载的呢??我们接着往下看configureProfiles()方法:
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
environment.getActiveProfiles();
Set<String> profiles = new LinkedHashSet(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
这里通过AbstractEnvironment的方法获取所有的"ActiveProfiles",然后添加的应用环境的"activeProfiles"变量中去。而AbstractEnvironment获取"ActiveProfiles"就是获取配置文件的"spring.profiles.active"属性来获取相应的文件名称的,可是遗憾的是这里并没有能获取到任何的"ActiveProfiles",我把这个段代码跟踪完并没用发现任何的配置文件。
那只能继续往下执行下面的方法:
listeners.environmentPrepared((ConfigurableEnvironment)environment);
前文说到了,这个"listeners"其实就是EventPubulishingRunListener,是一个发布事件的监听器。这里它会创建一个ApplicationEnvironmentPreparedEvent实例,然后是通过SimpleApplicationEventMulticaster这一事件广播器拿到对应的监听器,并执行。这里最终拿到的监听器是ConfigFileApplicationListener,它会根据配置文扩展名以及默认配置文件名"application",在"classpath:/,classpath:/config/,file:./,file:./config/"下面寻找配置文件,这里面的方法感觉比较复杂,很多次的遍历(当时看的很晕),有兴趣可以去仔细看一下这部分代码。
到这里就完成我们应用配置文件的加载。
下面是在ConfigFileApplicationListener中DEBUG时的几个截图:
图-001显示的是配置文件扩展名的一个set集合,其实我一直以为spring boot配置文件只支持properties和yml,看来是错的,实际上应该是支持图中这4种的,properties和yml都是使用过的,就不说了。xml是传统的spring开发的配置文件,这个支持也是情理之中。但是yaml语言文件的后缀名是yml,所以yaml出现在这里是什么情况??(难道我理解有误?)
图-002就是在“classpath:/”下寻找默认名称为application,扩展名为properties的文件,根据图中的变量名称和值还是很好理解的。
图-003是找到"ActiveProfiles"后加载配置文件的截图,之所以名称是"test",是我在application.properties配置了spring.profiles.active=test。
图-004是所有的配置源列表,我自己创建的application.properties和application-test.properties都在,其他的应该是一些默认配置源。
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);
这三个方法放到一起说,根据方法名称可以看出这里也是做一些"context"容器的一些准备工作,然后刷新容器。自己跟踪了一下代码,但是感觉自己能力有限,有些内容并不太清楚,所以值简单的介绍一下。
首先会将在"SpringApplication"中的initializers的几个对象作为注入到"context"容器中,这个过程中也会向"context"容器中注入beanFactoryProcessor,此外还会广播ApplicationPreparedEvent事件给监听器,启动相关的监听器,此外我们自定义的bean,比如controller、service等组件也会在refreshContext()方法执行后注入到"context"容器中,我觉得这几个方法应该是spring boot的核心,但是自己能力有限,跟踪代码把自己都跟丢了....有些方法又是云里雾里,看来还是自己对spring不熟悉的原因,等自己有时间再一点点的看吧。
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的启动,自己也是第一次专门的看框架源码,不知从何处着手,所以就只能大概的分析下启动的过程,而且有很多地方自己看的还不够深入,还有各个监听器的作用也需要自己去了解,等自己有时间再来学习一下。