一. 入口
对于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,也就是如下图所示: