Spring ApplicationListener 理解

 

在开发时有时候需要在整个应用开始运行时执行一些特定代码,比如初始化环境,准备测试数据、加载一些数据到内存等等。

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 }

 

posted @ 2019-01-08 14:45  panchanggui  阅读(499)  评论(0编辑  收藏  举报