一. 入口
對於Springboot工程,我們只需要一行代碼調用就可以運行起來,如下:
public static void main(String[] args) {
SpringApplication.run(LajitongServerMain.class, args);
}
那接下來我們就要從這行代碼入手,分析其內部實現過程。
二. 源代碼分析
代碼位置:org.springframework.boot.SpringApplication
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);
}
上面代碼我們可以發現幾點:
- run方法其實是可以接受一個Class數組的
- 在run方法裏面,實例化了一個SpringApplication對象,然後調用它的run方法。
那接下來,我們就要分析這兩點,初始化SpringApplication對象,以及它的run方法實現。
2.1 實例化SpringApplication對象
@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 = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
這裏比較重要的一個是getSpringFactoriesInstances,這個方法是根據類型去讀取spring.factories配置文件裏面對應的配置項。
比如setInitializers裏面,傳入的類型是ApplicationContextInitializer.class。
那接下來,我們來分析這個讀取流程。
spring.factories是Spring提供給廠商自定義配置的文件,我們也可以定義我們自己的spring.factories文件,然後就會運行裏面的配置項。
比如在我們自己的項目裏面,添加一個spring.factories文件,裏面配置SpringApplicationRunListener除了系統的之外,我還加了MyEventPublishListener
在MyEventPublishListener裏面,只是打印了一下運行日誌
那我們的服務跑起來的時候,有如下日誌:
也就是說我們自定義的spring.factories配置是生效了的,我們在項目啓動的時候,做一些自定義配置,比如初始化資源。
2.1.1 獲取spring.factories配置
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 = getClassLoader();
// Use names and ensure unique to protect against duplicates
// 1. 獲取配置項
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 2. 初始化配置項
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 3. 排序,值越小,優先級越高
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
繼續往裏面看獲取配置項SpringFactoriesLoader.loadFactoryNames方法。
2.1.2 SpringFactoriesLoader.loadFactoryNames
SpringFactoriesLoader類定義是在Spring-core裏面,我們上一篇講過,Spring有3個核心組件,Spring-core, Spring-context, Spring-beans。他們的代碼在Spring-framework裏面。
於是,我們打開Spring-framework,找到其代碼,如下圖。
代碼如下;
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
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()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
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裏面去讀取factoryClass.getName()的配置,就像springboot裏面提供的配置一樣,ApplicationContextInitializer類型就是下面的配置,有4個配置項。
# 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.web.context.ServerPortInfoApplicationContextInitializer
所以上面setInitializers的時候,就是讀取這4個類型
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
然後實例化,存入SpringApplication裏面的成員列表裏面
private List<ApplicationContextInitializer<?>> initializers;
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
接下來還要設置listeners
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
2.1.3 獲取ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>();
this.listeners.addAll(listeners);
}
getSpringFactoriesInstance的實現上面已經解釋過了,在springboot的默認配置是這樣的。
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
所以listeners裏面存儲的是EventPublishingRunListener對象。
/**
* {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
* <p>
* Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
* before the context is actually refreshed.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Artsiom Yudovin
*/
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
}
EventPublishingRunListener從註釋上面來說,是使用ApplicationEventMulticaster來發布SpringApplicationEvent事件。其實EventPublishingRunListener其實就是個事件中轉站,它利用ApplicationEventMulticater來轉發事件。
ApplicationEventMulticaster是Spring-context裏面提供的一種機制,它可以根據傳入的事件類型找到相應的監聽者,這個和安卓的EventBus效果類似。
這個事件分發後面篇幅我們再說。
回到SpringApplication實例化的構造方法,最後一個行,獲取mainApplicationClass
2.1.4 獲取mainApplicationClass
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方法。
2.1.5 自定義項目的spring.factories文件
上面說了那麼多spring.factories文件的東西,那麼如果我們自己項目中想使用spring.factories文件,是定義到哪個文件夾位置呢?
其實需要是放在src/main/resources/META-INF目錄下面,如果沒有這個目錄,請自己創建文件夾,如下圖所示:
2.2 SpringApplication.run
根據註釋,它是運行一個Spring application,創建和刷新一個新的ApplicationContext。代碼有點長,慢慢看。
/**
* 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) {
//1. 啓動計時器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//2. 設置java.awt.headless系統屬性爲true,沒有圖形界面,適合於服務器程序開發
configureHeadlessProperty();
//3. 獲取spring.factories配置文件中的運行listener,一般是EventPublishingRunListener,作爲事件中轉
SpringApplicationRunListeners listeners = getRunListeners(args);
//4. 通知開始starting,發出starting事件給觀察者.
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
2.2.1 啓動計時器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
StopWatch是Spring-core裏面提供的功能,可以用來統計總共運行時間
/**
* Simple stop watch, allowing for timing of a number of tasks,
* exposing total running time and running time for each named task.
*
* <p>Conceals use of {@code System.currentTimeMillis()}, improving the
* readability of application code and reducing the likelihood of calculation errors.
*
* <p>Note that this object is not designed to be thread-safe and does not
* use synchronization.
*
* <p>This class is normally used to verify performance during proof-of-concepts
* and in development, rather than as part of production applications.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
* @since May 2, 2001
*/
public class StopWatch {
...
public void start(String taskName) throws IllegalStateException {
if(this.running) {
throw new IllegalStateException("Can't start StopWatch: it's already running");
} else {
this.running = true;
this.currentTaskName = taskName;
this.startTimeMillis = System.currentTimeMillis();
}
}
....
}
記錄開始時間 this.startTimeMillis = System.currentTimeMillis(); 這個startTimeMillis值待會在stopWatch.stop()時會使用。
2.2.2 設置成服務器模式
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
configureHeadlessProperty();
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
一般是服務器模式下設置java.awt.headless=true,表示可能缺少鍵盤等支持。
2.2.3 獲取Run Listener
SpringApplicationRunListeners listeners = getRunListeners(args);
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
getSpringFactoriesInstances在上面已經分析過了,可以根據傳入的class type獲取到spring.factories配置的類。而對於SpringApplicationRunListeners,一般配置是這樣的
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener,\
所以實例化後就是EventPublishingRunListener對象,EventPublishingRunListener是一個事件中間轉發類,利用一個SimpleApplicationEventMulticaster轉發各種事件。
大概如下:
/**
* {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
* <p>
* Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
* before the context is actually refreshed.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Artsiom Yudovin
*/
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(
this.application, this.args, context));
}
...
}
SimpleApplicationEventMulticaster是Spring-context提供的一種消息傳遞機制,這個具體可以再分析。
2.2.4 發送starting消息
listeners.starting();
@Override
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
listeners 的值如下
其中MyEventPublishListener是我們在spring.factories配置的
而EventPublishingRunListener是SpringBoot裏面spring.factories配置的。
所以會執行MyEventPublishListener和EventPublishingRunListener的starting方法。
2.2.5 準備環境
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
可以根據不同的項目類型創建不同的Environment,關於Environment具體可以參考下面鏈接
https://www.jb51.net/article/145192.htm
2.2.6 打印Banner
Banner printedBanner = printBanner(environment);
相信大家對Springboot啓動的圖形不會陌生,類似於下圖:
那麼這個圖形就是從這裏打印出來的。
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null)
? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
printBanner會構造一個SpringApplicationBannerPrinter對象,然後根據不同的模式(this.bannerMode == Mode.LOG),通過logger或者System.out打印。
所以主要看SpringApplicationBannerPrinter的print做了什麼。
public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
Banner banner = getBanner(environment);
banner.printBanner(environment, sourceClass, out);
return new PrintedBanner(banner, sourceClass);
}
主要看getBanner方法
private Banner getBanner(Environment environment) {
Banners banners = new Banners();
banners.addIfNotNull(getImageBanner(environment));
banners.addIfNotNull(getTextBanner(environment));
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
return DEFAULT_BANNER;
}
這裏springboot支持自定義的啓動圖形,getImageBanner和getTextBanner。 如果沒有定義,那麼就嘗試尋找fallbackBanner,這個fallbackBanner可以通過SpringApplication設置, 一般我們都不會設置。
所以默認都會返回DEFAULT_BANNER,DEFAULT_BANNER的定義如下:
private static final Banner DEFAULT_BANNER = new SpringBootBanner();
class SpringBootBanner implements Banner {
private static final String[] BANNER = { "",
" . ____ _ __ _ _",
" /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\",
"( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
" \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )",
" ' |____| .__|_| |_|_| |_\\__, | / / / /",
" =========|_|==============|___/=/_/_/_/" };
private static final String SPRING_BOOT = " :: Spring Boot :: ";
private static final int STRAP_LINE_SIZE = 42;
@Override
public void printBanner(Environment environment, Class<?> sourceClass,
PrintStream printStream) {
for (String line : BANNER) {
printStream.println(line);
}
String version = SpringBootVersion.getVersion();
version = (version != null) ? " (v" + version + ")" : "";
StringBuilder padding = new StringBuilder();
while (padding.length() < STRAP_LINE_SIZE
- (version.length() + SPRING_BOOT.length())) {
padding.append(" ");
}
printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,
AnsiColor.DEFAULT, padding.toString(), AnsiStyle.FAINT, version));
printStream.println();
}
}
上面String[] BANNER對象是不是很熟悉呢?嗯,就是我們命令行裏面打印輸出的那個了。
得到SpringBootBanner對象後,通過調用printBanner方法打印出來,所以我們就看到命令行的輸出
banner.printBanner(environment, sourceClass, out);
2.2.7 StopWatch.stop()
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
...
try {
....
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
之前分析stopWatch.start()裏面是記錄了啓動開始時間,那麼stopWatch.stop()是做什麼呢?代碼如下:
public void stop() throws IllegalStateException {
if(!this.running) {
throw new IllegalStateException("Can't stop StopWatch: it's not running");
} else {
long lastTime = System.currentTimeMillis() - this.startTimeMillis;
this.totalTimeMillis += lastTime;
this.lastTaskInfo = new StopWatch.TaskInfo(this.currentTaskName, lastTime);
if(this.keepTaskList) {
this.taskList.add(this.lastTaskInfo);
}
++this.taskCount;
this.running = false;
this.currentTaskName = null;
}
}
這裏面記錄了程序啓動時間,也就是從stopWatch.start()到stop的時間-totalTimeMillis
接下來判斷是否需要打印啓動時間
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
如果需要打印,那麼調用logStarted打印信息,如下:
public void logStarted(Log log, StopWatch stopWatch) {
if (log.isInfoEnabled()) {
log.info(getStartedMessage(stopWatch));
}
}
private StringBuilder getStartedMessage(StopWatch stopWatch) {
StringBuilder message = new StringBuilder();
message.append("Started ");
message.append(getApplicationName());
message.append(" in ");
message.append(stopWatch.getTotalTimeSeconds());
try {
double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
message.append(" seconds (JVM running for " + uptime + ")");
}
catch (Throwable ex) {
// No JVM time available
}
return message;
}
獲取的打印信息getStartedMessage,也就是如下圖所示: