對於每一個使用過SpringBoot/SpringCloud的程序員而言,下面這段代碼可能大家都不陌生:
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
SpringApplication是springboot驅動spring應用上下文的引導類,用於啓動和引導springBoot應用程序。
從SpringApplication.run(TestApplication.class, args)開始追蹤,第一步我們會來到這個地方:
/**
* 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);
}
按照註釋來說,這個方法接收的兩個參數分別是:
第一個參數 primarySource
:加載的主要資源類
第二個參數 args
:傳遞給應用的應用參數
繼續向下,我們可以看到,這一步返回了一個 new SpringApplication(primarySources).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);
}
這一步需要注意的是,這個地方傳遞了一個 null
/**
* 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 = deduceWebApplicationType();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
然後便到了這個構造器。可以看出,這個構造器大概分爲了七步完成初始化:
//第一步,初始化資源加載器爲 null
this.resourceLoader = resourceLoader;
//第二步,使用斷言,確保資源類不能爲null
Assert.notNull(primarySources, "PrimarySources must not be null");
//第三步,構建一個主要資源類的LinkedHashSet
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//第四步,判斷當前web應用的基本類型,判斷具體環境
this.webApplicationType = deduceWebApplicationType();
我們來詳細看下deduceWebApplicationType()這個方法:
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";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
這段代碼,就是推斷應用的類型 ,創建的是一個 SERVLET 應用還是 REACTIVE應用或者是 NONE。
這裏簡單看一下ClassUtils.isPresent這個方法
/**
* Determine whether the {@link Class} identified by the supplied name is present
* and can be loaded. Will return {@code false} if either the class or
* one of its dependencies is not present or cannot be loaded.
* @param className the name of the class to check
* @param classLoader the class loader to use
* (may be {@code null} which indicates the default class loader)
* @return whether the specified class is present
*/
public static boolean isPresent(String className, @Nullable ClassLoader classLoader){
try {
forName(className, classLoader);
return true;
}
catch (Throwable ex) {
// Class or one of its dependencies is not present...
return false;
}
}
/**
* Replacement for {@code Class.forName()} that also returns Class instances
* for primitives (e.g. "int") and array class names (e.g. "String[]").
* Furthermore, it is also capable of resolving inner class names in Java source
* style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State").
* @param name the name of the Class
* @param classLoader the class loader to use
* (may be {@code null}, which indicates the default class loader)
* @return a class instance for the supplied name
* @throws ClassNotFoundException if the class was not found
* @throws LinkageError if the class file could not be loaded
* @see Class#forName(String, boolean, ClassLoader)
*/
public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
//採用斷言確定類名不爲空
Assert.notNull(name, "Name must not be null");
Class<?> clazz = resolvePrimitiveClassName(name);
if (clazz == null) {
clazz = commonClassCache.get(name);
}
if (clazz != null) {
return clazz;
}
// "java.lang.String[]" style arrays
if (name.endsWith(ARRAY_SUFFIX)) {
String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
Class<?> elementClass = forName(elementClassName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
// "[Ljava.lang.String;" style arrays
if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
Class<?> elementClass = forName(elementName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
// "[[I" or "[[Ljava.lang.String;" style arrays
if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
Class<?> elementClass = forName(elementName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
ClassLoader clToUse = classLoader;
if (clToUse == null) {
clToUse = getDefaultClassLoader();
}
try {
return (clToUse != null ? clToUse.loadClass(name) : Class.forName(name));
}
catch (ClassNotFoundException ex) {
int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
if (lastDotIndex != -1) {
String innerClassName =
name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
try {
return (clToUse != null ? clToUse.loadClass(innerClassName) : Class.forName(innerClassName));
}
catch (ClassNotFoundException ex2) {
// Swallow - let original exception get through
}
}
throw ex;
}
}
這邊簡單談一下java的類加載機制:
java類加載過程可以分爲三個步驟:
- 加載
- 鏈接
- 初始化
類加載器就是來實現類加載的功能的。java爲我們提供了三種不同類型的類加載器,BootstrapClassLoader、ExtensionClassLoader和AppClassLoader,三種類加載器分別針對核心類、擴展類和classPath下以及我們自定義的jar進行加載。類加載器根據雙親委派模型進行工作,即當一個類被加載時,先交由父加載器進行,如果父加載器無法找到這個類,再交給子加載器進行加載。當然,我們可以自定義子加載器進行個性化加載。
————https://blog.csdn.net/qq_21399231/article/details/80751489
這邊再簡單說個概念:
對於類中靜態代碼塊的初始化工作,是在鏈接階段中的第二個小階段準備階段執行的。
假設你定義了一個靜態變量a:
private static int a = 10;
在鏈接階段,JVM在準備階段便將a設置爲他的初始值,也就是 0;
然後,在鏈接階段執行完成之後,在初始化階段,a才被設置爲 10;
關於具體的類加載機制,可以參見博客內其他文章。
上文 forName 方法中,我們可以看到這麼一塊:
ClassLoader clToUse = classLoader;
if (clToUse == null) {
clToUse = getDefaultClassLoader();
}
/**
* Return the default ClassLoader to use: typically the thread context
* ClassLoader, if available; the ClassLoader that loaded the ClassUtils
* class will be used as fallback.
* <p>Call this method if you intend to use the thread context ClassLoader
* in a scenario where you clearly prefer a non-null ClassLoader reference:
* for example, for class path resource loading (but not necessarily for
* {@code Class.forName}, which accepts a {@code null} ClassLoader
* reference as well).
* @return the default ClassLoader (only {@code null} if even the system
* ClassLoader isn't accessible)
* @see Thread#getContextClassLoader()
* @see ClassLoader#getSystemClassLoader()
*/
@Nullable
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
這就很明瞭了,如果輸入的類加載器爲空,那麼系統便從當前線程上下文中獲取默認的類加載器,如果不進行設置,那麼返回的便是默認的類加載器app ClassLoader()。
//第五步,初始化應用上下文
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
先來看下這個:
private List<ApplicationContextInitializer<?>> initializers;
/**
* Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
* {@link ApplicationContext}.
* @param initializers the initializers to set
*/
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
這裏面實例化了一個數組,用於存放 initializers
這邊便涉及到了一個 ApplicationContextInitializer
ApplicationContextInitializer 是一個接口,用於ConfigurableApplicationContext通過調用refresh函數來初始化Spring容器之前的回調函數,通常在web應用中,設計在初始化Spring容器之前調用。例如依賴於容器ConfigurableApplicationContext中的Enviroment來記錄一些配置信息或者使一些配置文件生效;
這裏邊涉及到了兩個東西:Spring Factories 和 SPI 原則 。
拋開這兩個東西先不管,我們繼續看 getSpringFactoriesInstances(ApplicationContextInitializer.class) 這一塊代碼。
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 classLoader = Thread.currentThread().getContextClassLoader();
//使用當前classloader 從類路徑下找到META-INF/spring.factories配置的所有ApplicationContextInitializer集合
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//創建相關實例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
@SuppressWarnings("unchecked")
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 {
//使用ClassUtils.forName 指定classLoader獲取對應class對象
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;
}
SpringFactoriesLoader工廠加載機制是Spring內部提供的一個約定俗成的加載方式,與java spi類似,只需要在模塊的META-INF/spring.factories文件,這個Properties格式的文件中的key是接口、註解、或抽象類的全名,value是以逗號 “ , “ 分隔的實現類,使用SpringFactoriesLoader來實現相應的實現類注入Spirng容器中。
這裏簡單說下Class.forName(className)和 ClassLoader.loadClass(className) 的區別:
Class.forName(className)方法,內部實際調用的方法是 Class.forName(className,true,classloader);
第2個boolean參數表示類是否需要初始化, Class.forName(className)默認是需要初始化。
一旦初始化,就會觸發目標對象的 static塊代碼執行,static參數也也會被再次初始化。
ClassLoader.loadClass(className)方法,內部實際調用的方法是 ClassLoader.loadClass(className,false);
第2個 boolean參數,表示目標對象是否進行鏈接,false表示不進行鏈接,由上面介紹可以,
不進行鏈接意味着不進行包括初始化等一些列步驟,那麼靜態塊和靜態對象就不會得到執行
//第六步,設置監聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
先看下這一段
private List<ApplicationListener<?>> listeners;
/**
* Sets the {@link ApplicationListener}s that will be applied to the SpringApplication
* and registered with the {@link ApplicationContext}.
* @param listeners the listeners to set
*/
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>();
this.listeners.addAll(listeners);
}
這一段代碼的意思就是構建一個 List<ApplicationListener<?>> listeners,這麼個東西。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
/**
* Class to be extended by all application events. Abstract as it
* doesn't make sense for generic events to be published directly.
*
* @author Rod Johnson
* @author Juergen Hoeller
*/
public abstract class ApplicationEvent extends EventObject {
/** use serialVersionUID from Spring 1.2 for interoperability */
private static final long serialVersionUID = 7099057708183571937L;
/** System time when the event happened */
private final long timestamp;
/**
* Create a new ApplicationEvent.
* @param source the object on which the event initially occurred (never {@code null})
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
/**
* Return the system time in milliseconds when the event happened.
*/
public final long getTimestamp() {
return this.timestamp;
}
}
ApplicationContext事件機制是觀察者設計模式的實現,通過ApplicationEvent類和ApplicationListener接口,可以實現ApplicationContext事件處理。
參見:https://www.jianshu.com/p/0269a8390d5f
//第七步,推斷主入口應用類
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;
}
在完成SpringApplication對象的構建和初始化之後,就開始執行run方法的邏輯了。具體源代碼如下文所示:
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//統計應用啓動時間
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//初始化spring跟容器
ConfigurableApplicationContext context = null;
//自定義SpringApplication啓動錯誤的回調接口
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//設置系統屬性
configureHeadlessProperty();
//創建所有 Spring 運行監聽器併發布應用啓動事件
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//裝配參數和環境
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//發佈ApplicationEnvironmentPreparedEvent
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
//創建ApplicationContext,並裝配
context = this.createApplicationContext();
this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
//發佈ApplicationPreparedEvent
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
//發佈ApplicationStartedEvent
listeners.started(context);
//執行Spring中@Bean下的一些操作,如靜態方法等
this.callRunners(context, applicationArguments);
} catch (Throwable var9) {
this.handleRunFailure(context, listeners, exceptionReporters, var9);
throw new IllegalStateException(var9);
}
//發佈ApplicationReadyEvent
listeners.running(context);
return context;
}
參考文章:https://blog.csdn.net/hzhahsz/article/details/83960139
先看下configureHeadlessProperty:
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private boolean headless = true;
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
這個方法的作用是配置了一個名爲java.awt.headless的系統屬性,具體功能就是讓應用程序沒有顯示器也能啓動。
SpringApplicationRunListeners listeners = getRunListeners(args);
這段代碼的作用是獲取監聽器,具體源代碼如下:
private static final Log logger = LogFactory.getLog(SpringApplication.class);
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
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;
}
可以看到,加載監聽器的基本步驟和上文獲取ApplicationListener的方法基本一樣,都是通過springFactoryies從META-INF/spring.factories 中獲取。
接下來便是listeners.starting了
private final List<SpringApplicationRunListener> listeners;
listeners.starting();
public void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
private final SpringApplication application;
private final String[] args;
@Override
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableTypeeventType) {
ResolvableType type = (eventType != null ? eventType :resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
詳情參見:http://www.mamicode.com/info-detail-2644218.html