创建一个SpringBoot应用启动类的两个关键点
- 在main方法中调用 SpringApplication.run(Class<?> primarySource, String... args)
- 在启动类上添加@SpringBootApplication注解
下面针对以上两点对SpringApplication源码进行解读,首先来看SpringApplication的静态run方法
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param primarySource the primary source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
可以看出第一个静态run方法是对第二个静态run方法的调用,来看看第二个静态run方法,这个方法通过SpringApplication对象的构造方法创建了一个SpringApplication的对象实例,并调用了SpringApplication的实例方法run(args[])。那么SpringApplication构造做了那些工作呢?
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #SpringApplication(ResourceLoader, Class...)
* @see #setSources(Set)
*/
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
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 = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
通过上面的代码可以看出SpringApplication主要做了一下几件事情:
- 确定该应用的源。
- 根据类路径中引入的web组件库推断出当前web环境的类型,具体推断过程参考WebApplicationType枚举类的静态deduceFromClasspath()方法。
- 将所有实现了ApplicationContextInitializer接口的实现类进行实例化。
- 将所有实现了ApplicationListener接口的实现类进行实例化。
- 通过当前线程的堆栈信息推断出mainApplicationClass。
下面重点关注ApplicationContextInitializer、ApplicationListener接口实现类的实例化过程。即对SpringApplicationget的SpringFactoriesInstances方法的调用。
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 = getClassLoader();
// 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;
}
实例化的过程利用了java的反射机制,具体实现参考下面的代码。
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
这段代码就是java反射的具体使用,没有特别需要说明的地方,下面主要分析一下getSpringFactoriesInstances()方法中的SpringFactoriesLoader.loadFactoryNames(type, classLoader)这句代码。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 {
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 factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
上面这段源码中Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION))这句代码是一个从spring.factories中加载资源,并将指定类型的资源从Map中取出,这段代码的目的就是取出spring.factories中配置的ApplicationListener接口的实现了类和ApplicationContextInitializer接口实现类的集合,并将其实例化。
构造之后的run()方法解析
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
// 创建一个SpringBoot异常报告集合
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
// 1. 发布一个应用启动事件
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 2. 根据当前ClassPath的类型创建一个应用启动环境,并发布一个环境准备事件 listeners.environmentPrepared(environment);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置启动环境的Bean信息忽略参数
configureIgnoreBeanInfo(environment);
// 配置应用启动环境的Banner,Banner打印机制是在环境准备完成之后进行的
Banner printedBanner = printBanner(environment);
// 3. 根据当前ClassPath环境创建ApplicationContext
context = createApplicationContext();
// 实例化异常报告类
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 4. 准备ApplicationContext,同时发布上下文准备事件和上下文载入事件,这一步同时准备和载入上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新应用上下文
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
// 5. 发布应用启动成功事件 }
listeners.started(context);
// 调用所有实现了ApplicationRunner接口和CommandRunner接口的实例
callRunners(context, applicationArguments);* }
catch (Throwable ex) {
// 6. 如果整个过程包错,则会发布启动异常的错误报告,并发布启动失败事件
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 7.发布应用程序运行中事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
run方法做了一下几件事情
- 开启了应用停止监控
- 配置了headless系统环境
- 实例化了{@link org.springframework.boot.SpringApplicationRunListener}监听器,并在SpringApplication启动过程中发布相应的事件
- 创建并准备了应用启动的环境,如果是Servlet环境则创建StandardServletEnvironment实例,如果是Reactive
环境则创建StandardReactiveWebEnvironment环境,默认则创建StandardEnvironment环境 - 实例化了{@link org.springframework.boot.SpringBootExceptionReporter}
- 创建并准备了应用上下文环境,并在期间执行了所有{@link ApplicationContextInitializer}的接口实现的调用。
- 刷新了应用上下文环境
- 调用了{@link org.springframework.boot.ApplicationRunner}和{@link org.springframework.boot.CommandLineRunner}实现类的run方法
- 当启动报错时,处理了错误报告
SpringBoot应用上下文的实例化过程,代码如下:
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_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);
}
这段代码通过反射的方式实例化了相应的上下文,本例中我们使用的是webServlet环境所以实例化的是{@link org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext}容器。到此SpringBoot的启动流程差不多就完成了。
网友评论