1. 程式人生 > 實用技巧 >事件監聽器模式與Spring事件機制

事件監聽器模式與Spring事件機制

1.事件監聽器模式簡單使用

  比如監聽門開關改變事件以及name改變事件。

1.事件相關類

抽象門事件

package cn.qlq.event.base;

import java.util.EventObject;

public abstract class DoorEvent extends EventObject {

    private static final long serialVersionUID = 7099057708183571937L;

    /**
     * 事件發生時間
     */
    private final long timestamp;

    
public DoorEvent(Object source) { super(source); this.timestamp = System.currentTimeMillis(); } public final long getTimestamp() { return this.timestamp; } }

門name改變事件

package cn.qlq.event.base;

/**
 * 門改變事件
 * 
 * @author Administrator
 *
 */
public class DoorNameChangeEvent extends
DoorEvent { private static final long serialVersionUID = 2106840053610489753L; private String originName; private String newName; public DoorNameChangeEvent(Object source, String originName, String newName) { super(source); this.originName = originName; this.newName = newName; }
public String getOriginName() { return originName; } public void setOriginName(String originName) { this.originName = originName; } public String getNewName() { return newName; } public void setNewName(String newName) { this.newName = newName; } }

門狀態改變事件

package cn.qlq.event.base;

/**
 * 門改變事件
 * 
 * @author Administrator
 *
 */
public class DoorStatusChangeEvent extends DoorEvent {

    private static final long serialVersionUID = 2106840053610489753L;

    private String originStatus;

    private String newStatus;

    public DoorStatusChangeEvent(Object source, String originStatus, String newStatus) {
        super(source);
        this.originStatus = originStatus;
        this.newStatus = newStatus;
    }

    public String getOriginStatus() {
        return originStatus;
    }

    public void setOriginStatus(String originStatus) {
        this.originStatus = originStatus;
    }

    public String getNewStatus() {
        return newStatus;
    }

    public void setNewStatus(String newStatus) {
        this.newStatus = newStatus;
    }

}

2.監聽器相關類

抽象監聽器類

package cn.qlq.event.base;

import java.util.EventListener;

public interface DoorListener<E extends DoorEvent> extends EventListener {

    void onEvent(E event);

}

門name改變事件監聽器

package cn.qlq.event.base;

public class DoorNameChangeListener implements DoorListener<DoorNameChangeEvent> {

    @Override
    public void onEvent(DoorNameChangeEvent event) {
        System.out.println("=============門name改變===========" + event.getTimestamp());
        System.out.println("原來name: " + event.getOriginName());
        System.out.println("最新name: " + event.getNewName());
    }
}

門status改變事件監聽器

package cn.qlq.event.base;

public class DoorStatusChangeListener implements DoorListener<DoorStatusChangeEvent> {

    @Override
    public void onEvent(DoorStatusChangeEvent event) {
        System.out.println("=============門status改變===========" + event.getTimestamp());
        System.out.println("原來狀態: " + event.getOriginStatus());
        System.out.println("最新狀態: " + event.getNewStatus());
    }
}

3.門類

  addListener方法獲取到泛型的實際引數然後將listener快取到cache屬性中,用於釋出事件。publishEvent釋出事件根據事件對應的typeName到cache中找到listener集合,然後釋出事件。

package cn.qlq.event.base;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Door {

    // 快取事件監聽器
    private Map<String, List<DoorListener<? extends DoorEvent>>> cache = new HashMap<>();

    public String status;

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        if (status != null && !status.equals(this.status)) {
            publishEvent(new DoorStatusChangeEvent(this, this.status, status));
        }

        this.status = status;
    }

    public void addListener(DoorListener<? extends DoorEvent> listener) {
        // 獲取到介面。根據介面獲取到泛型
        Type[] genericInterfaces = listener.getClass().getGenericInterfaces();
        Type type = genericInterfaces[0];
        if (type instanceof ParameterizedType) {
            ParameterizedType typeTmp = (ParameterizedType) type;
            // 原始型別。例如 interface cn.qlq.event.base.DoorListener
            Type rawType = typeTmp.getRawType();
            // 獲取到實際引數型別
            Type[] actualTypeArguments = typeTmp.getActualTypeArguments();
            Type type2 = actualTypeArguments[0];
            String actuallyTypeName = type2.getTypeName();
            List<DoorListener<? extends DoorEvent>> list = cache.get(actuallyTypeName);
            if (list == null) {
                list = new ArrayList<DoorListener<? extends DoorEvent>>();
                cache.put(actuallyTypeName, list);
            }
            list.add(listener);
        }
    }

    public <E extends DoorEvent> void publishEvent(E event) {
        String typeName = event.getClass().getTypeName();
        List<DoorListener<? extends DoorEvent>> list = cache.get(typeName);
        for (DoorListener doorListener : list) {
            doorListener.onEvent(event);
        }
    }

}

4.客戶端測試程式碼

package cn.qlq.event.base;

public class Client {

    public static void main(String[] args) {

        Door door = new Door();
        door.addListener(new DoorStatusChangeListener());
        door.addListener(new DoorNameChangeListener());

        // 釋出事件
        door.publishEvent(new DoorNameChangeEvent(door, "木門", "鐵門"));

        door.setStatus("close");
        door.setStatus("close");
        door.setStatus("open");

    }
}

結果:

=============門name改變===========1594192130541
原來name: 木門
最新name: 鐵門
=============門status改變===========1594192130541
原來狀態: null
最新狀態: close
=============門status改變===========1594192130541
原來狀態: close
最新狀態: open

補充:Type與Class的區別

檢視JDK原始碼,發現有個Type類,而且是從1.5引入的。如下:

/** <a href="http://www.cpupk.com/decompiler">Eclipse Class Decompiler</a> plugin, Copyright (c) 2017 Chen Chao. */
package java.lang.reflect;

/**
 * Type is the common superinterface for all types in the Java
 * programming language. These include raw types, parameterized types,
 * array types, type variables and primitive types.
 *
 * @since 1.5
 */
public interface Type {
    /**
     * Returns a string describing this type, including information
     * about any type parameters.
     *
     * @implSpec The default implementation calls {@code toString}.
     *
     * @return a string describing this type
     * @since 1.8
     */
    default String getTypeName() {
        return toString();
    }
}

Type是Class的父介面。Type 是 Java 程式語言中所有型別的公共高階介面。它們包括原始型別、引數化型別、陣列型別、型別變數和基本型別。Type可以表示出泛型的型別,而Class不能。

如果想要獲取泛型型別的陣列,可以將Type轉化為它的子介面ParameterizedType,通過getActualTypeArguments() 獲取。參考上面的:Door類的addListener方法。

另外:java中8種基本資料型別和void也有class物件。如下Integer類的方法:

    public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

測試如下:

  getGenericSuperclass() 方法獲取父類的Type;getGenericInterfaces()方法獲取所有實現介面的Type。Type可以用於獲取泛型的實際型別。ParameterizedType 型別的例項可以獲取到原始型別和實際型別。

package cn.qlq.event.base;

import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;

public class Client<T> {

    private T prop;

    public static void main(String[] args) {
        System.out.println(int.class.isPrimitive());
        System.out.println(Integer.class.isPrimitive());
        System.out.println("===========");

        Client<String> client = new Client<String>();
        printObj(client);

        Integer integer = new Integer(1);
        printObj(integer);

        Integer[] integers = { 1, 3 };
        printObj(integers);
    }

    private static void printObj(Object obj) {
        System.out.println("===========");
        Class<?> class2 = obj.getClass();
        System.out.println(class2);
        System.out.println(class2.getTypeName());
        Type genericSuperclass2 = class2.getGenericSuperclass();
        System.out.println(genericSuperclass2);
        Type[] genericInterfaces2 = class2.getGenericInterfaces();
        System.out.println(Arrays.asList(genericInterfaces2));
        TypeVariable<?>[] typeParameters = class2.getTypeParameters();
        System.out.println(Arrays.asList(typeParameters));
    }

    public T getProp() {
        return prop;
    }

    public void setProp(T prop) {
        this.prop = prop;
    }

}

結果:

true
false
===========
===========
class cn.qlq.event.base.Client
cn.qlq.event.base.Client
class java.lang.Object
[]
[T]
===========
class java.lang.Integer
java.lang.Integer
class java.lang.Number
[java.lang.Comparable<java.lang.Integer>]
[]
===========
class [Ljava.lang.Integer;
java.lang.Integer[]
class java.lang.Object
[interface java.lang.Cloneable, interface java.io.Serializable]
[]

2.Spring的事件

1. 標準事件

  Spring的ApplicationContext 提供了支援事件和程式碼中監聽器的功能。
  我們可以建立bean用來監聽在ApplicationContext 中釋出的事件。ApplicationEvent類和在ApplicationContext介面中處理的事件,如果一個bean實現了ApplicationListener介面,當一個ApplicationEvent 被髮布以後,bean會自動被通知。

Spring 提供了以下5種標準的事件:
1. 上下文更新事件(ContextRefreshedEvent):該事件會在ApplicationContext被初始化或者更新時釋出。也可以在呼叫ConfigurableApplicationContext 介面中的refresh()方法時被觸發。
2. 上下文開始事件(ContextStartedEvent):當容器呼叫ConfigurableApplicationContext的Start()方法開始/重新開始容器時觸發該事件。
3. 上下文停止事件(ContextStoppedEvent):當容器呼叫ConfigurableApplicationContext的Stop()方法停止容器時觸發該事件。
4. 上下文關閉事件(ContextClosedEvent):當ApplicationContext被關閉時觸發該事件。容器被關閉時,其管理的所有單例Bean都被銷燬。
5. 請求處理事件(RequestHandledEvent):在Web應用中,當一個http請求(request)結束觸發該事件

比如經常在容器啟動後建立預設使用者等操作:

package cn.qlq.event;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import cn.qlq.service.user.UserService;

/**
 * 上下文更新事件(ContextRefreshedEvent):該事件會在ApplicationContext被初始化或者更新時釋出。
 * 也可以在呼叫ConfigurableApplicationContext 介面中的refresh()方法時被觸發。
 * 
 * @author Administrator
 *
 */
@Component
public class ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {
    
    @Autowired
    private UserService userService;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("ContextRefreshedEvent=========== 容器啟動完成");
        System.out.println(event);
        
        // 建立預設使用者
//        userService.addUser(user);
    }

}

啟動後控制檯:

ContextRefreshedEvent=========== 容器啟動完成
org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@13b3d178, started on Wed Jul 08 16:28:45 CST 2020]
2020/07/08-16:29:00 [main] INFO cn.qlq.MySpringBootApplication Started MySpringBootApplication in 15.976 seconds (JVM running for 17.039)

2.自定義事件

1.定義事件

package cn.qlq.event;

import org.springframework.context.ApplicationEvent;

public class CustomApplicationEvent extends ApplicationEvent {

    private String msg;

    private static final long serialVersionUID = -9184671635725233773L;

    public CustomApplicationEvent(Object source, final String msg) {
        super(source);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

}

2.定義監聽器

package cn.qlq.event;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class CustomEventListener implements ApplicationListener<CustomApplicationEvent> {
    @Override
    public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
        // handle event
        System.out.println("收到事件,訊息為:" + applicationEvent.getMsg());
        System.out.println(applicationEvent);
    }
}

3.程式碼釋出事件

@Controller
public class LoginController {

    @Autowired
    private ApplicationContext applicationContext;/**
     * 跳轉到登陸介面
     * 
     * @return
     */
    @RequestMapping("login")
    public String login() {
        applicationContext.publishEvent(new CustomApplicationEvent(this, "有人訪問登陸"));
        return "login";
    }
}

結果:

收到事件,訊息為:有人訪問登陸
cn.qlq.event.CustomApplicationEvent[source=cn.qlq.controller.system.LoginController@333a44f2]