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 分钟前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构
尘浮生11 分钟前
Java项目实战II基于微信小程序的电影院买票选座系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
郑祎亦34 分钟前
Spring Boot 项目 myblog 整理
spring boot·后端·java-ee·maven·mybatis
不是二师兄的八戒34 分钟前
本地 PHP 和 Java 开发环境 Docker 化与配置开机自启
java·docker·php
爱编程的小生1 小时前
Easyexcel(2-文件读取)
java·excel
本当迷ya1 小时前
💖2025年不会Stream流被同事排挤了┭┮﹏┭┮(强烈建议实操)
后端·程序员
带多刺的玫瑰1 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
计算机毕设指导62 小时前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
Gu Gu Study2 小时前
枚举与lambda表达式,枚举实现单例模式为什么是安全的,lambda表达式与函数式接口的小九九~
java·开发语言
Chris _data2 小时前
二叉树oj题解析
java·数据结构