對我來說,寫博客是一個自我學習,自我提升的一種方式,本文所有的內容也是我查看了好多博主的文章後,在自我探索和驗證的過程,我會盡量把所有涉及到的方法都寫在本文中,也希望您能在閱讀的同時對我提出寶貴意見。
我們都知道springboot都有一個main方法,main裏面調用SpringApplication.run()啓動整個spring-boot程序,args就是我們執行啓動命令帶入的參數,默認以空格隔開
從main函數說起
/**
* 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<C extends ConfigurableApplicationContext> {
/**
* 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 <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 = Thread.currentThread().getContextClassLoader();
// 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;
}
設置應用上下文初始化器主要分以下五個步驟:
1、獲取當前線程的上下文類加載器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
2、獲取ApplicationContextInitializer
實例名稱並去重
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
loadFactoryNames
方法相關的源碼如下:
public static List<String> 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<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()) {
List<String> 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<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<>(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<E extends ApplicationEvent> 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方法,內容很多,下一章在總結。