我们经常需要在容器启动的时候做一些hook动作,比如注册消息消费者,监听配置等,今天我们就来总结一下SpringBoot给开发者留下的7个启动扩展点。容器刷新完成扩展点1.监控容器刷新完成扩展点ApplicationListener基本用法熟悉Spring的同学一定知道,容器刷新成功意味着所有Bean初始化完成。当容器刷新时,Spring会调用容器中所有实现了ApplicationListener的Bean的onApplicationEvent方法,应用程序可以借此达到监听容器初始化完成事件的目的。@ComponentpublicclassStartupApplicationListenerExampleimplementsApplicationListener{privatestaticfinalLoggerLOG=Logger.getLogger(StartupApplicationListenerExample.class);publicstaticintcounter;@OverridepublicvoidonApplicationEvent(ContextRefreshedEventevent){LOG.info("Incrementcounter");counter++;}}容易出错的地方需要格外注意的时候,在一个web项目中(比如springmvc),系统中会有两个容器,一个是根应用上下文,一个是我们自己的上下文(作为子容器的子容器)根应用程序上下文)。如果按照上面的写法,会导致onApplicationEvent方法执行两次。解决这个问题的方法如下:@ComponentpublicclassStartupApplicationListenerExampleimplementsApplicationListener{privatestaticfinalLoggerLOG=Logger.getLogger(StartupApplicationListenerExample.class);publicstaticintcounter;@OverridepublicvoidonApplicationEvent(ContextRefreshedEventevent){if(event.getApplicationContext()).getParent/()==rootapplicationcontextdoesnothaveparentLOG.info("Incrementcounter");counter++;}}}高阶玩法当然这个扩展也可以有更高阶的玩法:自定义事件,可以用Spring实现一个最小的观察者模式成本:首先自定义一个事件:publicclassNotifyEventtextendsApplicationEvent{privateStringemail;privateStringcontent;publicNotifyEvent(Objectsource){super(source);}publicNotifyEvent(Objectsource,Stringemail,Stringcontent){super(source);this.email=email;this.content=content;}//省略getter/setter方法}注册一个事件监听器@ComponentpublicclassNotifyListenerimplementsApplicationListener{@OverridepublicvoidonApplicationEvent(NotifyEventevent){System.out.println("邮件地址:"+event.getEmail());System.out.println("邮件内容:"+event.getContent());}}发布事件@RunWith(SpringRunner.class)@SpringBootTestpublicclassListenerTest{@AutowiredprivateWebApplicationContextwebApplicationContext;@TestpublicvoidtestListener(){NotifyEventevent=newNotifyEvent("object","abc@qq.com","Thisisthecontent");webApplicationContext.publishEvent(event);}}执行单元测试,可以看到打印出了邮件地址和内容2.CommandLineRunner接口SpringBoot的作为容器在上下文初始化完成后,SpringBoot也会调用所有实现了CommandLineRunner接口的run方法。下面的代码可以起到和上面一样的作用:@ComponentpublicclassCommandLineAppStartupRunnerimplementsCommandLineRunner{privatestaticfinalLoggerLOG=LoggerFactory.getLogger(CommandLineAppStartupRunner.class);publicstaticintcounter;@Overridepublicvoidrun(String...args)throwsException{LOG.info("Incrementcounter");counter++;}使用这个扩展点还有两个需要注意的地方:实现CommandLineRunner的多个bean的执行顺序可以根据Bean的@Order注解调整其run方法接受控制台输入的参数,用ApplicationListener<上下文刷新事件>;和这个扩展相比,更灵活//从控制台输入参数的例子java-jarCommandLineAppStartupRunner.jarabcabcd3,SpringBoot的ApplicationRunner接口这个扩展和SpringBoot的CommandLineRunner接口的扩展类似,只是接受的参数是一个ApplicationArguments类,为控制平台输入的参数提供了更好的封装。以--开头的视为带选项的参数,否则为普通参数。throwsException{LOG.info("Applicationstartedwithoptionnames:{}",args.getOptionNames());LOG.info("Incrementcounter");counter++;}}例如:java-jarCommandLineAppStartupRunner.jarabcabcd--autho=markverboseBean初始化完成前扩展点内容总结了容器初始化的扩展点。在某些场景下,比如监听消息的时候,我们希望在Bean初始化完成后立即注册监听器,而不是等待整个容器刷新。对于这种场景,Spring也有足够的扩展。要点:1、@PostConstruct注解@PostConstruct注解一般放在Bean方法上,被@PostConstruct修饰的方法会在Bean初始化后立即调用:@ComponentpublicclassPostConstructExampleBean{privatestaticfinalLoggerLOG=Logger.getLogger(PostConstructExampleBean.class);@金towiredprivateEnvironmentenvironment;@PostConstructpublicvoidinit(){LOG.info(Arrays.asList(environment.getDefaultProfiles()));}}2.InitializingBean接口InitializingBean的用法与@PostConstruct基本相同,只是需要实现对应的BeanafterPropertiesInpleitleSet方法@ComponentpublicanclassInitializingBeanmentExampiz{privatestaticfinalLoggerLOG=Logger.getLogger(InitializingBeanExampleBean.class);@AutowiredprivateEnvironmentenvironment;@OverridepublicvoidafterPropertiesSet()throwsException{LOG.info(Arrays.asList(environment.getDefaultProfiles()));}}3.初始化方法@Bean注解通过@Bean注入Bean时,可以指定初始化方法:Bean定义getDefaultProfiles()));}}Bean注入@Bean(initMethod="init")publicInitMethodExampleBeaninitMethodExampleBean(){returnnewInitMethodExampleBean();}4.通过构造函数注入Spring也支持通过构造函数注入,我们你可以在构造函数中编写做事的代码,这样也可以达到目的(environment.getDefaultProfiles()));}}Bean初始化完成扩展点执行顺序?可以使用一个简单的测试:@Component@Scope(value="prototype")info("InitializingBean");}@PostConstructpublicvoidpostConstruct(){LOG.info("PostConstruct");}publicvoidinit(){LOG.info("init-method");}}实例化这个Bean后的输出:[main]INFOo.b.startup.AllStrategiesExampleBean-Constructor[main]INFOo.b.startup.AllStrategiesesExampleBean-PostConstruct[main]INFOo.b.startup.AllStrategiesExampleBean-InitializingBean[main]INFOo.b.startup.AllStrategiesExampleBean-初始化方法