七种方式教你在Spring Boot初始化时搞点事情
Java后端技术
共 11204字,需浏览 23分钟
·
2021-04-20 19:13
往期热门文章:
1、《往期精选优秀博文都在这里了!》 2、ConcurrentHashMap有十个提升性能的地方,你都知道吗? 3、Java 中的 Switch 都支持 String 了,为什么不支持 long? 4、为什么数据库字段要使用NOT NULL? 5、CTO 说了,用错 @Autowired 和 @Resource 的人可以领盒饭了
SpringBoot
留给开发者的7个启动扩展点。容器刷新完成扩展点
1、监听容器刷新完成扩展点ApplicationListener<ContextRefreshedEvent>
基本用法
Spring
的同学一定知道,容器刷新成功意味着所有的Bean
初始化已经完成,当容器刷新之后Spring
将会调用容器内所有实现了ApplicationListener<ContextRefreshedEvent>
的Bean
的onApplicationEvent
方法,应用程序可以以此达到监听容器初始化完成事件的目的。@Component
public class StartupApplicationListenerExample implements
ApplicationListener<ContextRefreshedEvent> {
private static final Logger LOG
= Logger.getLogger(StartupApplicationListenerExample.class);
public static int counter;
@Override public void onApplicationEvent(ContextRefreshedEvent event) {
LOG.info("Increment counter");
counter++;
}
}
易错的点
web
容器中的时候需要额外注意,在web 项目中(例如spring mvc
),系统会存在两个容器,一个是root application context
,另一个就是我们自己的context
(作为root application context
的子容器)。如果按照上面这种写法,就会造成onApplicationEvent
方法被执行两次。解决此问题的方法如下:@Component
public class StartupApplicationListenerExample implements
ApplicationListener<ContextRefreshedEvent> {
private static final Logger LOG
= Logger.getLogger(StartupApplicationListenerExample.class);
public static int counter;
@Override public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext().getParent() == null) {
// root application context 没有parent
LOG.info("Increment counter");
counter++;
}
}
}
高阶玩法
Spring
以最小成本实现一个观察者模式:先自定义一个事件:
public class NotifyEvent extends ApplicationEvent {
private String email;
private String content;
public NotifyEvent(Object source) {
super(source);
}
public NotifyEvent(Object source, String email, String content) {
super(source);
this.email = email;
this.content = content;
}
// 省略getter/setter方法
}
注册一个事件监听器
@Component
public class NotifyListener implements ApplicationListener<NotifyEvent> {
@Override
public void onApplicationEvent(NotifyEvent event) {
System.out.println("邮件地址:" + event.getEmail());
System.out.println("邮件内容:" + event.getContent());
}
}
发布事件
@RunWith(SpringRunner.class)
@SpringBootTest
public class ListenerTest {
@Autowired
private WebApplicationContext webApplicationContext;
@Test
public void testListener() {
NotifyEvent event = new NotifyEvent("object", "abc@qq.com", "This is the content");
webApplicationContext.publishEvent(event);
}
}
执行单元测试可以看到邮件的地址和内容都被打印出来了
2、SpringBoot
的CommandLineRunner
接口
SpringBoot
也会调用所有实现了CommandLineRunner
接口的run
方法,下面这段代码可起到和上文同样的作用:@Component
public class CommandLineAppStartupRunner implements CommandLineRunner {
private static final Logger LOG =
LoggerFactory.getLogger(CommandLineAppStartupRunner.class);
public static int counter;
@Override
public void run(String...args) throws Exception {
LOG.info("Increment counter");
counter++;
}
}
多个实现了 CommandLineRunner
的Bean
的执行顺序可以根据Bean
上的@Order
注解调整其 run
方法可以接受从控制台输入的参数,跟ApplicationListener<ContextRefreshedEvent>
这种扩展相比,更加灵活
// 从控制台输入参数示例
java -jar CommandLineAppStartupRunner.jar abc abcd
3、SpringBoot
的ApplicationRunner
接口
SpringBoot
的CommandLineRunner
接口的扩展类似,只不过接受的参数是一个ApplicationArguments
类,对控制台输入的参数提供了更好的封装,以--
开头的被视为带选项的参数,否则是普通的参数@Component
public class AppStartupRunner implements ApplicationRunner {
private static final Logger LOG =
LoggerFactory.getLogger(AppStartupRunner.class);
public static int counter;
@Override
public void run(ApplicationArguments args) throws Exception {
LOG.info("Application started with option names : {}",
args.getOptionNames());
LOG.info("Increment counter");
counter++;
}
}
java -jar CommandLineAppStartupRunner.jar abc abcd --autho=mark verbose
Bean
初始化完成扩展点
Bean
初始化完成之后立刻注册监听器,而不是等到整个容器刷新完成,Spring
针对这种场景同样留足了扩展点:1、@PostConstruct
注解
@PostConstruct
注解一般放在Bean
的方法上,被@PostConstruct
修饰的方法会在Bean
初始化后马上调用:@Component
public class PostConstructExampleBean {
private static final Logger LOG
= Logger.getLogger(PostConstructExampleBean.class);
@Autowired
private Environment environment;
@PostConstruct
public void init() {
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}
2、 InitializingBean
接口
InitializingBean
的用法基本上与@PostConstruct
一致,只不过相应的Bean
需要实现afterPropertiesSet
方法@Component
public class InitializingBeanExampleBean implements InitializingBean {
private static final Logger LOG
= Logger.getLogger(InitializingBeanExampleBean.class);
@Autowired
private Environment environment;
@Override
public void afterPropertiesSet() throws Exception {
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}
3、@Bean
注解的初始化方法
@Bean
注入Bean
的时候可以指定初始化方法:Bean
的定义public class InitMethodExampleBean {
private static final Logger LOG = Logger.getLogger(InitMethodExampleBean.class);
@Autowired
private Environment environment;
public void init() {
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}
Bean
注入@Bean(initMethod="init")
public InitMethodExampleBean initMethodExampleBean() {
return new InitMethodExampleBean();
}
4、通过构造函数注入
Spring
也支持通过构造函数注入,我们可以把搞事情的代码写在构造函数中,同样能达到目的@Component
public class LogicInConstructorExampleBean {
private static final Logger LOG
= Logger.getLogger(LogicInConstructorExampleBean.class);
private final Environment environment;
@Autowired
public LogicInConstructorExampleBean(Environment environment) {
this.environment = environment;
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}
Bean
初始化完成扩展点执行顺序?
@Component
@Scope(value = "prototype")
public class AllStrategiesExampleBean implements InitializingBean {
private static final Logger LOG
= Logger.getLogger(AllStrategiesExampleBean.class);
public AllStrategiesExampleBean() {
LOG.info("Constructor");
}
@Override
public void afterPropertiesSet() throws Exception {
LOG.info("InitializingBean");
}
@PostConstruct
public void postConstruct() {
LOG.info("PostConstruct");
}
public void init() {
LOG.info("init-method");
}
}
Bean
后输出:[main] INFO o.b.startup.AllStrategiesExampleBean - Constructor
[main] INFO o.b.startup.AllStrategiesExampleBean - PostConstruct
[main] INFO o.b.startup.AllStrategiesExampleBean - InitializingBean
[main] INFO o.b.startup.AllStrategiesExampleBean - init-method
往期热门文章:
1、《历史文章分类导读列表!精选优秀博文都在这里了!》
2、程序员离职事件始末 3、别总写代码,这130个网站比涨工资都重要 4、程序员养生指北 5、如何解决MySQL order by limit语句的分页数据重复问题? 6、Java中八个潜在的内存泄露风险,你知道几个? 7、一个牛逼的 多级缓存 实现方案! 8、阿里一面:如何保障消息100%投递成功、消息幂等性? 9、GitHub 热榜:被网友疯狂恶搞的「蚂蚁呀嘿」项目终于开源了! 10、记住!看小电影前一定要检查一下域名是不是 HTTPS 的,不然....
评论