SpringBoot事件监听机制

观察者设计模式

主题/观察者模式,简称为观察者模式(Observer Pattern)是一种行为设计模式,也被称为发布/订阅(Publisher/Subscriber)模式。在该模式中,对象之间存在依赖关系,其中"被观察者"对象(Subject)维护一个观察者对象的列表,并在自身状态发生改变时通知所有观察者对象。观察者对象在接收到通知后会执行相应的操作。这种模式有助于实现松耦合的代码结构,特别是在需要处理对象状态改变并通知相关对象的场景中。

原理

  1. 被观察者(Subject):这是目标对象,可以有多个观察者订阅它的状态改变。

  2. 观察者(Observer):这是订阅被观察者状态的对象,当被观察者状态改变时,观察者会收到通知并执行相应的操作。

实例代码

下面是一个简单的Java实现,使用了接口和抽象类来简化观察者模式的实现。

java 复制代码
import java.util.ArrayList;
import java.util.List;

// 被观察者(Subject)抽象类
abstract class Subject {
    protected List<Observer> observers = new ArrayList<>();

    // 注册观察者
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    // 删除观察者
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    // 通知所有观察者
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }

    // 实际状态改变的方法
    protected void changeState() {
        System.out.println("状态改变");
        notifyObservers(); // 告知所有观察者状态已改变
    }
}

// 具体被观察者实现类
class ConcreteSubject extends Subject {
    // 状态
    private String state;

    // 其他状态管理方法...
}

// 观察者(Observer)抽象类
abstract class Observer {
    protected Subject subject;

    // 观察者初始化方法
    public Observer(Subject subject) {
        this.subject = subject;
        this.subject.registerObserver(this); // 注册观察者
    }

    // 被观察者状态改变时将调用此方法
    protected abstract void update();
}

// 具体观察者实现类
class ConcreteObserver extends Observer {
    // 具体的更新方法实现...
}

public class ObserverPatternDemo {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();

        ConcreteObserver observer1 = new ConcreteObserver(subject);
        ConcreteObserver observer2 = new ConcreteObserver(subject);

        // 状态改变后,所有观察者都会收到通知并执行 update 方法

        // 状态改变模拟,实际应用中可以是业务逻辑导致的状态改变
        subject.changeState();
    }
}

在这个例子中,ConcreteSubject 类是被观察者,它维护一个观察者列表,并在状态改变时通过调用 notifyObservers() 方法通知所有观察者。ConcreteObserver 类是观察者,它实现了 update 方法来处理状态改变。ObserverPatternDemo 类展示了如何使用这个模式。当 ConcreteSubject 的状态改变时,所有注册的观察者都会收到通知并执行相应的更新逻辑。

java 复制代码
package com.example.constraint.util.study;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

/**
 * 观察者模式测试类
 */
public class ObserverMode {
    public static void main(String[] args) {
        Event event = new Event();
        EventListener eventListener1 = new EventListener(event);
        EventListener eventListener2 = new EventListener(event);
        event.changeStatus(1);
        event.changeStatus(100);
    }
}

interface Subject {
    int getStatus();
    default void changeStatus(int status) {
        notifyObservers();
    }
    void addObserver(Observer observer);
    void notifyObservers();
}

interface Observer {
    void publish();
}

class Event implements Subject {

    private int status;
    private List<Observer> observers = new ArrayList<>();

    @Override
    public int getStatus() {
        return status;
    }

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

    @Override
    public void changeStatus(int status) {
        this.status = status;
        // 主题状态发生改变,通知观察者
        notifyObservers();
    }

    @Override
    public void notifyObservers() {
        observers.forEach(Observer::publish);
    }
}

@Slf4j
class EventListener implements Observer {
    private Subject subject;

    public EventListener(Subject subject) {
        this.subject = subject;
        this.subject.addObserver(this);
    }

    @Override
    public void publish() {
        log.info("event status changed -> {}", this.subject.getStatus());
    }
}

动态代理

JDK动态代理

java 复制代码
package com.example.constraint.util.study;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * jdk动态代理
 */
public class DynamicProxy {
    public static void main(String[] args) {
        LogManager logManager = new LogManager();
        LogUtil proxyInstance = (LogUtil) Proxy.newProxyInstance(
                LogManager.class.getClassLoader(),
                LogManager.class.getInterfaces(),
                new LogProxy(logManager));
        proxyInstance.printInfo();
        System.out.println(StringUtils.repeat("*", 12));
        proxyInstance.getThread();
    }
}

interface LogUtil {
    void printInfo();
    void getThread();
}

@Slf4j
class LogManager implements LogUtil {

    @Override
    public void printInfo() {
        log.info("log info");
    }

    @Override
    public void getThread() {
        log.info("thread - {}", Thread.currentThread().getId());
    }

}

@Slf4j
class LogProxy implements InvocationHandler {
    private Object target; // 被代理对象

    public LogProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("before log...");
        Object result = method.invoke(this.target);
        log.info("after log...");
        return result;
    }
}
text 复制代码
21:00:18.797 [main] INFO com.example.constraint.util.study.LogProxy - before log...
21:00:18.800 [main] INFO com.example.constraint.util.study.LogManager - log info
21:00:18.800 [main] INFO com.example.constraint.util.study.LogProxy - after log...
************
21:00:18.802 [main] INFO com.example.constraint.util.study.LogProxy - before log...
21:00:18.803 [main] INFO com.example.constraint.util.study.LogManager - thread - 1
21:00:18.803 [main] INFO com.example.constraint.util.study.LogProxy - after log...

CGLib动态代理

在Java中,CGlib是一个常用的类库,用于实现动态代理,尤其对于那些声明了接口(继承了某个接口或实现了某个接口)的类,因为Java语言不支持在运行时动态创建接口的实现类。下面是一个使用CGlib实现动态代理的简单示例:

引入依赖

xml 复制代码
<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.9</version> <!-- 或者你使用的版本 -->
    </dependency>
</dependencies>
java 复制代码
package com.example.constraint.util.study;

import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.apache.commons.lang.StringUtils;

import java.lang.reflect.Method;

/**
 * CGLib动态代理实现
 */
public class DynamicProxyCGLib {
    public static void main(String[] args) {
        InfoUtil infoUtil = new InfoUtil();
        InfoUtilProxy infoUtilProxy = new InfoUtilProxy(infoUtil);
        InfoUtil instance = (InfoUtil) infoUtilProxy.getInstance();
        instance.logInfo();
        System.out.println(StringUtils.repeat("*", 12));
        instance.getTime();
    }
}

@Slf4j
class InfoUtil {
    void logInfo() {
        log.info("info logging...");
    }

    void getTime() {
        log.info("currentTimeMillis {}", System.currentTimeMillis());
    }
}

@Slf4j
class InfoUtilProxy implements MethodInterceptor {
    private Object target;
    public InfoUtilProxy(Object target) {
        this.target = target;
    }

    public Object getInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        log.info("before log...");
        Object result = method.invoke(this.target);
        log.info("after log...");
        return result;
    }
}
text 复制代码
21:25:15.722 [main] INFO com.example.constraint.util.study.InfoUtilProxy - before log...
21:25:15.724 [main] INFO com.example.constraint.util.study.InfoUtil - info logging...
21:25:15.724 [main] INFO com.example.constraint.util.study.InfoUtilProxy - after log...
************
21:25:15.725 [main] INFO com.example.constraint.util.study.InfoUtilProxy - before log...
21:25:15.726 [main] INFO com.example.constraint.util.study.InfoUtil - currentTimeMillis 1723987515726
21:25:15.726 [main] INFO com.example.constraint.util.study.InfoUtilProxy - after log...

Spring事件监听

Spring Boot的事件监听机制主要利用了Spring的ApplicationEvent和ApplicationListener接口。以下是使用代码说明:

创建事件类

首先,我们需要创建一个事件类,该类需要继承自ApplicationEvent。

java 复制代码
import org.springframework.context.ApplicationEvent;

public class GreetingCreatedEvent extends ApplicationEvent {

    private String message;

    public GreetingCreatedEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

注册监听器

在Spring Boot的配置类中,我们可以使用@Component和@EventListener注解来注册监听器。

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;

@Configuration
public class GreetingEventListenerConfig {

    @EventListener
    public void handleGreetingCreatedEvent(GreetingCreatedEvent event) {
        String message = event.getMessage();
        System.out.println("Event handler called: " + message);
    }
}

触发事件

在你的业务逻辑中,你可以创建一个事件并调用publish方法来触发事件。

java 复制代码
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@Service
public class GreetingService {

    private final ApplicationEventPublisher publisher;

    public GreetingService(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void createGreeting(String message) {
        GreetingCreatedEvent event = new GreetingCreatedEvent(this, message);
        publisher.publishEvent(event);
    }
}

以上就是使用Spring Boot事件监听机制的完整代码说明,通过这种方式,可以创建和处理各种事件,实现系统之间的异步通信。

SpringBoot事件监听机制使用了什么设计模式?

Spring Boot事件监听机制主要使用了以下几种设计模式:

  1. 发布/订阅(Publisher/Subscriber)模式 :也称为主题/观察者模式。在这个模式中,事件(主题)的发布者(Publisher)不直接引用事件的订阅者(Subscriber)。一旦有事件发生,发布者会将事件广播给所有的订阅者。在Spring Boot中,ApplicationEventPublisher作为发布者,事件监听器作为订阅者,当事件被触发时,事件监听器接收到通知并进行相应的处理。

  2. 代理(Proxy)模式 : Spring Boot中的SimpleApplicationEventMulticaster作为代理类,实现了ApplicationEventMulticaster接口,负责将事件发送给相应的事件监听器。这种设计模式有助于实现事件的多播和实现类的封装。

  3. 接口隔离原则(ISP) :通过定义ApplicationEvent接口以及更多的专门的事件接口(如GreetingCreatedEvent),遵循了接口隔离原则。这样可以使得监听器只处理它感兴趣的具体事件,而不是被所有的事件类型所绑定,从而提高了系统的灵活性和扩展性。

  4. 依赖注入(Dependency Injection,DI) :在Spring Boot中,监听器通过在配置类中使用@Component@EventListener注解进行依赖注入,使得事件处理逻辑的注入和管理变得更加简单和灵活。这符合依赖注入的设计模式,有助于实现松耦合和易于维护的代码结构。

  5. 单例(Singleton)模式:Spring Boot中的事件监听器通常是单例的,这样可以确保在整个应用程序生命周期中只有一份实例,且所有需要处理事件的组件都可以通过依赖注入获取到这个实例。这有助于减少资源消耗和简化事件处理逻辑的管理。

通过这些设计模式的综合使用,Spring Boot事件监听机制实现了高效、灵活、可扩展的事件处理机制。

SpringBoot事件监听机制原理

Spring Boot事件监听机制是基于Spring的事件驱动架构和消息传递模型实现的。具体来说,它基于以下原理:

  1. 事件(ApplicationEvent) :Spring框架提供了一个基础的事件类ApplicationEvent,它是一个抽象类,所有事件都必须继承它。事件类包含一个对象参数,通常是触发事件的对象的引用。

  2. 监听器(ApplicationListener) :这是一个接口,所有处理事件的类都需要实现该接口。监听器接口中的一个方法onApplicationEvent会被调用,当有事件发生时,任何实现了这个接口的对象的这个方法都会被触发。

  3. 事件广播器(ApplicationEventPublisher) :这个接口允许你发出事件,然后所有注册的监听器都会被触发。Spring Boot应用程序上下文中缺省提供了一个实现这个接口的类ApplicationEventMulticaster

  4. 事件多播器(ApplicationEventMulticaster) :它负责将事件传递给所有注册的监听器。在Spring Boot中,这个多播器通常会使用SimpleApplicationEventMulticaster,它是一个实现简单,线程安全的事件多播器。

  5. 事件注册 :监听器注册通常在配置类中使用@Component和@EventListener注解实现。当一个类被标注为@Component,且包含一个实现ApplicationListener接口的方法,并且方法被@EventListener注解标注,那么这个类和方法就会被注册为事件监听器。

  6. 事件触发 :事件可以通过ApplicationEventPublisherpublishEvent方法触发。你可以使用publishEvent来触发由特定对象生成的特定事件。

通过这样的架构,可以实现事件驱动的设计,使得系统中的各个部分可以根据事件进行异步通信和协调。Spring Boot通过其内部实现方便地支持了这一模式,使得开发者可以很容易地在应用中引入事件处理机制。

@EventListener原理是什么

@EventListener是Spring框架中的一个注解,用于将类实例或方法标记为事件监听器。当事件(如ApplicationEvent的子类)被发布时,如果该事件类型与标注了@EventListener的方法的类型相匹配,那么Spring会调用该方法来处理事件。这种机制允许应用程序在事件发生时自动执行某些操作,而无需显式调用特定的方法。

@EventListener原理的简要说明:

  1. 事件发布 :事件通常是由Spring的ApplicationEventPublisher类或其他实现了ApplicationEventPublisher接口的类发布的。当需要触发事件时,这些发布者会创建一个事件对象,并使用publishEvent方法发布事件。

  2. 事件监听 :当事件被发布时,Spring会查找所有被@EventListener注解标记的方法。这些方法可以是类方法,也可以是静态方法。如果这些方法的类型与发布的事件类型匹配,那么它们就会被调用。

  3. 方法签名匹配 :Spring用来匹配事件和方法的规则是"最小匹配原则"。这意味着方法的参数列表(除了返回类型)必须与事件的类型完全匹配。例如,如果事件是MyEvent,那么被监听的方法的参数必须是MyEvent类型,或者可以是ApplicationEvent类型,因为MyEventApplicationEvent的子类。

  4. 事件处理:一旦找到匹配的方法,Spring会调用该方法,并传入事件对象作为参数。方法执行完毕后,事件处理过程结束。

示例代码:

java 复制代码
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

public class MyEventListener implements ApplicationListener<ContextRefreshedEvent> {

    private final ApplicationEventPublisher publisher;

    public MyEventListener(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 在这里处理事件,例如:
        System.out.println("Context has been refreshed.");
    }
}

在这个例子中,MyEventListener类中的onApplicationEvent方法被@EventListener注解标注,当应用上下文刷新时,这个方法会被调用。

通过使用@EventListener,开发者可以方便地实现事件驱动的编程模式,使得应用程序更加灵活和模块化。

@EventListener注解本身并未直接使用AOP(面向切面编程)。@EventListener是一个用于监听特定事件的注解,主要与Spring的事件发布和处理机制相结合。它允许开发者在特定事件发生时执行特定的代码逻辑,通常在事件处理逻辑与业务逻辑分离的场景下使用。

然而,@EventListener注解的实现和使用,确实可以间接依赖于AOP机制。这是因为Spring框架内部在处理事件时,可能使用了AOP技术来增强事件监听方法的行为。

在Spring中,事件处理通常涉及到以下几个环节:

  1. 事件发布 :当需要触发一个事件时,Spring会使用ApplicationEventPublisher或其子类来发布事件。

  2. 事件监听 :当事件被发布后,Spring会查找所有被@EventListener注解标记的方法以匹配发布的事件。这里的查找和匹配过程,从宏观上看,是Spring框架在进行的一种"增强"操作,实际上是在执行AOP的逻辑。

  3. 方法调用:一旦找到匹配的方法,Spring会调用这些方法来处理事件。这实际上是由Spring的容器在事件处理链中进行方法调用,这可以被视为AOP机制的一个应用实例,因为它涉及到方法调用的控制和增强。

然而,需要明确的是,@EventListener本身并不是实现AOP的API。而是Spring框架利用其内部的AOP机制来实现事件处理逻辑的透明性和耦合性降低。在Spring的事件处理机制中,AOP的主要作用在于动态地为事件监听方法添加处理逻辑,而不仅仅是作为事件监听的注解本身。

@EventListener的实现通常涉及到反射(Reflection)。在Spring框架中,当使用@EventListener注解时,Spring会通过反射来动态查找和匹配事件监听方法,并在特定的事件触发时调用这些方法。反射是Java语言的一个核心特性,允许程序在运行时通过其类的元数据信息(如类名、方法名、属性等)来动态地操作类和对象。

具体到@EventListener的使用情况,以下是对反射原理的简要说明:

  1. 事件监听类的扫描 :Spring在启动时会扫描所有被@EventListener注解的类,查找这些类中被注解的方法。

  2. 方法的类型匹配:对于每个被注解的方法,Spring会检查其参数类型以及方法签名,以确定该方法是否可以处理特定的事件类型。这里涉及到对元数据的直接操作,即通过反射来获取方法签名,包括参数类型和返回类型。

  3. 方法调用:一旦确定了方法可以处理某个事件,Spring会通过反射调用这个方法。反射在此处的应用允许在运行时动态地获取方法的参数,并将实际事件对象作为参数传递给方法。

这种使用反射的方式来处理事件监听,使得Spring能够实现动态的依赖注入、事件匹配和方法调用,而这些操作在运行时进行,增强了框架的灵活性和扩展性。通过反射,Spring能够在事件处理过程中实现高度的代码分离和模块化,使得开发者能够更加专注于业务逻辑,而无需担心事件处理细节。

Spring在查找所有被@EventListener注解的方法时,主要通过以下步骤和机制来实现:

  1. 扫描和加载:Spring框架在启动时,会进行组件扫描(Component Scan)以查找所有符合配置的bean定义。这个扫描过程是基于Java的反射机制进行的。

  2. 切片扫描 :在扫描范围内的类会被检查,Spring使用@Component@Service@Repository等注解标记的类作为组件候选。对于@EventListener注解,Spring同样会检查是否有这个注解被使用。

  3. 注解处理 :一旦发现带有@EventListener的类,Spring框架会读取这个注解,并根据注解的值(如果有的话)来查找特定的事件类型。例如,如果注解中指定了eventTypes的值,Spring会查找与这些事件类型匹配的方法。

  4. 方法匹配 :对于每个带有@EventListener注解的方法,Spring会检查方法签名和参数类型,以确定它是否能够处理特定的事件类型。如果有多个事件类型,Spring会尝试找到与所有事件类型相匹配的方法。

  5. 事件处理:当特定事件发生时,Spring会调用匹配到的方法。这里可能涉及到事件的序列化、方法的执行以及异常的处理。

  6. 依赖注入 :在执行方法之前,Spring会使用依赖注入机制来注入方法所需的任何依赖。如果方法中使用了@Autowired或其他依赖注入注解,Spring会负责为这些变量提供正确的值。

通过上述步骤,Spring能够高效地查找并执行所有被@EventListener注解的方法,从而实现事件驱动的设计模式。这种方式不仅使得代码更加模块化和易于维护,也使得事件处理逻辑更加灵活。

相关推荐
一只叫煤球的猫5 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9655 小时前
tcp/ip 中的多路复用
后端
bobz9656 小时前
tls ingress 简单记录
后端
皮皮林5517 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友7 小时前
什么是OpenSSL
后端·安全·程序员
bobz9657 小时前
mcp 直接操作浏览器
后端
前端小张同学9 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook9 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康10 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在10 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net