spring扩展点之三:Spring 的监听事件 ApplicationListener 和 ApplicationEvent 用法,在spring启动后作些事情

spring扩展点之三:Spring 的监听事件 ApplicationListener 和 ApplicationEvent 用法,在spring启动后作些事情html

服务网关zuul之七:zuul中的动态刷新路由配置java

 《观察者模式与监听模式web

 《JDK自带的观察者模式spring

JDK自带的监听器模式sql

ApplicationEvent事件机制源码分析设计模式

背景

在开发工做中,用到spring cloud的zuul,zuul中的动态刷新zuul的路由信息中用到了事件监听,事件监听也是设计模式中 发布-订阅模式、观察者模式的一种实现。缓存

在spring-cloud-netflix-core-1.4.4.RELEASE.jar中org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent.javamvc

package org.springframework.cloud.netflix.zuul;

import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.ApplicationEvent;

/**
 * @author Dave Syer
 */
@SuppressWarnings("serial")
public class RoutesRefreshedEvent extends ApplicationEvent {

    private RouteLocator locator;

    public RoutesRefreshedEvent(RouteLocator locator) {
        super(locator);
        this.locator = locator;
    }

    public RouteLocator getLocator() {
        return this.locator;
    }

}

观察者模式:简单的来说就是你在作事情的时候身边有人在盯着你,当你作的某一件事情是旁边观察的人感兴趣的事情的时候,他会根据这个事情作一些其余的事,可是盯着你看的人必需要到你这里来登记,不然你没法通知到他(或者说他没有资格来盯着你作事情)。app

正文

要想顺利的建立监听器,并起做用,这个过程当中须要这样几个角色:
一、事件(event)能够封装和传递监听器中要处理的参数,如对象或字符串,并做为监听器中监听的目标。
二、监听器(listener)具体根据事件发生的业务处理模块,这里能够接收处理事件中封装的对象或字符串。
三、事件发布者(publisher)事件发生的触发者。异步

在Spring中的,若是一个Bean实现了ApplicationListener接口,而且已经发布到容器中去,每次ApplicationContext发布一个ApplicationEvent事件,这个Bean就会接到通知。Spring事件机制是观察者模式的实现。

Spring中提供的标准事件:

  • ContextRefreshEvent,当ApplicationContext容器初始化完成或者被刷新的时候,就会发布该事件。好比调用ConfigurableApplicationContext接口中的refresh()方法。此处的容器初始化指的是全部的Bean都被成功装载,后处理(post-processor)Bean被检测到而且激活,全部单例Bean都被预实例化,ApplicationContext容器已经可使用。只要上下文没有被关闭,刷新能够被屡次触发。XMLWebApplicationContext支持热刷新,GenericApplicationContext不支持热刷新。

  • ContextStartedEvent,当ApplicationContext启动的时候发布事件,即调用ConfigurableApplicationContext接口的start方法的时候。这里的启动是指,全部的被容器管理生命周期的Bean接受到一个明确的启动信号。在常常须要中止后从新启动的场合比较适用。

  • ContextStoppedEvent,当ApplicationContext容器中止的时候发布事件,即调用ConfigurableApplicationContext的close方法的时候。这里的中止是指,全部被容器管理生命周期的Bean接到一个明确的中止信号。

  • ContextClosedEvent,当ApplicationContext关闭的时候发布事件,即调用ConfigurableApplicationContext的close方法的时候,关闭指的是全部的单例Bean都被销毁。关闭上下后,不能从新刷新或者从新启动。

  • RequestHandledEvent,只能用于DispatcherServlet的web应用,Spring处理用户请求结束后,系统会触发该事件。

实现

ApplicationEvent,容器事件,必须被ApplicationContext发布。

ApplicationListener,监听器,可由容器中任何监听器Bean担任。

实现了ApplicationListener接口以后,须要实现方法onApplicationEvent(),在容器将全部的Bean都初始化完成以后,就会执行该方法。

观察者模式

观察者模式,Observer Pattern也叫做发布订阅模式Publish/Subscribe。定义对象间一对多的依赖关系,使得每当一个对象改变状态,则全部依赖与它的对象都会获得通知,并被自动更新。

观察者模式的几角色名称:

  • Subject被观察者,定义被观察者必须实现的职责,它能动态的增长取消观察者,它通常是抽象类或者是实现类,仅仅完成做为被观察者必须实现的职责:管理观察者并通知观察者。
  • Observer观察者,观察者接受到消息后,即进行更新操做,对接收到的信息进行处理。
  • ConcreteSubject具体的被观察者,定义被观察者本身的业务逻辑,同时定义对哪些事件进行通知。
  • ConcreteObserver具体的观察者,每一个观察者接收到消息后的处理反应是不一样的,每一个观察者都有本身的处理逻辑。

观察者模式的优势

  • 观察者和被观察者之间是抽象耦合,不论是增长观察者仍是被观察者都很是容易扩展。
  • 创建一套触发机制。

观察者模式的缺点

观察者模式须要考虑开发效率和运行效率问题,一个被观察者,多个观察者,开发和调试比较复杂,Java消息的通知默认是顺序执行的,一个观察者卡壳,会影响总体的执行效率。这种状况通常考虑异步的方式。

使用场景

  • 关联行为场景,关联是可拆分的。
  • 事件多级触发场景。
  • 跨系统的消息交换场景,如消息队列的处理机制。

Java中的观察者模式

java.util.Observable类和java.util.Observer接口。

订阅发布模型

观察者模式也叫做发布/订阅模式。

1、非注解的监听器的实现方式

非注解的监听器的实现方式,这样有利于了解一下注解实现的原理 

什么是ApplicationContext? 
它是Spring的核心,Context咱们一般解释为上下文环境,可是理解成容器会更好些。 
ApplicationContext则是应用的容器。
Spring把Bean(object)放在容器中,须要用就经过get方法取出来。
ApplicationEven:是个抽象类,里面只有一个构造函数和一个长整型的timestamp。
ApplicationListener:是一个接口,里面只有一个onApplicationEvent方法。

package org.springframework.context;
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);

}

因此本身的类在实现该接口的时候,要实装该方法。


若是在上下文中部署一个实现了ApplicationListener接口的bean,那么每当在一个ApplicationEvent发布到ApplicationContext时,这个bean获得通知。其实这就是标准的Oberver设计模式。

 

2.一、初始化处理

在一些业务场景中,当容器初始化完成以后,须要处理一些操做,好比一些数据的加载、初始化缓存、特定任务的注册等等。通常来讲一个项目启动时须要加载或者执行一些特殊的任务来初始化系统,一般的作法就是用servlet去初始化,可是 servlet在使用spring bean时不能直接注入,还须要在web.xml配置,比较麻烦(见http://www.cnblogs.com/duanxz/p/3772979.html)。这个时候咱们就可使用Spring提供的ApplicationListener来进行操做。
本文以在Spring boot下的使用为例来进行说明。首先,须要实现ApplicationListener接口并实现onApplicationEvent方法。把须要处理的操做放在onApplicationEvent中进行处理:
而后,实例化ApplicationStartListener这个类,在Spring boot中经过一个配置类来进行实例化:
随后,启动Spring boot服务,打印出一下内容:
从打印的结果能够看出,ApplicationStartListener的onApplicationEvent方法在容器启动时已经被成功调用了。而此时初始化的容器为root容器。

下面给出例子:
首先建立一个ApplicationEvent实现类:

import org.springframework.context.ApplicationEvent;  
  
public class EmailEvent extends ApplicationEvent {  
    /** 
     * <p>Description:</p> 
     */  
    private static final long serialVersionUID = 1L;  
    public String address;    
    public String text;  
      
    public EmailEvent(Object source) {  
        super(source);  
    }  
      
    public EmailEvent(Object source, String address, String text) {  
        super(source);  
        this.address = address;  
        this.text = text;  
    }  
      
    public void print(){  
        System.out.println("hello spring event!");  
    }  
  
}  

给出监听器:

import org.springframework.context.ApplicationEvent;  
import org.springframework.context.ApplicationListener;  
  
public class EmailListener implements ApplicationListener {  
  
    public void onApplicationEvent(ApplicationEvent  event) {  
        if(event instanceof EmailEvent){  
            EmailEvent emailEvent = (EmailEvent)event;  
            emailEvent.print();  
            System.out.println("the source is:"+emailEvent.getSource());  
            System.out.println("the address is:"+emailEvent.address);  
            System.out.println("the email's context is:"+emailEvent.text);  
        }  
          
    }  
  
}  
applicationContext.xml文件配置:  
<bean id="emailListener" class="com.spring.event.EmailListener"></bean>  

测试类:  

import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
public class Test {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");  
          
        //HelloBean hello = (HelloBean) context.getBean("helloBean");  
        //hello.setApplicationContext(context);  
        EmailEvent event = new EmailEvent("hello","boylmx@163.com","this is a email text!");  
        context.publishEvent(event);  
        //System.out.println();  
    }  
}  

测试结果:
hello spring event!
the source is:hello
the address is:boylmx@163.com
the email's context is:this is a email text!

 

2、注解 实现事件监听

好处:不用每次都去实现ApplicationListener,能够在一个class中定义多个方法,用@EventListener来作方法级别的注解。例如:

package com.mu.listener;

import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import com.mu.event.MyTestEvent;

@Component
public class MyAnnotationListener {

    @EventListener
    public void listener1(MyTestEvent event) {
        System.out.println("注解监听器1:" + event.getMsg());
    }
}

 

在实际工做中,事件监听常常会用在发送通知,消息、邮件等状况下,那么这个时候每每是须要异步执行的,不能在业务的主线程里面,那怎么样能够实现异步处理呢?固然你能够写一个线程,单独作这个事情,在此,我比较推荐的是用spring的@Async注解方式,一个简单的注解,就能够把某一个方法或者类下面的全部方法所有变成异步处理的方法,这样,就能够作处处理监听事件的时候也不会阻塞主进程了。
新增监听器listener2,在方法上加上@Async注解,可是此注解不能标注static修饰的方法

package com.mu.listener;

import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import com.mu.event.MyTestEvent;

@Component
public class MyAnnotationListener {

    @EventListener
    public void listener1(MyTestEvent event) {
        System.out.println("注解监听器1:" + event.getMsg());
    }

    @EventListener
 @Async public void listener2(MyTestEvent event) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("注解监听器2:" + event.getMsg());
    }

}

 

想要启动注解方式的异步处理办法,还须要作一下配置

注解的应用范围: 
类:表示这个类中的全部方法都是异步的 
方法:表示这个方法是异步的,若是类也注解了,则以这个方法的注解为准 
配置:executor:指定一个缺省的executor给@Async使用。 

 

 

-------------------------------------------------------------------------------------------------------------------------

当spring 容器初始化完成后执行某个方法 防止onApplicationEvent方法被执行两次

在作web项目开发中,尤为是企业级应用开发的时候,每每会在工程启动的时候作许多的前置检查。

  好比检查是否使用了咱们组禁止使用的Mysql的group_concat函数,若是使用了项目就不能启动,并指出哪一个文件的xml文件使用了这个函数。

而在Spring的web项目中,咱们能够介入Spring的启动过程。咱们但愿在Spring容器将全部的Bean都初始化完成以后,作一些操做,这个时候咱们就能够实现一个接口:

package com.yk.test.executor.processor
public class InstantiationTracingBeanPostProcessor implements ApplicationListener<ContextRefreshedEvent> {
     @Override
     public void onApplicationEvent(ContextRefreshedEvent event) {
       //须要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
  }
 }

同时在Spring的配置文件中,添加注入:

<bean class="com.yk.test.executor.processor.InstantiationTracingBeanPostProcessor"/>

可是这个时候,会存在一个问题,在web 项目中(spring mvc),系统会存在两个容器,一个是root application context ,另外一个就是咱们本身的 projectName-servlet  context(做为root application context的子容器)。

这种状况下,就会形成onApplicationEvent方法被执行两次。为了不上面提到的问题,咱们能够只在root application context初始化完成后调用逻辑代码,其余的容器的初始化完成,则不作任何处理,修改后代码

以下:

@Override
   public void onApplicationEvent(ContextRefreshedEvent event) {
       if(event.getApplicationContext().getParent() == null){//root application context 没有parent,他就是老大.
            //须要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
       }
   }

 

Spring 的事件传播机制 是基于观察者模式(Observer)实现的,它能够将 Spring Bean 的改变定义为事件 ApplicationEvent,经过 ApplicationListener 监听 ApplicationEvent 事件,一旦Spring Bean 使用 ApplicationContext.publishEvent( ApplicationEvent event )发布事件后,Spring 容器会通知注册在 bean.xml 中全部 ApplicationListener 接口的实现类,最后 ApplicationListener 接口实现类判断是否响应刚发布出来的 ApplicationEvent 事件。

因此,要使用 Spring 事件传播机制须要如下四点:

1. 创建事件类,继承 ApplicationEvent 父类

2. 创建监听类,实现 ApplicationListener 接口

3. 在配置文件 bean.xml 中注册写好的全部 事件类 和 监听类

4. 须要发布事件的类 要实现 ApplicationContextAware 接口,并获取 ApplicationContext 参数

随后即可以开始使用 Spring 事件传播机制为咱们服务:(为了讲解流程的连贯性,续以上步骤来测试)

4.1 在本身编写的须要发布事件的 Action 类中实例化 1 中编写好的事件类,并使用 ApplicationContext.publishEvent 发布事件

5. 经过 Spring 调用 Action 方法,观察输出结果(本文使用 Junit 测试)

 

如下为1-5步骤的源码:

1. 创建事件类 ActionEvent.java 

 

[java]  view plain  copy
 
  1. public class ActionEvent extends ApplicationEvent{  
  2.   
  3.     public ActionEvent(Object source) {  
  4.         super(source);  
  5.         System.out.println("This is ActionEvent");  
  6.     }  
  7. }  

 

2. 创建监听类 ActionListener1.java、ActionListener2.java

 

 

[java]  view plain  copy
 
  1. public class ActionListener1 implements ApplicationListener {  
  2.   
  3.     public void onApplicationEvent(ApplicationEvent event) {  
  4.         if(event instanceof ActionEvent){  
  5.             System.out.println("ActionListener1: "+event.toString());  
  6.         }  
  7.     }  
  8.   
  9. }  
[java]  view plain  copy
 
  1. public class ActionListener2 implements ApplicationListener {  
  2.   
  3.     public void onApplicationEvent(ApplicationEvent event) {  
  4.         if(event instanceof ActionEvent){  
  5.             System.out.println("ActionListener2: "+event.toString());  
  6.         }  
  7.     }  
  8.   
  9. }  

 

3. 在 bean.xml 中注册事件类和监听类

 

[java]  view plain  copy
 
  1. <bean id="loginaction" class="com.ayali.action.LoginAction"/>  
  2. <bean id="listener1" class="com.ayali.action.ActionListener1"/>  
  3. <bean id="listener2" class="com.ayali.action.ActionListener2"/>  

 

4. 编写 须要发布事件的 loginAction.java

 

 

[java]  view plain  copy
 
  1. public class LoginAction implements ApplicationContextAware{  
  2.   
  3.     private ApplicationContext applicationContext;  
  4.       
  5.     public void setApplicationContext(ApplicationContext applicationContext)  
  6.             throws BeansException {  
  7.         this.applicationContext = applicationContext;  
  8.     }  
  9.       
  10.     public void login(String username, String password){  
  11.         ActionEvent event = new ActionEvent(username);  
  12.         this.applicationContext.publishEvent(event);  
  13.     }  
  14.   
  15. }  

 

5. 编写测试方法

 

 

[java]  view plain  copy
 
  1. public void testActionListener(){  
  2.     ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");  
  3.     LoginAction loginAction = (LoginAction) ctx.getBean("loginaction");  
  4.     loginAction.login("jack", "123");  
  5. }  

 

输出结果为:

 

[html]  view plain  copy
 
    1. This is ActionEvent  
    2. ActionListener1:com.ayali.action.ActionEvent[source=jack]  
    3. ActionListener2:com.ayali.action.ActionEvent[source=jack]  
相关文章
相关标签/搜索