事件监听器模式与Spring事件机制&Springboot的ApplicationRunner和CommandLineRunner

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的事件

  自己测试Spring的事件机制是同步的,也就是一个线程在处理。发事件的和listener 是一个线程。

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]

 3. Springboot 的 ApplicationRunner 和 CommandLineRunner

  springboot 提供了两个接口,在spring 容器启动完成后会调用相关的bean, 两个接口源码如下:

package org.springframework.boot;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

/**
 * Interface used to indicate that a bean should <em>run</em> when it is contained within
 * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 * <p>
 * If you need access to {@link ApplicationArguments} instead of the raw String array
 * consider using {@link ApplicationRunner}.
 *
 * @author Dave Syer
 * @since 1.0.0
 * @see ApplicationRunner
 */
@FunctionalInterface
public interface CommandLineRunner {

    /**
     * Callback used to run the bean.
     * @param args incoming main method arguments
     * @throws Exception on error
     */
    void run(String... args) throws Exception;

}

----

package org.springframework.boot;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

/**
 * Interface used to indicate that a bean should <em>run</em> when it is contained within
 * a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 *
 * @author Phillip Webb
 * @since 1.3.0
 * @see CommandLineRunner
 */
@FunctionalInterface
public interface ApplicationRunner {

    /**
     * Callback used to run the bean.
     * @param args incoming application arguments
     * @throws Exception on error
     */
    void run(ApplicationArguments args) throws Exception;

}
View Code

  从注释可以看出来,可以注册多个,而且可以通过@Order 注解或者实现Ordered 接口来进行排序。 

  自己的理解: ApplicationRunner 和 CommandLineRunner 的区别是两者将main 方法的入参封装的位置不同。 CommandLineRunner 是作为可变数组传到run 方法, ApplicationRunner 是包装为 ApplicationArguments  对象。

1. 测试:

三个类:

(1)  MyApplicationRunner

package com.xm.ggn.test;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class MyApplicationRunner implements ApplicationRunner, Ordered {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("com.xm.ggn.test.MyApplicationRunner.run");
        System.out.println(args);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
View Code

(2) MyCommandLineRunner

package com.xm.ggn.test;

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(2)
public class MyCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("com.xm.ggn.test.MyCommandLineRunner.run");
        System.out.println(args);
    }
}
View Code

(3) MyCommandLineRunner2

package com.xm.ggn.test;

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(3)
public class MyCommandLineRunner2 implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("com.xm.ggn.test.MyCommandLineRunner2.run");
        System.out.println(args);
    }
}
View Code

传递参数为[1 2 3]查看结果:

接收到的参数如下:

 

 控制台打印的信息如下:

com.xm.ggn.test.MyApplicationRunner.run
org.springframework.boot.DefaultApplicationArguments@1da1a8a4
com.xm.ggn.test.MyCommandLineRunner.run
[Ljava.lang.String;@300f120d
com.xm.ggn.test.MyCommandLineRunner2.run
[Ljava.lang.String;@300f120d

2. 源码查看

其调用链如下:

 1. org.springframework.boot.SpringApplication#run(java.lang.String...) 方法开始运行

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

  IOC完成之后调用 callRunners(context, applicationArguments); 执行下面第2步。

2. org.springframework.boot.SpringApplication#callRunners

    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        for (Object runner : new LinkedHashSet<>(runners)) {
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

    private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
        try {
            (runner).run(args);
        }
        catch (Exception ex) {
            throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
        }
    }

  可以看到拿到容器中上面两种类型的对象,然后调用  AnnotationAwareOrderComparator.sort(runners);  对List 进行排序, 之后调用 callRunner 进行调用。

 

posted @ 2020-07-08 19:55  QiaoZhi  阅读(605)  评论(0编辑  收藏  举报