文章目录
前言
在Spring应用服务启动时,需要提前加载一些数据和执行一些的初始化工作。例如:删除临时文件,清除缓存信息,读取配置文件,获取数据库连接,这些工作在开机启动之后只要执行一次就行了
0. ApplicationListener
容器刷新成功意味着所有的Bean初始化已经完成,当容器刷新之后Spring将会调用容器内所有实现了ApplicationListener<ContextRefreshedEvent>
的Bean的onApplicationEvent
方法,应用程序可以以此达到监听容器初始化完成事件
的目的。
@Component
@Slf4j
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
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
@Slf4j
public class StartupApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
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以最小成本实现一个观察者模式:
自定义一个事件:
@Data
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;
}
}
注册一个事件监听器
@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", "[email protected]", "This is the content");
webApplicationContext.publishEvent(event);
}
}
执行单元测试可以看到邮件的地址和内容都被打印出来了
1.ServletContextAware接口
实现ServletContextAware接口重写setServletContext方法
- 实现该接口的类可以
直接获取Web应用的ServletContext实例(Servler容器)
@Component
public class ServletContextAwareImpl implements ServletContextAware {
/**
* 在填充普通bean属性之后但在初始化之前调用
* 类似于initializing Bean的afterpropertiesset或自定义init方法的回调
*
*/
@Override
public void setServletContext(ServletContext servletContext) {
System.out.println("===============ServletContextAwareImpl初始化================");
}
}
2.ServletContextListener接口
实现ServletContextListener接口重写contextInitialized方法
- 它能够
监听 ServletContext 对象的生命周期
,即监听 Web 应用的生命周期。 - 当Servlet 容器启动或终止Web 应用时,会触发
ServletContextEvent 事件
,该事件由ServletContextListener
的2个方法两处理:- contextInitialized(ServletContextEvent sce) :当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化,并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。
- contextDestroyed(ServletContextEvent sce):当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。
@Component
public class ServletContextListenerImpl implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
//ServletContext servletContext = sce.getServletContext();
System.out.println("===============ServletContextListenerImpl初始化(contextInitialized)================");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("===============ServletContextListenerImpl(contextDestroyed)================");
}
}
3.@PostConstruct注解
- 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且
在构造函数之后,init()方法之前运行
。 - 使用 @PostConstruct 注解进行初始化操作的顺序是
最快的
,前提是这些操作不依赖其它Bean的初始化完成
。
@Component
public class PostConstructInit {
//静态代码块会在依赖注入后自动执行,并优先执行
static{
System.out.println("---static--");
}
/**
* @Postcontruct’在依赖注入完成后自动调用
*/
@PostConstruct
public static void haha(){
System.out.println("===============@Postcontruct初始化(在依赖注入完成后自动调用)================");
}
}
@PreDestroy
@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前。
4.CommandLineRunner接口
实现CommandLineRunner 接口重写run方法
- 可以接收
字符串数组
的命令行参数 - 在
构造SpringApplication实例完成之后调用
- 可以通过
Order注解
或者使用Ordered接口
来指定调用顺序,@Order()中的值越小,优先级越高
- 同时使用ApplicationRunner和CommandLineRunner,
默认情况下ApplicationRunner比CommandLineRunner先执行
@Component
public class CommandLineRunnerImpl implements CommandLineRunner {
/**
* 用于指示bean包含在SpringApplication中时应运行的接口。可以在同一应用程序上下文中定义多个commandlinerunner bean,并且可以使用有序接口或@order注释对其进行排序。
* 如果需要访问applicationArguments而不是原始字符串数组,请考虑使用applicationRunner。
*
*/
@Override
public void run(String... args) throws Exception {
System.out.println("===============CommandLineRunnerImpl初始化================");
}
}
5. ApplicationRunner接口
实现ApplicationRunner接口重写run方法
- ApplicationRunner 是使用
ApplicationArguments来获取参数的
- 在
构造SpringApplication实例完成之后调用run()的时候调用
- 可以通过
@Order
注解或者使用Ordered接口
来指定调用顺序
,@Order()
中的值越小,优先级越高
- 同时使用ApplicationRunner和CommandLineRunner,
默认情况下ApplicationRunner比CommandLineRunner先执行
- ApplicationRunner 和 CommandLineRunner 功能一致,唯一的区别主要体现在对参数的处理上,
ApplicationRunner 可以接收更多类型的参数
@Component
public class ApplicationRunnerImpl implements ApplicationRunner {
/**
* 用于指示bean包含在SpringApplication中时应运行的接口。可以定义多个applicationRunner Bean
* 在同一应用程序上下文中,可以使用有序接口或@order注释对其进行排序。
*/
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("===============ApplicationRunnerImpl初始化================");
}
}
- args.getSourceArgs():获取命令行中的所有参数。
- args.getNonOptionArgs():获取命令行中的无key参数(和CommandLineRunner一样)。
- args.getOptionNames():获取所有key/value形式的参数的key。
- args.getOptionValues(key)):根据key获取key/value形式的参数的value。
6. InitializingBean接口
在对象的所有属性被初始化后之后才会调用afterPropertiesSet()方法
public class InitializingBeanImpl implements InitializingBean {
@Override
public void afterPropertiesSet() {
System.out.println("===============InitializingBeanImpl初始化================");
}
}
7.SmartLifecycle接口
SmartLifecycle 不仅仅能在初始化后
执行一个逻辑,还能再关闭前
执行一个逻辑,并且也可以控制多个 SmartLifecycle 的执行顺序,就像这个类名表示的一样,这是一个智能的生命周期管理接口
。
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
/**
* SmartLifecycle测试
*
*
*
*/
@Component
public class TestSmartLifecycle implements SmartLifecycle {
private boolean isRunning = false;
/**
* 1. 我们主要在该方法中启动任务或者其他异步服务,比如开启MQ接收消息<br/>
* 2. 当上下文被刷新(所有对象已被实例化和初始化之后)时,将调用该方法,默认生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。
* 如果为“true”,则该方法会被调用,而不是等待显式调用自己的start()方法。
*/
@Override
public void start() {
System.out.println("start");
// 执行完其他业务后,可以修改 isRunning = true
isRunning = true;
}
/**
* 如果项目中有多个实现接口SmartLifecycle的类,则这些类的start的执行顺序按getPhase方法返回值从小到大执行。默认为 0<br/>
* 例如:1比2先执行,-1比0先执行。 stop方法的执行顺序则相反,getPhase返回值较大类的stop方法先被调用,小的后被调用。
*/
@Override
public int getPhase() {
// 默认为0
return 0;
}
/**
* 根据该方法的返回值决定是否执行start方法。默认为 false<br/>
* 返回true时start方法会被自动执行,返回false则不会。
*/
@Override
public boolean isAutoStartup() {
// 默认为false
return true;
}
/**
* 1. 只有该方法返回false时,start方法才会被执行。 默认返回 false <br/>
* 2. 只有该方法返回true时,stop(Runnable callback)或stop()方法才会被执行。
*/
@Override
public boolean isRunning() {
// 默认返回false
return isRunning;
}
/**
* SmartLifecycle子类的才有的方法,当isRunning方法返回true时,该方法才会被调用。
*/
@Override
public void stop(Runnable callback) {
System.out.println("stop(Runnable)");
// 如果你让isRunning返回true,需要执行stop这个方法,那么就不要忘记调用callback.run()。
// 否则在你程序退出时,Spring的DefaultLifecycleProcessor会认为你这个TestSmartLifecycle没有stop完成,程序会一直卡着结束不了,等待一定时间(默认超时时间30秒)后才会自动结束。
// PS:如果你想修改这个默认超时时间,可以按下面思路做,当然下面代码是springmvc配置文件形式的参考,在SpringBoot中自然不是配置xml来完成,这里只是提供一种思路。
// <bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
// <!-- timeout value in milliseconds -->
// <property name="timeoutPerShutdownPhase" value="10000"/>
// </bean>
callback.run();
isRunning = false;
}
/**
* 接口Lifecycle的子类的方法,只有非SmartLifecycle的子类才会执行该方法。<br/>
* 1. 该方法只对直接实现接口Lifecycle的类才起作用,对实现SmartLifecycle接口的类无效。<br/>
* 2. 方法stop()和方法stop(Runnable callback)的区别只在于,后者是SmartLifecycle子类的专属。
*/
@Override
public void stop() {
System.out.println("stop");
isRunning = false;
}
}
- start():bean 初始化完毕后,该方法会被执行。
- stop():容器关闭后,spring 容器发现当前对象实现了 SmartLifecycle,就调用 stop(Runnable), 如果只是实现了 Lifecycle,就调用 stop()。
- isRunning():当前状态,用来判你的断组件是否在运行。
- getPhase():控制多个 SmartLifecycle 的回调顺序的,返回值越小越靠前执行 start() 方法,越靠后执行 stop() 方法。
- isAutoStartup():start 方法被执行前先看此方法返回值,返回 false 就不执行 start 方法了。
- stop(Runnable):容器关闭后,spring 容器发现当前对象实现了 SmartLifecycle,就调用 stop(Runnable), 如果只是实现了 Lifecycle,就调用 stop()。
8.控制Bean初始化顺序
如果想要指定Spring容器加载Bean的顺序,可以通过实现org.springframework.core.Ordered接口
或者使用org.springframework.core.annotation.Order注解
来实现。
- order值越小,该初始化类也就越早执行
8.1.实现Ordered接口
/**
* Created by pangkunkun on 2017/9/3.
*/
@Component
public class MyApplicationRunner implements ApplicationRunner,Ordered{
@Override
public int getOrder(){
return 1;//通过设置这里的数字来知道指定顺序
}
@Override
public void run(ApplicationArguments var1) throws Exception{
System.out.println("MyApplicationRunner1!");
}
}
8.2.使用@Order注解
@Component
@Order(value = 1)
public class MyApplicationRunner implements ApplicationRunner{
@Override
public void run(ApplicationArguments var1) throws Exception{
System.out.println("MyApplicationRunner1!");
}
原始初始化方式
如果不依赖于 Spring 的实现,我们可以在静态代码块
,在类构造函数
中实现相应的逻辑
- Java 类的初始化顺序依次是
静态变量 > 静态代码块 > 全局变量 > 初始化代码块 > 构造方法
。