原创文章,转载请标注出处:《Spring基础系列-容器启动流程(2)》
一、概述
上一篇文章讲述了SSM架构的web项目中容器的启动流程,这一篇我们来看看SpringBoot项目中容器的启动流程。
二、启动流程
Springboot项目需要直接从main方法启动。
源码1-来自DemoApplicaiton(应用自定义的)
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
首先调用的是SpringApplication中的run方法。
源码2-来自:SpringApplication
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);
}
第一个run方法会调用第二个run方法。
在第二个run方法中有两步:
- 第一步:创建SpringApplicaiton实例
- 第二步:调用run方法
首先我们先创建SpringApplication实例:
源码3-来自:SpringApplication
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判断应用类型
this.webApplicationType = deduceWebApplicationType();
//添加初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//添加监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//定位main方法所在类,并做记录
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringBoot应用有三种类型:
源码4-来自:WebApplicationType
public enum WebApplicationType {
NONE,SERVLET,REACTIVE
}
解析:
- NONE:应用不是web应用,启动时不必启动内置的服务器程序
- SERVLET:这是一个基于Servlet的web应用,需要开启一个内置的Servlet容器(web服务器)
- REACTIVE:这是一个反应式web应用,需要开启一个内置的反应式web服务器
三者的判断条件如下
源码5-来自:SpringApplication
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
上面的方法中涉及四个常量:
源码6-来自:SpringApplication
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
至于代码很是简单。
然后是设置初始化器,结合之前的讲述在SSM架构的web应用中也拥有初始化器,这两个代表的是同一个概念,不同于之前的处理方式,这里仅仅是将初始化器找到并设置到initializers属性中。
我们来看看它是如何获取这些初始化器的:
源码7-来自:SpringApplication、SpringFactoriesLoader
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 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;
}
// 下面来自:SpringFactoriesLoader
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//首先尝试从缓存获取
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try
//从META-INF/spring.factories文件中获取配置的内容
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
//添加到缓存备用
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
由此可见,初始化器是从META-INF/spring.factories文件中获取到的,那么我们就可以自建该目录文件进行添加,前提是需要自定义初始化器。
还有这里只有在第一次调用时需要从文件读取,第二次就可以从缓存获取,哪怕需要的不是初始化器的内容,因为第一次的时候就会将所有的配置内容全部获取并解析保存到缓存备用。这也就为下一步设置监听器这一步省下了不少时间。
最后一步就是将main方法所在类做个记录。
源码8-来自:SpringApplication
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;
}
通过新建一个运行时异常的方式获取方法调用栈,在栈中搜索方法名为main的方法所在的类。
如果我们想要获取方法的调用栈也可以采用这种方式,使用异常来获取调用栈。
完成SpringApplication实例的创建之后,直接调用其run方法,执行应用启动。
源码9-来自:SpringApplication
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//配置系统属性headless,默认为true
configureHeadlessProperty();
//获取所有的运行时监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();//启动监听器
try {
//封装自定义参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//根据参数配置环境environment
//如果是web项目则创建的是StandardServletEnvironment,否则是StandardEnvironment
//然后配置属性和profile
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//设置是否忽略BeanInfo,默认为true
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//创建ApplicationContext
//如果是Servlet web项目则创建AnnotationConfigEmbeddedWebApplicationContext,如果是Reactive web
//项目则创建AnnotationConfigReactiveWebServerApplicationContext,否则AnnotationConfigApplicationContext
context = createApplicationContext();
//加载异常报告器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//筹备上下文环境,也就是初始化
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文
refreshContext(context);
//刷新后操作,这是两个孔方法,是可以自定义实现逻辑的
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);//容器和应用启用之后,callRunner之前执行
//容器启动完成之后回调runner,包括:ApplicationRunner和CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
这是整个容器启动的所有逻辑入口,参照注释即可理解,我们重点关注prepareContext方法,这个方法是在容器刷新之前的预初始化操作:
源码10-来自:SpringApplication
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置environment到上下文
context.setEnvironment(environment);
//设置BeanName生成器
//设置资源加载器或者类加载器
postProcessApplicationContext(context);
//执行初始化器
applyInitializers(context);
listeners.contextPrepared(context);//执行监听器的contextPrepared操作
//配置日志信息
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
//注册单例(springApplicationArguments,springBootBanner)
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载Bean到容器
load(context, sources.toArray(new Object[0]));
//执行监听器的contextLoaded操作
listeners.contextLoaded(context);
}
重点关注下load方法,他的目的是加载Bean定义:
源码11-来自:SpringApplication
//为load操作做准备,创建资源读取器扫描器
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
//创建BeanDefinition读取器,扫描器(AnnotatedBeanDefinitionReader,XmlBeanDefinitionReader,
//GroovyBeanDefinitionReader,ClassPathBeanDefinitionScanner)
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
//配置BeanName生成器,资源加载器,环境参数到读取器和扫描器中
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
//执行Bean的加载
loader.load();
}
//统计加载Bean的数量
public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
}
//针对不同的情况加载bean资源
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
//
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
//Resource资源可能是XML定义的Bean资源或者groovy定义的Bean资源
if (source instanceof Resource) {
return load((Resource) source);
}
//Packet资源一般是用于注解定义的Bean资源,需要扫描器扫描包
if (source instanceof Package) {
return load((Package) source);
}
//
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
针对不同的资源进行加载。
下面就要回到run方法中的重点操作refreshContext(context)了:
源码12-来自:SpringApplication
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
这里也到了执行refresh()方法的时候了,下面我们就要看看这个refresh了。