Xc's Blog

为SpringBoot的启动过程埋点

2018/12/10 Share

简而言之,继承org.springframework.boot.SpringApplicationRunListener,并且让Spring加载进来。

为什么要为SpringBoot的启动过程埋点

因为SpringBoot启动过慢,需要找出在Spring的哪一步遇到了性能问题,然后进行专项优化。

为什么不能用Spring的EventListener来处理

EventListener的一种用法如下:

1
2
3
4
@EventListener
public void handleEventObject(Object event) {
System.out.println("event: "+event.getClass());
}

如果可以用,那EventHandler是侵入性很低,工作量又小的一种方式,但是EventListener只有在Spring容器初始化完毕后才会收到消息,无法胜任监听

SpringApplicationRunListeners VS SpringApplicationRunListener

SpringApplicationRunListener是Spring内部的一个接口,方法有(入参和出参省略):

  • starting()

  • environmentPrepared()

  • contextPrepared()

  • contextLoaded()

  • void started()

  • void running()

  • void failed()

简单看一眼这些方法名就知道,这些都像是在Spring容器初始化的过程会调用到的回调函数,如果我们可以自己定义SpringApplicationRunListener并让Spring加载,就可以做到为SpringBoot的启动埋点。但翻遍文档后发现,Spring并未为开发者提供SpringApplicationRunListener的配置入口,于是便看了看SpringBoot的部分源码。

Spring 何时加载 SpringApplicationRunListener

关键代码 new SpringApplication(primarySources).run(args);:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 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) {
//...
//Spring加载了SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
//回调starting方法
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//prepareEnvironment 会将listeners 传入,并在过程中调用listeners的回调函数
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//prepareContext 会将listeners 传入,并在过程中调用listeners的回调函数
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//回调started方法
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//异常时执行对应的回调
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
//回调running
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

简单阅读启动代码可知,listener的加载是在Spring读取配置文件之前的,因此Spring在配置文件中配置Listenr的难度很高,没有将这个接口暴露给用户也是正常的。如果我们需要自定义listener,需要研究getRunListeners这个方法

getRunListeners的实现

1
2
3
4
5
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}

SpringApplicationRunListeners 是 SpringApplicationRunListener的管理类,负责统一调用多个listener的回调方法。真正生成listener对象的是getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)这段代码。

getSpringFactoriesInstances的相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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
// 找到所有实现了type类的类名
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 将类名对应的类初始化,并且作为list返回
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

SpringFactoriesLoader容易被误解是Spring的某个工厂类的Util方法,实际阅读下来后发现,其实这个方法其实是和工厂方法无关的,loadFactoryNames方法会从META-INF/spring.factories文件中读取配置,并返回所有的对应的接口的实现类。这次我们需要的接口是SpringApplicationRunListener,那我们就需要在文件中写org.springframework.boot.SpringApplicationRunListener=com.mine.SpringApplicationRunListener

CATALOG
  1. 1. 为什么要为SpringBoot的启动过程埋点
  2. 2. 为什么不能用Spring的EventListener来处理
  3. 3. SpringApplicationRunListeners VS SpringApplicationRunListener
  4. 4. Spring 何时加载 SpringApplicationRunListener
  5. 5. getRunListeners的实现