简而言之,继承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
|
public ConfigurableApplicationContext run(String... args) { SpringApplicationRunListeners listeners = getRunListeners(args); 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; }
|
简单阅读启动代码可知,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(); Set<String> names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); 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