Spring Boot 1.5x启动全过程源码分析(上)
前言
基于 Spring Boot 1.5 版本进行分析,阅读本文需要有一些 Java 和 Spring 框架基础。
Spring Boot 的入口类
@SpringBootApplication
@ImportResource("classpath:spring/spring.xml")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class PayApplication {
private static final Logger log = LoggerFactory.getLogger(PayApplication.class);
public static void main(String[] args) {
SpringApplication.run(PayApplication.class, args);
//起socket服务
SocketServer server = new SocketServer();
server.startSocketServer(8084);
}
}
Spring Boot 最简单通用的入口类。入口类的要求是最顶层包下面第一个含有 main 方法的类,使用注解 @SpringBootApplication 来启用 Spring Boot 特性,使用 SpringApplication.run 方法来启动 Spring Boot 项目。
下面来看看关于run的源码。
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
第一个参数 source:加载的主要资源类
第二个参数 args:传递给应用的应用参数
由于传过去的参数args,只有一个,所以调用的是第一个run方法
看看run方法内部
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
**
创建一个SpringApplication应用类
**
1.initialize()初始化资源
下面是有关initialize的方法
public SpringApplication(Object... sources) {
initialize(sources);
}
private void initialize(Object[] sources) {
//1.初始化主要加载资源类集合并去重
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//2.推断是否是web应用的核心方法
this.webEnvironment = deduceWebEnvironment();
//3.设置应用上线文初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//4.设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//5.推断主入口应用类
this.mainApplicationClass = deduceMainApplicationClass();
}
initialize方法里对应用启动所需要的资源进行初始化。这个sources对象是当前应用的PayApplication,如下图
2.deduceWebEnvironment()推断是否是web应用的核心方法
来看deduceWebEnvironment()方法和相关源码;
private boolean deduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
这个就是根据类路径下是否有对应项目类型的类推断出不同的应用类型。
3.setInitializers设置应用上下文初始化器
设置应用上下文初始化器,其实就是初始化一个 ApplicationContextInitializer 应用上下文初始化器实例的集合
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
this.initializers.addAll(initializers);
}
3.1 getSpringFactoriesInstances
再来看看getSpringFactoriesInstances这个方法的源码
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
//1.获取当前线程上下文类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//2.获取 ApplicationContextInitializer 的实例名称集合并去重
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//3.根据以上类路径创建初始化器实例列表
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//4.初始化器实例列表排序
AnnotationAwareOrderComparator.sort(instances);
//5.返回初始化器实例列表
return instances;
}
设置应用上下文初始化器可分为以下 5 个步骤
1. 获取当前线程上下文类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
2. 获取 ApplicationContextInitializer 的实例名称集合并去重
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
有关loadFactoryNames方法相关源码
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
根据类路径下的 META-INF/spring.factories 文件解析并获取 ApplicationContextInitializer 接口的所有配置的类路径名称。
3. 根据以上类路径创建初始化器实例列表
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<T>(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;
}
4. 初始化器实例列表排序
AnnotationAwareOrderComparator.sort(instances);
5. 返回实例
return instances;
4.设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<ApplicationListener<?>>();
this.listeners.addAll(listeners);
}
ApplicationListener是一个接口,里面只有一个onApplicationEvent方法。如果在上下文中部署一个实现了ApplicationListener接口的bean,那么每当在一个ApplicationEvent发布到 ApplicationContext时,调用ApplicationContext.publishEvent()方法,这个bean得到通知。类似于Oberver设计模式。
设置监听器和设置初始化器调用的方法是一样的,只是传入的类型不一样,设置监听器的接口类型为: getSpringFactoriesInstances
对应的 spring-boot-autoconfigure-2.0.3.RELEASE.jar!/META-INF/spring.factories 文件配置内容请见下方。
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
5.推断主应用入口类
this.mainApplicationClass = deduceMainApplicationClass();
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;
}
getStackTrace()返回一个表示该线程堆栈转储的堆栈跟踪元素数组,通过构造一个运行时异常,再遍历异常栈中的方法名,获取方法名为 main 的栈帧,从来得到入口类的名字再返回该类
总结
Sringboot启动源码,每一个关键方法涉及的东西比较多,不深入探讨。本篇先将SpringApplication的构造过程大致过了一遍,下一篇就是关于run方法。