深入浅出Spring/SpringBoot 事件监听机制

说明

事件监听机制可以理解为是一种观察者模式,有数据发布者(事件源)和数据接受者(监听器);

在Java中,事件对象都是继承java.util.EventObject对象,事件监听器都是java.util.EventListener实例;

EventObject对象不提供默认构造器,需要外部传递source参数,即用于记录并跟踪事件的来源;

观察者模式

观察者模式面向的需求是:A对象(观察者)对B对象(被观察者)的某种变化高度敏感,需要在B变化的一瞬间做出反应。举个例子,气象局需要构建一套系统,这系统有两个公告牌,分别用于显示当前的实时天气和未来几天的天气预报。当气象局发布新的天气数据(WeatherData)后,两个公告牌上显示的天气数据必须实时更新。气象局同时要求我们保证程序拥有足够的可扩展性,因为后期随时可能要新增新的公告牌。程序里的观察者和这种真正的【观察】略有不同,观察者不需要时刻盯着被观察者(例如A不需要每隔1ms就检查一次B的状态),二是采用注册(Register)或者成为订阅(Subscribe)的方式告诉被观察者:我需要你的某某状态,你要在它变化时通知我。采取这样被动的观察方式,既省去了反复检索状态的资源消耗,也能够得到最高的反馈速度。

观察者模式的应用

观察者模式通常基于Subject和Observer接口类来设计,下面是是类图:
深入浅出Spring/SpringBoot 事件监听机制_第1张图片
结合上面的类图,我们现在将观察者模式应用到WeatherData项目中来。于是有了下面这张类图:
深入浅出Spring/SpringBoot 事件监听机制_第2张图片

/**
* 主题(发布者、被观察者)
*/
public interface Subject {

    /**
     * 注册观察者
     */
    void registerObserver(Observer observer);

    /**
     * 移除观察者
     */
    void removeObserver(Observer observer);

    /**
     * 通知观察者
     */
    void notifyObservers(); 
}
/**
 * 观察者
 */
public interface Observer {
    void update();
}

公告牌用于显示的公共接口

public interface DisplayElement {
    void display();
}

WeatherData

public class WeatherData implements Subject {

    private List observers;

    private float temperature;//温度
    private float humidity;//湿度
    private float pressure;//气压
    private List forecastTemperatures;//未来几天的温度

    public WeatherData() {
        this.observers = new ArrayList();
    }

    @Override
    public void registerObserver(Observer observer) {
        this.observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        this.observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }

    public void measurementsChanged() {
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, 
    float pressure, List forecastTemperatures) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        this.forecastTemperatures = forecastTemperatures;
        measurementsChanged();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

    public List getForecastTemperatures() {
        return forecastTemperatures;
    }
}

显示当前天气的公告牌CurrentConditionsDisplay

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private WeatherData weatherData;

    private float temperature;//温度
    private float humidity;//湿度
    private float pressure;//气压

    public CurrentConditionsDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        this.weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("当前温度为:" + this.temperature + "℃");
        System.out.println("当前湿度为:" + this.humidity);
        System.out.println("当前气压为:" + this.pressure);
    }

    @Override
    public void update() {
        this.temperature = this.weatherData.getTemperature();
        this.humidity = this.weatherData.getHumidity();
        this.pressure = this.weatherData.getPressure();
        display();
    }
}

显示未来几天天气的公告牌ForecastDisplay

public class ForecastDisplay implements Observer, DisplayElement {

    private WeatherData weatherData;

    private List forecastTemperatures;//未来几天的温度

    public ForecastDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        this.weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("未来几天的气温");
        int count = forecastTemperatures.size();
        for (int i = 0; i < count; i++) {
            System.out.println("第" + i + "天:" + forecastTemperatures.get(i) + "℃");
        }
    }

    @Override
    public void update() {
        this.forecastTemperatures = this.weatherData.getForecastTemperatures();
        display();
    }
}

到这里,我们整个气象局的WeatherData应用就改造完成了。两个公告牌CurrentConditionsDisplay和ForecastDisplay实现了Observer和DisplayElement接口,在他们的构造方法中会调用WeatherData的registerObserver方法将自己注册成观察者,这样被观察者WeatherData就会持有观察者的应用,并将它们保存到一个集合中。当被观察者``WeatherData状态发送变化时就会遍历这个集合,循环调用观察者公告牌更新数据的方法。后面如果我们需要增加或者删除公告牌就只需要新增或者删除实现了Observer和DisplayElement`接口的公告牌就好了。

观察者模式将观察者和主题(被观察者)彻底解耦,主题只知道观察者实现了某一接口(也就是Observer接口)。并不需要观察者的具体类是谁、做了些什么或者其他任何细节。任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现了Observer接口的对象列表。

测试

public class ObserverPatternTest {

    public static void main(String[] args) {

        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

        List forecastTemperatures = new ArrayList();
        forecastTemperatures.add(22f);
        forecastTemperatures.add(-1f);
        forecastTemperatures.add(9f);
        forecastTemperatures.add(23f);
        forecastTemperatures.add(27f);
        forecastTemperatures.add(30f);
        forecastTemperatures.add(10f);
        weatherData.setMeasurements(22f, 0.8f, 1.2f, forecastTemperatures);
    }
}

spring事件发布监听机制

有了观察者设计模式的一定了解之后,我们直接再看一张浅出图 ,spring事件发布监听机制的图
深入浅出Spring/SpringBoot 事件监听机制_第3张图片
SpringBoot中事件监听机制则通过发布-订阅实现,主要包括以下三部分:

事件 ApplicationEvent,继承JDK的EventObject,可自定义事件。
事件发布者 ApplicationEventPublisher,负责事件发布。
事件监听者 ApplicationListener,继承JDK的EventListener,负责监听指定的事件。

观察者模式和发布订阅模式是有一点点区别的,区别有以下几点:

前者:观察者订阅主题,主题也维护观察者的记录,而后者:发布者和订阅者不需要彼此了解,而是在消息队列或代理的帮助下通信,实现松耦合。
前者主要以同步方式实现,即某个事件发生时,由Subject调用所有Observers的对应方法,后者则主要使用消息队列异步实现

深入浅出Spring/SpringBoot 事件监听机制_第4张图片

业务场景

用户注册功能为例吧,假设用户注册成功之后,我们将会发送邮件,优惠券等等操作

很容易就能写出下面的逻辑:

@RestController
@RequestMapping("/user")
public class SimpleUserController {

    @Autowired
    private SimpleEmailService emailService;

    @Autowired
    private SimpleCouponService couponService;

    @Autowired
    private SimpleUserService userService;

    @GetMapping("/register")
    public String register(String username) {
        // 注册
        userService.register(username);
        // 发送邮件
        emailService.sendEmail(username);
        // 发送优惠券
        couponService.addCoupon(username);
        return "注册成功!";
    }
}

这样写会有什么问题呢?

方法调用时,同步阻塞导致响应变慢,需要异步非阻塞的解决方案。

注册接口此时做的事情:注册,发邮件,优惠券,违反单一职责的原则。当然,如果后续没有拓展和修改的需求,这样子倒可以接受。

如果后续注册的需求频繁变更,相应就需要频繁变更register方法,违反了开闭原则。

针对以上的问题,我们想一想解决的方案:

一、异步非阻塞的效果可以新开一个线程执行耗时的发送邮件任务,但频繁地创建和销毁线程比较耗时,并且并发线程数无法控制,创建过多的线程会导致堆栈溢出。

二、使用线程池执行任务解决上述问题。

@Service
@Slf4j
public class SimpleEmailService {
	// 启动一个线程执行耗时操作
    public void sendEmail(String username) {
        Thread thread = new Thread(()->{
            try {
                // 模拟发邮件耗时操作
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("给用户 [{}] 发送邮件...", username);
        });
        thread.start();
    }
}

@Slf4j
@Service
public class SimpleCouponService {

    ExecutorService executorService = Executors.newSingleThreadExecutor();
	// 线程池执行任务,减少资源消耗
    public void addCoupon(String username) {
        executorService.execute(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("给用户 [{}] 发放优惠券", username);
        });
    }
}

这里用户注册事件对【发送短信和优惠券】其实是一对多的关系,可以使用观察者模式进行解耦:

 * 主题接口
 * 
 */
public interface Subject {

    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers(String message);
}

/**
 * 观察者接口
 
 */
public interface Observer {

    void update(String message);
}

@Component
@Slf4j
public class EmailObserver implements Observer {

    @Override
    public void update(String message) {
        log.info("向[{}]发送邮件", message);
    }
}
@Component
@Slf4j
public class CouponObserver implements Observer {

    @Override
    public void update(String message) {
        log.info("向[{}]发送优惠券",message);
    }
}

@Component
public class UserRegisterSubject implements Subject {

    @Autowired
    List observers;

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String username) {
        for (Observer observer : observers) {
            observer.update(username);
        }
    }
}


@RestController
@RequestMapping("/")
public class UserController {

    @Autowired
    UserRegisterSubject subject;

    @Autowired
    private SimpleUserService userService;


    @GetMapping("/reg")
    public String reg(String username) {
        userService.register(username);
        subject.notifyObservers(username);
        return "success";
    }
}

通过SpringBoot的方式,能够很容易实现事件监听,接下来我们改造一下上面的案例:
定义注册事件

public class UserRegisterEvent extends ApplicationEvent {
    
    private String username;

    public UserRegisterEvent(Object source) {
        super(source);
    }

    public UserRegisterEvent(Object source, String username) {
        super(source);
        this.username = username;
    }

    public String getUsername() {
        return username;
    }
}

注解方式 @EventListener定义监听器

/**
 * 注解方式 @EventListener

 */
@Service
@Slf4j
public class CouponService {
    /**
     * 监听用户注册事件,执行发放优惠券逻辑
     */
    @EventListener
    public void addCoupon(UserRegisterEvent event) {
        log.info("给用户[{}]发放优惠券", event.getUsername());
    }
}

实现ApplicationListener的方式定义监听器

/**
 * 实现ApplicationListener的方式
 *
 */
@Service
@Slf4j
public class EmailService implements ApplicationListener {
    /**
     * 监听用户注册事件, 异步发送执行发送邮件逻辑
     */
    @Override
    @Async
    public void onApplicationEvent(UserRegisterEvent event) {
        log.info("给用户[{}]发送邮件", event.getUsername());
    }
}

注册事件发布者

@Service
@Slf4j
public class UserService implements ApplicationEventPublisherAware {

    // 注入事件发布者
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * 发布事件
     */
    public void register(String username) {
        log.info("执行用户[{}]的注册逻辑", username);
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }
}

定义接口

@RestController
@RequestMapping("/event")
public class UserEventController {

    @Autowired
    private UserService userService;

    @GetMapping("/register")
    public String register(String username){
        userService.register(username);
        return "恭喜注册成功!";
    }
}      

主程序类

@EnableAsync // 开启异步
@SpringBootApplication
public class SpringBootEventListenerApplication {

    public static void main(String[] args) {

        SpringApplication.run(SpringBootEventListenerApplication.class, args);
    }

}

Spring/SpringBoot 事件监听机制原理

Spring事件

Spring事件对象为ApplicationEvent,继承EventObject,源码如下:

public abstract class ApplicationEvent extends EventObject {

	/**
	 * Create a new ApplicationEvent.
	 * @param source the object on which the event initially occurred (never {@code null})
	 */
	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}

}

Spring事件监听器为ApplicationListener,继承EventListener, 源码如下:

public interface ApplicationListener extends EventListener {
    void onApplicationEvent(E var1);
}

实现Spring事件监听有两种方式:

面向接口编程,实现ApplicationListener接口;
基于注解驱动,@EventListener(Spring自定义的注解);
实例:

面向接口编程,实现ApplicationListener接口:
自定义事件对象:

public class MyApplicationEvent extends ApplicationEvent {
public MyApplicationEvent(Object source) {
super(source);
}
}
自定义事件监听器:

public class MyApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(MyApplicationEvent event) {
System.out.println(“收到事件:” + event);
}
}

启动服务并发布事件:

public class ApplicationEventBootstrap {

public static void main(String[] args) {
    AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext();
    // 注册自定义事件监听器
    context.addApplicationListener(new MyApplicationListener());
    // 启动上下文
    context.refresh();
    // 发布事件,事件源为Context
    context.publishEvent(new MyApplicationEvent(context));
    // 结束
    context.close();
}

}
运行结果:

收到事件:com.xx.MyApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@cb0ed20, started on Sat May 16 16:32:04 CST 2020]
2. 使用注解 @EventListener实现Spring事件监听:

@Component
public class MyApplicationListener2 {

@EventListener(MyApplicationEvent.class)
public void onEvent(MyApplicationEvent event) {
    System.out.println("收到事件:" + event);
}

}
启动并发布事件:

public class ApplicationEventBootstrap {

public static void main(String[] args) {
    AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext();
    // 注册自定义事件监听器
    context.register(MyApplicationListener2.class);
    // 启动上下文
    context.refresh();
    // 发布事件,事件源为Context
    context.publishEvent(new MyApplicationEvent(context));
    // 结束
    context.close();
}

}
运行结果:

收到事件:com.xx.MyApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@cb0ed20, started on Sat May 16 16:32:04 CST 2020]
通过实例可以看出,上面两种方式都可正常发布和接收事件。

实现原理
通过上面实例可以看出,context 可以发布事件,那底层是怎么发布的,让我们继续看源码:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {

getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

}
}
通过源码我们可以看出,事件应该是通过ApplicationEventMulticaster发布的,我们继续看:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster
Spring 中事件发布都是通过SimpleApplicationEventMulticaster来实现的

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
// 异步
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
可以看出,如果设置了Executor则异步发送,否则同步;而且可以看出通过 resolveDefaultEventType(event) 对发布的事件类型进行了校验,这就是为什么我们可以直接使用泛型来指定我们想接收的事件对象, 比如上面的ApplicationListener。

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
最后就使用对应的ApplicationListener进行接收和处理就行了,那么ApplicationListener是什么时候注册的呢?

如何添加ApplicationListener?

直接添加,使用content.addApplicationListener(上面实例中有使用);
将自定义的ApplicationListener注册为一个Bean,Spring再初始化Bean之后会添加,具体代码在ApplicationListenerDetector#postProcessAfterInitialization,判断一个Bean如果是ApplicationListener,则也是使用context.addApplicationListener添加;
使用注解@EventListener,在初始化Bean之后,会在EventListenerMethodProcessor中进行处理和添加;
第三种实现的源码如下(EventListenerMethodProcessor中):

private void processBean(final String beanName, final Class targetType) {

// 获取public 且有@EventListener的方法
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));

ApplicationListener applicationListener = factory.createApplicationListener(beanName, targetType, methodToUse);
// 添加监听器
context.addApplicationListener(applicationListener);
}
Spring内建事件
ContextRefreshedEvent: Spring应用上下文就绪事件;
ContextStartedEvent: Spring应用上下文启动事件;
ContextStopedEvent: Spring应用上下文停止事件;
ContextClosedEvent: Spring应用上下文关闭事件;
Spring Boot事件
Spring Boot事件是在Spring事件基础上进行的封装

public abstract class SpringApplicationEvent extends ApplicationEvent
事件对象改为SpringApplicationEvent,事件源为SpringApplication(Spring事件源为Context);

底层发布事件还是使用
SimpleApplicationEventMulticaster 对象,不过有点需要说明的是,Spring Boot 1.4开始,SpringApplication和ApplicationContext使用的都是
SimpleApplicationEventMulticaster实例,但是两者属于不同的对象(1.0 ~ 1.3版本是同一个对象);

事件回顾:
public class EventBootstrap {

public static void main(String[] args) {
    new SpringApplicationBuilder(Object.class)
            .listeners(event -> {
                System.out.println("事件对象:"
                + event.getClass().getSimpleName()
                + " ,事件源:" + event.getSource().getClass().getSimpleName());
            })
            .web(WebApplicationType.NONE)
            .run(args)
            .close();
}

}
运行结果:

事件对象:ApplicationContextInitializedEvent ,事件源:SpringApplication
事件对象:ApplicationPreparedEvent ,事件源:SpringApplication
事件对象:ContextRefreshedEvent ,事件源:AnnotationConfigApplicationContext
事件对象:ApplicationStartedEvent ,事件源:SpringApplication
事件对象:ApplicationReadyEvent ,事件源:SpringApplication
事件对象:ContextClosedEvent ,事件源:AnnotationConfigApplicationContext
从结果可以看出,事件对象类型和事件源,以及事件发布顺序。

你可能感兴趣的:(#,springBoot,java,开发语言,后端)