SpringBoot监听器
为什么要使用监听器
emmmm,这是我从网上抄来的讲解,很生动,我就喜欢这样的解释,不知道有没有男生和我一样,更喜欢具象的讲解
为什么要使用监听器,举个例子,大家在过红绿灯的时候,每一个司机其实就是一个观察者,那观察的目标是什么呢?观察的目标就是红绿灯,那这个过程中会产生什么事件呢?就是红灯,黄灯,绿灯的事件,当司机收到这些事件之后会做出不同的举动。那如果不使用这种模式,就是我不用红绿灯,我直接找个交警去一个司机一个司机通知,告诉每个司机,你可以走了,你不能走,比较一下大家就可以发现第一种方式效率更高。其实上面的红绿灯的场景就是一个典型的监听器模式,是23中设计模式中的一种
完成监控器都需要啥(简版)
监听器,我得有一个人监听啊,就是司机
事件(其实事件就是目标中发生了什么事,不同事对应不同的事件),就是红绿灯
触发场景(目标执行了什么东西,然后观察者才可以观察到变化,这个其实就是触发场景)
ApplicationEvent
在源码里我们发现里面只有两个构造函数,最重要的是构造函数需要一个是Object类型的参数,不重要的是也有一个时间,其实那个clock.millis()也是一个获取时间的方法而已。到时候我们写自己的事件的时候,就可以直接继承ApplicationEvent。
这,就是我们的事件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context;
import java.time.Clock;
import java.util.EventObject;
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp;
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
public ApplicationEvent(Object source, Clock clock) {
super(source);
this.timestamp = clock.millis();
}
public final long getTimestamp() {
return this.timestamp;
}
}
ApplicationListener
老规矩,继续看源码
发现也就是一个接口,一点一点解释就是,我这个接口,需要的参数是一个继承了ApplicationEvent了的类E,然后有一个方法onApplicationEvent,用到了这个E的实例,我们写自己的监听器的时候就要实现这个接口了,然后将onApplicationEvent里写上自己的逻辑。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context;
import java.util.EventListener;
import java.util.function.Consumer;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
return (event) -> {
consumer.accept(event.getPayload());
};
}
}
这,就是我们的监听器
SmartApplicationListener
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context.event;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;
public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
//指定支持哪些类型的事件
boolean supportsEventType(Class<? extends ApplicationEvent> var1);
//指定支持发生事件所在的类型
default boolean supportsSourceType(@Nullable Class<?> sourceType) {
return true;
}
default int getOrder() {
return 2147483647;
}
default String getListenerId() {
return "";
}
}
ApplicationContext:publishEvent
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.lang.Nullable;
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
@Nullable
String getId();
String getApplicationName();
String getDisplayName();
long getStartupDate();
@Nullable
ApplicationContext getParent();
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
源码里最关键的是继承了ApplicationEventPublisher类,我们来看看ApplicationEventPublisher里有什么
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.context;
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
this.publishEvent((Object)event);
}
void publishEvent(Object var1);
}
就只有这么个publishEvent方法,发现也是可以使用ApplicationEvent作为参数的,是不是就连贯起来了,这个方法的用处就是手动发布一个事件,这个时候监听器Listener就可以间听到了。这,就是触发场景
自定义监听器
下面我们就自己写一个监听器的流程来试一下
1、事件里都有啥信息?
package com.newcrud.entity;
import lombok.Data;
@Data
public class MyListenerEntity {
private Integer id;
private String name;
}
2、继承ApplicationEvent写一个自己的事件
package com.newcrud.event;
import com.newcrud.entity.MyListenerEntity;
import org.springframework.context.ApplicationEvent;
public class MyEvent extends ApplicationEvent {
private MyListenerEntity myListenerEntity;
/**
* 重写构造函数
*
* @param source 发生事件的对象
* @param myListenerEntity 注册用户对象
*/
public MyEvent(Object source, MyListenerEntity myListenerEntity) {
super(source);
this.myListenerEntity=myListenerEntity;
}
}
3、实现ApplicationListener,将我们自己的事件放到自己的监听器里
package com.newcrud.config;
import com.newcrud.entity.MyListenerEntity;
import com.newcrud.event.MyEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
//使用Compent将这个监听器交给Spring容器来加载
@Slf4j
public class MyListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent myEvent) {
//将事件中的信息获取到
MyListenerEntity myListenerEntity=myEvent.getMyListenerEntity();
log.info(myListenerEntity.getName());
log.info(String.valueOf(myListenerEntity.getId()));
}
}
当然我们这里只是简单的打印了一些消息,真正的使用过程中一定是各种各样拿到参数后去用各种逻辑做处理。
4、让我们来触发事件吧,其实就是将信息组装到事件里然后用ApplicationContext的publishEvent方法手动发布一下
package com.newcrud.service.impl;
import com.newcrud.entity.MyListenerEntity;
import com.newcrud.event.MyEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class MyListenerImpl {
@Autowired
ApplicationContext applicationContext;
public MyListenerEntity getMyListenerEntity(){
MyListenerEntity myListenerEntity =new MyListenerEntity();
myListenerEntity.setName("zhangsan");
myListenerEntity.setId(1);
MyEvent myEvent = new MyEvent(this,myListenerEntity);
// 发布事件
applicationContext.publishEvent(myEvent);
log.info("触发器被触发");
return myListenerEntity;
}
}
5、测试一下下
package com.newcrud.controller;
import com.newcrud.entity.MyListenerEntity;
import com.newcrud.service.impl.MyListenerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/listener")
public class MyListenerController {
@Autowired
MyListenerImpl myListener;
@GetMapping("my")
public MyListenerEntity getMyListenerEntity(){
return myListener.getMyListenerEntity();
}
}
检查结果
postman请求:http://localhost:8084/listener/my
接口返回
{
"id": 1,
"name": "zhangsan"
}
检查SpringBoot的日志
2021-09-22 16:18:49.815 INFO 19326 --- [nio-8084-exec-1] com.newcrud.config.MyListener : zhangsan
2021-09-22 16:18:49.815 INFO 19326 --- [nio-8084-exec-1] com.newcrud.config.MyListener : 1
2021-09-22 16:18:49.815 INFO 19326 --- [nio-8084-exec-1] com.newcrud.service.impl.MyListenerImpl : 触发器被触发
注册监听器有两种方式
Compemt
就是刚刚我们用的方式
META-INF/spring.factories
在资源目录中的 META-INF/spring.factories 文件中自动注册:
org.springframework.context.ApplicationListener=
cn.javastack.springboot.features.listener.JavastackListener
如果是监听 Spring 应用上下文(ApplicationContext)创建之后的事件,可以直接在监听器上使用 @Component 注解即可,否则需要使用第一种方法的自动注册,因为 ApplicationContext 并未创建,这时的 Bean 是不能被加载的。
SpringBoot自带的监听器和事件
监听器
除了我们自己写的监听器之外,其实SpringBoot已经为我们准备了很多的监听器,比如Servlet的监听器
Servlet 中的监听器分为以下 3 种类型。
1、监听 ServletContext、 Request、 Session 作用域的创建和销毁
• ServletContextlistener:监听 ServeltContext。
• HttpSessionlistener:监听新的 Session 创建事件。
• ServletRequestlistener:监听 ServletRequest 的初始化和销毁。
2. 监听 ServletContext、 Request、 Session 作用域中属性的变化(增加、修改、删除)
• ServletContextAttributelistener:监听 Se「vlet 上下文参数的变化 。
• HttpSessionAttributelistener:监听 HttpSession 参数的变化。
• ServletRequestAttributelistener「:监听 ServletRequest 参数的变化 。
3. 监听 HttpSession 中对象状态的改变(被绑定、解除绑走、钝化、活化)
• HttpSessionBindinglistener:监听 HttpSession,并绑定及解除绑定。
• HttpSessionActivationlistener:监听钝化和活动的 HttpSession 状态改变。
Web 监听器是一种 Servlet 特殊类,它们能帮助开发者监听 Web 中特定的事件,比如 ServletContext、HttpSession 、ServletRequest 的创建和销毁;变量的创建、销毁和修改等。可以在某些动作前后增加处理,实现监控。
还是来做一个实验,比如说我们要使用HttpSessionListener,那我们就先进入源码看一下
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package javax.servlet.http;
import java.util.EventListener;
public interface HttpSessionListener extends EventListener {
default void sessionCreated(HttpSessionEvent se) {
}
default void sessionDestroyed(HttpSessionEvent se) {
}
}
发现只有两个方法,一个是初始化的时候执行的,一个是销毁的时候执行的,为了看效果,那我们就改一下
package com.newcrud.config;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@Component
public class MyListener2 implements HttpSessionListener {
public static Integer count = 0; //记录在线的用户数量
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("新来了一个");
count++;
se.getSession().getServletContext().setAttribute("count",count);
}
}
再写一个测试类
package com.newcrud.controller;
import com.newcrud.entity.MyListenerEntity;
import com.newcrud.service.impl.MyListenerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/listener")
public class MyListenerController {
@Autowired
MyListenerImpl myListener;
@GetMapping("my")
public MyListenerEntity getMyListenerEntity(){
return myListener.getMyListenerEntity();
}
//下面是新增的
@GetMapping("much")
public String getHowMuch(HttpServletRequest httpServletRequest){
Integer count = (Integer) httpServletRequest.getSession().getServletContext().getAttribute("count");
return "一共几个人呢:"+count;
}
}
执行一下接口:http://localhost:8084/listener/much
执行第一次
postman结果:
一共几个人呢:1
SpringBoot日志:
新来了一个
事件
同时也定义了很多很多的事件
容器事件(ApplicationContextEvent):
ContextRefresheEvent:
ApplicationContext容器初始化或刷新时触发该事件。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用
ContextStartedEvent:
当spring启动时,或者说是context调用start()方法时,会触发此事件
ContextStoppedEvent:
当spring停止时,或者说context调用stop()方法时,会触发此事件
ContextClosedEvent:
当spring关闭时,或者说context调用close()方法时,会触发此事件
Web应用相关(RequestHandledEvent):
ServletRequestHandledEvent:
每次请求处理结束后,容器上下文都发布了一个ServletRequestHandledEvent事件