文章目录
SpringBoot启动流程分析
探究下SpringBoot到底是如何做到"约定优于配置"的。
首先,先找到这个应用程序的入口主方法,在上面打一个断点。
启动之后,Debug进入到SpringApplication.run()
方法:可以看到这里首先创建了SpringApplication对象,然后调用其run方法。
所以本文会花两个部分来介绍Spring的初始化过程,包括:
new SpringApplication()
(SpringApplication) run()
SpringApplication类的初始化
到这里会执行new SpringApplication(primarySources)
创建spring应用对象,继续往下跟会执行SpringApplication构造器。
// 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));
// 1. 可能的web应用程序类型的类型。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 2. 设置初始化应用context
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 3.设置初始化监听
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 4. 推演主程序类
this.mainApplicationClass = deduceMainApplicationClass();
}
很多不为人知的事情都是发生在这个对象初始化的时候,这里我们都来一一解密。
1. 推断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)) {
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;
}
}
这段代码是来推断我们的应用是哪种web应用程序。Spring boot依据Class path来推断web应用类型:
- reactive
路径包含org.springframework.web.reactive.DispatcherHandler
,并且不包含org.springframework.web.servlet.DispatcherServlet
和org.glassfish.jersey.servlet.ServletContainer
- none
不是reactive,并且路径中没有javax.servlet.Servlet
和org.springframework.web.context.ConfigurableWebApplicationContext servlet
- 剩余的部分都是servlet
public enum WebApplicationType {
NONE, // 不是web应用
SERVLET, // servlet容器
REACTIVE; // 反应型web应用(webflux)
当然一开始我们加入了web的依赖,所以我们是servlet容器。springboot在启动过程中将会根据当前应用的类型创建对应的ApplicationContext。
springboot在启动时需要推断当前的应用环境,springboot2.0当中一共定义了三种环境:none, servlet, reactive。
none表示当前的应用即不是一个web应用也不是一个reactive应用,是一个纯后台的应用。
servlet表示当前应用是一个标准的web应用。
reactive表示是一个响应式的web应用。
现在的webflux框架就是响应式的,响应式代表其是非阻塞的,而以前使用的spring mvc是阻塞的。
2. 初始化ApplicationContextInitializer
在设置初始化应用context的时候 ,是先执行了getSpringFactoriesInstances(ApplicationContextInitializer.class)
方法,参数是ApplicationContextInitializer.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<>(
// 加载ApplicationContextInitializer.class类型的类
// 这里传入就是参数 ApplicationContextInitializer.clas
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 实例化加载到的类
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//加载完这些类对它们的顺序进行排列,
//一般通过@Order注解或者Ordered接口来规定优先级
AnnotationAwareOrderComparator.sort(instances);
// 返回
return instances;
}
这里为什么要进行排序,是为了避免初始化的时候,类之间的依赖关系的。
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 {
// 去资源路径下加载
public static final String ACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static final String FACTORIES_RESOURCE_LOCATION = "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()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
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
,通过断点我们可以看到应用程序是加载了以下几个jar下的spring.factores文件。
实际上就是通过工厂类来创建了文件中包含的类。
双击Shift搜索
spring.factories
可以看到它存在于以下工程中
- spring-boot-2.1.5.RELEASE.jar下的spring.factores(截图未完整截取)
- spring-boot-autoconfigure-2.1.5.RELEASE.jar下的spring.factores
- spring-beans-5.1.7.RELEASE.jar下的spring.factores
从Map中根据org.springframework.context.ApplicationContextInitializer
的类型拿到需要的类初始化类,断点进入getOrDefault(factoryClassName, Collections.emptyList());
方法
之后就是把加载到的需要初始化的类进行实例化添加到一个集合中等待备用
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
3. 初始化ApplicationListener
最关键的的还是这句:
当我们跟进去之后,会发现在初始化监听类的时候和上面初始化应用上下文是一样的代码。
唯一不同的是getSpringFactoriesInstances(ApplicationListener.class))
传进去的是ApplicationListener.class
,而初始化应用上下文传入的参数是ApplicationContextInitializer.class
。
4. 推断主程序类
为什么会需要推断主程序类呢?
查看下面两个代码:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(InnerApplication.class, args);
}
@SpringBootApplication
public static class InnerApplication{
}
}
这两种方式都能让程序运行起来,但是传入run()函数的参数是不一样的,推断主程序类就是起到这么一个作用。
this.mainApplicationClass = deduceMainApplicationClass();
这个实现是怎么实现的呢?也就是通过一个异常堆栈来实现的。基本流程就是创建一个运行时异常,然后获得堆栈数组,遍历StackTraceElement数组,判断方法名称是否为“mian”,如果过是则通过Class.forName()方法创建Class对象。
通过"main".equals
来判断当前堆栈的方法getMethodName()
,可以看到下图是com.example.demo.DemoApplication.main(…)方法。该方法运行的类就是DemoApplication.java类,所以,通过反射来运行该类即可。
到这里就完成了SpringBoot启动过程中初始化SpringApplication的过程。
小结
这篇文章主要是给大家说了下SpringBoot启动过程中初始化SpringApplication的流程,大致可以分为四个步骤:
- 推演web应用的类型(如果没有加web依赖类型NONE)
- 初始化ApplicationContextInitializer
- 初始化ApplicationListener
- 推演出主程序类
通过这样四个步骤就完成了第一步SpringApplication的初始化过程。
run()方法
/**
* 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}
*
* 运行spring应用,并刷新一个新的 ApplicationContext(Spring的上下文)
* ConfigurableApplicationContext 是 ApplicationContext 接口的子接口。在 ApplicationContext
* 基础上增加了配置上下文的工具。 ConfigurableApplicationContext是容器的高级接口
*/
public ConfigurableApplicationContext run(String... args) {
//记录程序运行时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// ConfigurableApplicationContext Spring 的上下文
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//从META-INF/spring.factories中获取监听器
//1、获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//2、构造应用上下文环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//处理需要忽略的Bean
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
///3、初始化应用上下文
context = createApplicationContext();
//实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[]{ConfigurableApplicationContext.class}, context);
//4、刷新应用上下文前的准备阶段
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//5、刷新应用上下文
refreshContext(context);
//刷新应用上下文后的扩展接口
afterRefresh(context, applicationArguments);
//时间记录停止
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//发布容器启动完成事件
listeners.started(context);
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;
}
1. 获取并启动监听器
事件机制在Spring是很重要的一部分内容,通过事件机制我们可以监听Spring容器中正在发生的一些事件,同样也可以自定义监听事件。Spring的事件为Bean和Bean之间的消息传递提供支持。当一个对象处理完某种任务后,通知另外的对象进行某些处理,常用的场景有进行某些操作后发送通知,消息、邮件等情况。
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
回到run方法,debug这个代码 SpringApplicationRunListeners listeners = getRunListeners(args); 看一下获取的是哪个监听器:
EventPublishingRunListener监听器是Spring容器的启动监听器。
listeners.starting();
开启了监听事件。
2. 构造应用上下文环境
应用上下文环境包括什么呢?包括计算机的环境,Java环境,Spring的运行环境,Spring项目的配置(在SpringBoot中就是那个熟悉的application.properties/yml)等等。
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
//创建并配置相应的环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
//根据用户配置,配置 environment系统环境
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 启动相应的监听器,其中一个重要的监听器 ConfigFileApplicationListener 就是加载项目配置文件的监听器。
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
//如果应用类型是 SERVLET 则实例化 StandardServletEnvironment
if (this.webApplicationType == WebApplicationType.SERVLET) {
return new StandardServletEnvironment();
}
return new StandardEnvironment();
}
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
// 将main 函数的args封装成 SimpleCommandLinePropertySource 加入环境中。
configurePropertySources(environment, args);
// 激活相应的配置文件
configureProfiles(environment, args);
}
其他暂略…
3. 初始化应用上下文
在前文,我们推断了应用上下文是none、reactive还是servlet。对应这三种类型,会初始化不同类型的ApplicationContext。
public static final String DEFAULT_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 {
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);
}
应用上下文可以理解成IoC容器的高级表现形式,应用上下文确实是在IoC容器的基础上丰富了一些高级功能。
4. 刷新应用上下文前的准备阶段
prepareContext()
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置容器环境
context.setEnvironment(environment);
//执行容器后置处理
postProcessApplicationContext(context);
//执行容器中的 ApplicationContextInitializer 包括spring.factories和通过三种方式自定义的
applyInitializers(context);
//向各个监听器发送容器已经准备好的事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
//将main函数中的args参数封装成单例Bean,注册进容器
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
//将 printedBanner 也封装成单例,注册进容器
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载我们的启动类,将启动类注入容器
load(context, sources.toArray(new Object[0]));
//发布容器已加载事件
listeners.contextLoaded(context);
}
5. 刷新应用上下文
refresh()
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//刷新上下文环境
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//这里是在子类中启动 refreshBeanFactory() 的地方
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
//准备bean工厂,以便在此上下文中使用
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
//设置 beanFactory 的后置处理
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
//调用 BeanFactory 的后处理器,这些处理器是在Bean 定义中向容器注册的
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
//注册Bean的后处理器,在Bean创建过程中调用
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
//对上下文中的消息源进行初始化
initMessageSource();
// Initialize event multicaster for this context.
//初始化上下文中的事件机制
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//初始化其他特殊的Bean
onRefresh();
// Check for listener beans and register them.
//检查监听Bean并且将这些监听Bean向容器注册
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//实例化所有的(non-lazy-init)单件
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
//发布容器事件,结束Refresh过程
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
} finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
主要根据IoC容器的初始化步骤和IoC依赖注入的过程进行分析,围绕以上两个过程,我们主要介绍重要的方法,其他的请看注释。
IoC容器的初始化过程包括三个步骤,在invokeBeanFactoryPostProcessors()方法中完成了IoC容器初始化过程的三个步骤。
- 第一步:Resource定位
在SpringBoot中,我们都知道他的包扫描是从主类所在的包开始扫描的,prepareContext()方法中,会先将主类解析成BeanDefinition,然后在refresh()方法的invokeBeanFactoryPostProcessors()方法中解析主类的BeanDefinition获取basePackage的路径。这样就完成了定位的过程。其次SpringBoot的各种starter是通过SPI扩展机制实现的自动装配,SpringBoot的自动装配同样也是在invokeBeanFactoryPostProcessors()方法中实现的。还有一种情况,在SpringBoot中有很多的@EnableXXX注解,细心点进去看的应该就知道其底层是@Import注解,在invokeBeanFactoryPostProcessors()方法中也实现了对该注解指定的配置类的定位加载。
常规的在SpringBoot中有三种实现定位,第一个是主类所在包的,第二个是SPI扩展机制实现的自动装配(比如各种starter),第三种就是@Import注解指定的类。(对于非常规的不说了)
- 第二步:BeanDefinition的载入
在第一步中说了三种Resource的定位情况,定位后紧接着就是BeanDefinition的分别载入。所谓的载入就是通过上面的定位得到的basePackage,SpringBoot会将该路径拼接成:classpath*:org/springframework/boot/demo/**/*.class这样的形式,然后一个叫做PathMatchingResourcePatternResolver的类会将该路径下所有的.class文件都加载进来,然后遍历判断是不是有@Component注解,如果有的话,就是我们要装载的BeanDefinition。大致过程就是这样的了。
TIPS:
@Configuration,@Controller,@Service等注解底层都是@Component注解,只不过包装了一层罢了。
- 第三个过程:注册BeanDefinition
这个过程通过调用上文提到的BeanDefinitionRegister接口的实现来完成。这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。通过上文的分析,我们可以看到,在IoC容器中将BeanDefinition注入到一个ConcurrentHashMap中,IoC容器就是通过这个HashMap来持有这些BeanDefinition数据的。比如DefaultListableBeanFactory 中的beanDefinitionMap属性。
参考:IOC