在开发时有时候需要在整个应用开始运行时执行一些特定代码,比如初始化环境,准备测试数据、加载一些数据到内存等等。
在spring中可以通过ApplicationListener来实现相关的功能,加载完成后触发contextrefreshedevent事件(上下文件刷新事件)。
ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理。
如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。这种事件机制都必须需要程序显示的触发。
其中spring有一些内置的事件,当完成某种操作时会发出某些事件动作。比如监听ContextRefreshedEvent事件,当所有的bean都初始化完成并被成功装载后会触发该事件,实现ApplicationListener<ContextRefreshedEvent>接口可以收到监听动作,然后可以写自己的逻辑。
同样事件可以自定义、监听也可以自定义,完全根据自己的业务逻辑来处理。
内置事件
序号 Spring 内置事件 & 描述
1 ContextRefreshedEvent
ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext接口中使用 refresh() 方法来发生。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用。
2 ContextStartedEvent
当使用 ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。
3 ContextStoppedEvent
当使用 ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。
4 ContextClosedEvent
当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。
5 RequestHandledEvent
这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件。
业务方监听事件举例
比如要监听ContextRefreshedEvent的时可以实现ApplicationListener接口,并且传入要监听的事件
1 @Component 2 public class TestApplicationListener implements ApplicationListener<ContextRefreshedEvent>{ 3 @Override 4 public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { 5 System.out.println(contextRefreshedEvent); 6 System.out.println("TestApplicationListener............................"); 7 } 8 }
自定义事件
可以自定义事件,然后做完业务处理后手动发出。同上集成某个监听接口,接收到事件后进行业务处理
事件定义:
1 public class EmailEvent extends ApplicationEvent{ 2 private String address; 3 private String text; 4 public EmailEvent(Object source, String address, String text){ 5 super(source); 6 this.address = address; 7 this.text = text; 8 } 9 public EmailEvent(Object source) { 10 super(source); 11 } 12 //......address和text的setter、getter 13 }
监听定义
1 public class EmailNotifier implements ApplicationListener{ 2 public void onApplicationEvent(ApplicationEvent event) { 3 if (event instanceof EmailEvent) { 4 EmailEvent emailEvent = (EmailEvent)event; 5 System.out.println("邮件地址:" + emailEvent.getAddress()); 6 System.our.println("邮件内容:" + emailEvent.getText()); 7 } else { 8 System.our.println("容器本身事件:" + event); 9 } 10 } 11 }
业务触发
1 public class SpringTest { 2 public static void main(String args[]){ 3 ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); 4 //创建一个ApplicationEvent对象 5 EmailEvent event = new EmailEvent("hello","abc@163.com","This is a test"); 6 //主动触发该事件 7 context.publishEvent(event); 8 } 9 }
不管是内置监听还是外部自定义监听一定要把实现ApplicationListener的类定义成一个bean才行,可以是通过注解@Component等也可以通过xml的方式去执行。
但是这个时候,会存在一个问题,在web 项目中(spring mvc),系统会存在两个容器,一个是root application context ,另一个就是我们自己的 projectName-servlet context(作为root application context的子容器)。
这种情况下,就会造成onApplicationEvent方法被执行两次。为了避免上面提到的问题,我们可以只在root application context初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理,修改后代码。event.getApplicationContext().getDisplayName().equals("Root WebApplicationContext") 。
如下:(要把实现ApplicationListener的类定义成一个bean)
1 @Component 2 public class SpringStartedListener implements ApplicationListener<ContextRefreshedEvent> { 3 4 private static final Logger logger = LoggerFactory.getLogger(SpringStartedListener.class); 5 private static final String CONTEXT_DISPLAY_NAME = "WebApplicationContext"; 6 7 @Autowired 8 private ThreadPoolTaskExecutor threadPoolTaskExecutor; 9 10 @Override 11 public void onApplicationEvent(ContextRefreshedEvent event) { 12 13 if (event.getApplicationContext().getDisplayName().indexOf(CONTEXT_DISPLAY_NAME) > -1) { 14 ConfigConstants.initConfig(); // 初始化配置 15 threadPoolTaskExecutor.submit(() -> { 16 //在这里启动netty 17 Server server = NettyServer.getInstance(); 18 try { 19 logger.debug("NettyServer Starting..."); 20 server.start(); 21 } catch (InterruptedException e) { 22 logger.error("init NettyServer err:{}", e.getMessage(), e); 23 } 24 }); 25 } 26 } 27 }