事件监听器模式与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; }
从注释可以看出来,可以注册多个,而且可以通过@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; } }
(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); } }
(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); } }
传递参数为[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 进行调用。