对我来说,写博客是一个自我学习,自我提升的一种方式,本文所有的内容也是我查看了好多博主的文章后,在自我探索和验证的过程,我会尽量把所有涉及到的方法都写在本文中,也希望您能在阅读的同时对我提出宝贵意见。
我们都知道springboot都有一个main方法,main里面调用SpringApplication.run()启动整个spring-boot程序,args就是我们执行启动命令带入的参数,默认以空格隔开
/**
* com.bicai.channelmanager
* {@link }
*
* @author lydong
* @see
* @since 2018/7/10 上午11:48
*/
@MapperScan("com.bicai.channelmanager.**.mapper")
@EnableFeignClients({"com.bicai.channelmanager.server", "com.bicai.common.component.feign"})
@EnableEurekaClient
@SpringBootApplication
public class ChannelManagerBootStrapApplication {
public static void main(String[] args) {
SpringApplication.run(ChannelManagerBootStrapApplication.class, args);
}
}
从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);
}
从这里可以看出,我们传入的class参数被包装成数组,可以发现springboot其实可以同时开启多个应用。执行run方法返回SpringApplication实例。而且这里只有一个方法run,进入看一看
/**
* 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);
}
走到这里,发现这里方法是通过两步来达成的,首先是SpringApplication的构造方法,然后调用其run方法。先从构造方法看,进去,里面是this(null,primarySources)方法,直接进去
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
//1、资源初始化资源加载器为 null
this.resourceLoader = resourceLoader;
//2、验证非null
Assert.notNull(primarySources, "PrimarySources must not be null");
//3、初始化主要加载资源类集合并用set去重
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//4、推断当前 WEB 应用类型
this.webApplicationType = deduceWebApplicationType();
//5、设置应用上下文初始化器(getSpringFactoriesInstances内部通过Thread.currentThread().getContextClassLoader()获取加载器加载)
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//6、设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//7、推断主入口应用类
this.mainApplicationClass = deduceMainApplicationClass();
}
如何判断的WEB应用类型的呢
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
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";
public enum WebApplicationType {
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE
}
isPresent方法里很简单,就是尝试去加载这个类,如果这个类在项目的依赖里,那么会返回true,也就会返回响应web类型,是一个枚举类.在往下看。
public interface ApplicationContextInitializer {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
用来初始化指定的 Spring 应用上下文,如注册属性资源、激活 Profiles 等,再来看下setInitializers方法
public void setInitializers(
Collection extends ApplicationContextInitializer>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
就是初始化一个 ApplicationContextInitializer
应用上下文初始化器实例的集合
在来看下getSpringFactoriesInstances方法
private Collection getSpringFactoriesInstances(Class type) {
return getSpringFactoriesInstances(type, new Class>[] {});
}
private Collection getSpringFactoriesInstances(Class type,
Class>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
设置应用上下文初始化器主要分以下五个步骤:
1、获取当前线程的上下文类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
2、获取ApplicationContextInitializer
实例名称并去重
Set names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
loadFactoryNames
方法相关的源码如下:
public static List loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration 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 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
文件解析并获取 ApplicationContextInitializer
接口的所有配置的类路径名称。
在根据传入的type匹配加载的class
查看该文件spring-boot-autoconfigure-2.0.3.RELEASE.jar,/META-INF/spring.factories
的初始化器相关配置内容如下:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
3、根据以上的类路径创建初始化器实例列表
List instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
private List createSpringFactoriesInstances(Class type,
Class>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set names) {
List 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;
}
4、初始化器实例列表排序
AnnotationAwareOrderComparator.sort(instances);
设置完初始化器之后,设置监听器,可以看下这个类ApplicationListener
@FunctionalInterface
public interface ApplicationListener extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
看到这里可知,ApplicationListener继承了JDK的java.util.EventListener接口,实现了观察者模式,在Spring 3.0时,ApplicationListener可以通用地声明事件类型是它感兴趣的。当用Spring ApplicationContext注册时,事件将相应地过滤*,并调用侦听器以匹配事件
设置监听器和设置初始化器调用的方法是一样的,只是传入的类型不一样,设置监听器的接口类型为:
getSpringFactoriesInstances
,对应的 spring-boot-autoconfigure-2.0.3.RELEASE.jar,/META-INF/spring.factories
文件配置内容请见下方。
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
可以看出目前只有一个 BackgroundPreinitializer
监听器。
然后推断主入口应用类
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;
}
这个方式很特别,我是第一次看到,是构造一个运行时异常,在遍历异常栈中的方法名,找到方法名匹配main方法的栈帧,然后返回该类
到这,SpringApplication的构造方法的初始化流程就执行完毕,之后执行run方法,内容很多,下一章在总结。