注明:以下内容基于spring-boot-1.4.2,starter为spring-boot-starter-web。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependencies>
第一步,从启动过程开始。
@SpringBootApplication
public class Application{
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
上面是官方提供的一个简单的启动类,就从这几行简单的代码开始学习框架吧。
和普通java程序一样,main作为入口函数。在启动类中只有一行,直接调用org.springframework.boot.SpringApplication类中的静态run方法,传入启动类的Class对象与启动时所带的参数【可选】;通过该静态方法,实例化SpringApplication,同时调用实例run方法。
* from org.springframework.boot.SpringApplication
/**
* 使用默认设置从指定的source启动一个Spring应用.
* @param source 加载来源
* @param args 启动参数,通常来源与java的main方法参数
* @return 返回ApplicationContext对象
*/
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
/**
* 使用默认设置从指定的1到多个source启动一个Spring应用.
* @param source 加载来源
* @param args 启动参数,通常来源与java的main方法参数
* @return 返回ApplicationContext对象
*/
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
在SpringApplication的构造函数中,调用私有的initialize方法进行变量的初始化。
* from org.springframework.boot.SpringApplication
/**
* 创建一个新的实例。应用上下文会从指定来源加载bean。实例在调用run方法前被定制化。
* @param sources bean来源
*/
public SpringApplication(Object... sources) {
initialize(sources);
}
private void initialize(Object[] sources) {
// sources初始化,类型为LinkedHashSet,此处填充了启动类的Class对象
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//webEnvironment,类型为boolean
this.webEnvironment = deduceWebEnvironment();
//设置初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//mainApplicationClass,类型为Class
this.mainApplicationClass = deduceMainApplicationClass();
}
初始化内容包括4项,分别为sources,webEnvironment,初始化器,监听器和mainApplicationClass,除了sources为直接填充外,其它几项都是通过调用方法完成初始化。接下来我们逐一来看。
webEnvironment初始化
调用私有方法deduceWebEnvironment,在该方法中会检查项目中是否引入了web工程所必要的类并且可以被加载,包括javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext,在ClassUtils.isPresent方法中会尝试找到并且进行类加载,任何一个类不满足要求,都会返回false。因为webEnvironment的含义应该是是否满足web工程运行条件。
* from org.springframework.boot.SpringApplication
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" };
初始化器Initializers
首先通过调用私有的getSpringFactoriesInstances方法获取ApplicationContextInitializer或者其子类的实例集合。
* from org.springframework.boot.SpringApplication
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) {
//获取当前线程所运行的类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 使用类名作为key,防止重复
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
获取初始化器的方法步骤比较多,有多层嵌套,我们一层层看。第一步是获取当前线程所运行的类加载器,这个没有疑问。第二步是加载配置文件,获取到初始化器的类名。
* from org.springframework.core.io.support.SpringFactoriesLoader
/**
* 使用给定的类加载器加载FACTORIES_RESOURCE_LOCATION中给出的factoryClass全部合格实现类的名称.
* @param factoryClass 父级接口或者抽象类
* @param classLoader 加载资源所使用的类加载器,如果为空的时候使用默认加载器
* @see #loadFactories
* @throws IllegalArgumentException 加载类名失败时抛出
*/
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);
}
}
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
我们再看看spring.factories中包含的内容,key是org.springframework.context.ApplicationContextInitializer。
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
此时Set<String> names中包含了上述四个类名。紧接这就是实例化。
* from org.springframework.boot.SpringApplication
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;
}
在私有方法createSpringFactoriesInstances中,使用反射机制对获取的4个类分别进行了实例化,放在ArrayList中并返回对象集合。在这里,这四个初始化器都实现了ApplicationContextInitializer接口,实现了仅有的initialize方法,各自负责不同的模块初始化逻辑。
编号 | 类 | 作用 |
1 | org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer | 检查常见的配置错误 |
2 | org.springframework.boot.context.ContextIdApplicationContextInitializer | 给ApplicationContext设置一个ID |
3 | org.springframework.boot.context.config.DelegatingApplicationContextInitializer | 委托context.initializer.classes环境变量指定的初始化器(通过类名)进行初始化工作 |
4 | org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer | 将内嵌的Web服务器使用的端口给设置到ApplicationContext中 |
到这里,涉及到的初始化器的实例全部准备就绪,但是初始化工作还没有开展,每个初始化器的具体工作会在执行初始化的时候再详细介绍。在代码中有两个工具类用屡次被使用:
– 一个是org.springframework.beans.BeanUtils,这个类中包含了一些java bean的静态方法,主要使用反射机制来初始化类,检查属性,拷贝属性等。
– 还有一个是org.springframework.util.ClassUtils,这个类包含各种Class对象操作,包括获取报名,方法名等等。
以上工具类主要在spring内部使用,但是很多静态方法都是公共的,在自己的程序中如果有需要应该也可以使用吧。
在获取了初始化器实例以后,还需要对实例进行排序。
* from org.springframework.core.annotation.AnnotationAwareOrderComparator
public static void sort(List<?> list) {
if (list.size() > 1) {
Collections.sort(list, INSTANCE);
}
}
使用的比较器就是AnnotationAwareOrderComparator,在上述四个初始化器中,只有DelegatingApplicationContextInitializer实现了Ordered接口,并设置order值为0,其它初始化器没有设置,使用默认的最低优先级,从而区分出在集合中与后续执行的先后顺序,并全部填充到SpringApplication的initializers中去。
设置监听器
获取监听器实例的过程和获取初始化器的过程是相同的,不同的是监听器的配置key在spring.factories中使用的是org.springframework.context.ApplicationListener。
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
同样,这个key也是上述监听器共同的实现接口。不同的监听器职责不同,详细的职责还需要再进一步深入挖掘。
编号 | 类 | 作用 |
---|---|---|
1 | org.springframework.boot.ClearCachesApplicationListener | 一旦上下文加载完就清理缓存 |
2 | org.springframework.boot.builder.ParentContextCloserApplicationListener | 如果父级context关闭以后关闭子级上下文。该监听器设置了order,处于较低优先级,比默认最低优先级要高一些。 |
3 | org.springframework.boot.context.FileEncodingApplicationListener | 检查系统文件的编码是否和环境期望的文件编码一致,如果不一致,中断启动程序。显示指定优先级为最低。 |
4 | org.springframework.boot.context.config.AnsiOutputApplicationListener | 根据配置文件设置ANSI输出的开关等信息,优先级比ConfigFileApplicationListener低一级。 |
5 | org.springframework.boot.context.config.ConfigFileApplicationListener | 加载环境基本配置信息,默认为 ‘application.properties’ 和/或 ‘application.yml’,优先级比最高优先级稍低一些。 |
6 | org.springframework.boot.context.config.DelegatingApplicationListener | 委托context.listener.classes配置的其它监听器来执行任务,设置order为0,优先级处于中间位置。 |
7 | org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener | 如果相关参数存在,则使用springboot相关的版本进行替代 |
8 | org.springframework.boot.logging.ClasspathLoggingApplicationListener | 记录线程上下文加载类TCCL的classpath到DEBUG级别的,设置order比LoggingApplicationListener的优先级稍低一点。 |
9 | org.springframework.boot.logging.LoggingApplicationListener | 根据配置初始化日志系统,优先级比最高优先级稍低一些。低于ConfigFileApplicationListener。 |
设置mainApplicationClass
通过RuntimeException的异常实例的错误栈信息来获取main函数所在的类,并返回类的Class对象。比直接使用传进来的sources更保险。
* from org.springframework.boot.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;
}
自此,才完成SpringApplication的实例化。