Spring/SpringBoot面向事件驱动编程

Spring/SpringBoot面向事件驱动编程

  1. 事件监听机制可以理解为是一种观察者模式,有数据发布者(事件源)和数据接受者(监听器);
  2. 在Java中,事件对象都是继承java.util.EventObject对象,事件监听器都是java.util.EventListener实例;
  3. EventObject对象不提供默认构造器,需要外部传递source参数,即用于记录并跟踪事件的来源;

Spring/Spring Boot在面向事件驱动编程方面提供了以下内容:

  1. 事件(Events):Spring提供了一个基于观察者模式的事件模型。通过定义和发布事件,可以将不同组件之间解耦,使其能够以异步方式进行通信。
  2. 事件监听器(Event Listeners):通过实现ApplicationListener接口或使用@EventListener注解,可以创建事件监听器。这些监听器用于接收和处理发布的事件,并根据需要执行相应的操作。
  3. 事件发布器(Event Publisher):Spring框架提供了ApplicationEventPublisher接口,用于发布事件。可以通过调用publishEvent()方法来发布自定义的事件,从而通知所有注册的监听器。
  4. 领域事件(Domain Events):在面向领域驱动设计(Domain-Driven Design)的应用程序中,领域事件用于表示系统中重要的业务操作或状态变化。Spring框架提供了支持,允许开发人员将领域事件与应用程序的其他部分集成起来。
  5. 异步事件处理(Asynchronous Event Handling):Spring支持将事件处理设置为异步执行。可以使用@Async注解将事件处理方法标记为异步方法,从而使事件的处理在独立的线程中执行。
  6. 条件事件(Conditional Events):使用@Conditional注解,可以根据满足特定条件才触发事件。这样可以根据应用程序的状态或配置选择性地发布事件,从而更加灵活地控制事件的触发。

通过这些功能,Spring/Spring Boot的面向事件驱动编程能够实现松耦合、可扩展和易于维护的应用程序架构。开发人员可以使用事件和监听器来处理应用程序中的各种场景,例如状态变化、业务流程、异步消息处理等,从而提高系统的响应性和可测试性。

观察者模式

观察者模式的定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知

观察者模式角色

  1. Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  2. ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  3. Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  4. ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

Spring事件

事件机制实现方式 实现Spring事件机制主要有4个类:

  1. ApplicationEvent:事件,每个实现类表示一类事件,可携带数据。
  2. ApplicationListener:事件监听器,用于接收事件处理时间。
  3. ApplicationEventMulticaster:事件管理者,用于事件监听器的注册和事件的广播。
  4. ApplicationEventPublisher:事件发布者,委托ApplicationEventMulticaster完成事件发布。

EventObject 应用事件,职责为定义业务

只需要实现ApplicationEvent 抽象类定义有参构造函数即可,source表示事件源,( 可按照自己的需求制定)

EventObject对象不提供默认构造器,需要外部传递source参数,即用于记录并跟踪事件的来源; Spring事件对象为ApplicationEvent,继承EventObject,源码如下:

java 复制代码
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();
	}

}

ApplicationListener

事件监听器,职责为处理事件广播器发布的事件

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

java 复制代码
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}

并提供了两个实现:SmartApplicationListener和GenericApplicationListener接口

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

  1. 面向接口编程,实现ApplicationListener接口;
  2. 基于注解驱动,@EventListener(Spring自定义的注解);
java 复制代码
@Log4j2
@Component
public class AEventListener implements ApplicationListener<TestEvent> {
    @Override
    public void onApplicationEvent(TestEvent event) {
        //逻辑处理
    }
}

@Log4j2
@Component
public class AEventListener implements ApplicationListener<TestEvent> {
    @Async
    @EventListener
    public void listener(TestEvent event) throws InterruptedException {
        Thread.sleep(2000);
        log.info("监听到数据:{}", event.getMessage());
    }
}

SmartApplicationListener

提供了监听器对泛型事件的支持,spring3.0 添加的

java 复制代码
public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
    boolean supportsEventType(Class<? extends ApplicationEvent> var1);
    boolean supportsSourceType(Class<?> var1);
}

GenericApplicationListener

增强对泛型事件的支持(支持泛型方式不同与SmartApplicationListener),spring4.2 添加的。

源码:

java 复制代码
public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
    boolean supportsEventType(ResolvableType var1);
    boolean supportsSourceType(Class<?> var1);
}

ApplicationEventMulticaster

事件广播器,职责为将EventPubsher(事件发布者)发布的event 广播给事件EventListener(事件监听器)。

Spring提供了默认的实现SimpleApplicationEventMulticaster,如果用户没有配置自定义事件广播器, 则会默认使用SimpleApplicationEventMulticaster作为事件广播器。在容器刷新的过程中会实例化、初始化事件广播器。

ApplicationEventPublisher

事件发布者,职责为发布事件。 spring的ApplicationContext 本来就实现了ApplicationEventPublisher接口,因此应用上下文本来就是 一个事件发布者,在AbstractApplicationContext中实现了事件发布的业务。

发布方式1: 直接注入ApplicationContext:

java 复制代码
@Autowired
private ApplicationContext applicationContext;
applicationContext.publishEvent();

发布方式2:直接注入ApplicationEventPublisher:

java 复制代码
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
applicationEventPublisher.publishEvent(myEvent)

实例:

  1. 面向接口编程,实现ApplicationListener接口:

自定义事件对象:

java 复制代码
public class MyApplicationEvent extends ApplicationEvent {
    public MyApplicationEvent(Object source) {
        super(source);
    }
}

自定义事件监听器:

java 复制代码
@Component
public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> {
    @Override
    public void onApplicationEvent(MyApplicationEvent event) {
        System.out.println("收到事件:" + event);
    }
}

启动服务并发布事件:

java 复制代码
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]

  1. 通过@EventListener注解,该会根据方法参数类型来自动监听相应事件的发布
java 复制代码
@Component
@Slf4j
public class OrderCreateEventListener3 {

    @EventListener (classes = {OrderCreateEvent.class}) //classes属性指定处理事件的类型
    @Async //异步监听
    @Order(0)//使用order指定顺序,越小优先级越高
    public void eventListener(OrderCreateEvent event) {
        log.info("通过注解@EventListener和@Async,异步监听OrderCreateEvent事件,orderId:" + event.getOrder().getOrderNo());
    }
}

如果要监听多个事件类型的发布,可以在@EventListener(classes = {FaceEvent.class,ArmEvent.class})指定,spring会多次调用此方法来处理多个事件。但是注意此时,方法参数不能有多个,否则会发生转换异常,可以将使用多个事件的父类作为唯一的方法参数来接收处理事件,但除非必要否则并不推荐监听多个事件的发布。

如果有多个监听器监听同一事件,我们可以在方法上使用spring的@order注解来定义多个监听器的顺序,order越小,优先级越高.

@EventListener还有一个属性,condition()里可以使用SPEL表达式来过滤监听到事件,即只有符合某种条件的才进行接收处理。比如: @EventListener(condition = "event.message == 'message'")

监听多个事件:

java 复制代码
    @EventListener({FaceEvent.class,ArmEvent.class})
    public void onApplicationEvent3(Object event) {

        if(event instanceof FaceEvent){
            LOGGER.info("===> B 收到人脸事件:  {}",((FaceEvent) event).getEventData());
        }else if(event instanceof ArmEvent){
            ArmEvent armEvent = (ArmEvent) event;
            LOGGER.info("===> B 收到臂膀事件:  {}",armEvent.getEventData());
        }
    }

注意事项

  • 事件没要处理的监听器,就会被抛弃。
  • 一个事件可以同时被多个监听处理类监听处理。
  • 默认情况下事件是同步的,即事件被publish后会等待Listener的处理。如果发布事件处的业务存在事务,监听器处理也会在相同的事务中。
  • 如果对于事件的处理不想受到影响,可以onApplicationEvent方法上加@Async支持异步或者在有@EventListener的注解方法上加上@Async。注:启动类上同时要加上@EnableAsync

在需要触发事件的地方注入ApplicationEventPublisher,并发布自定义事件:

java 复制代码
@Service
public class MyService {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void doSomething() {
        // 执行业务逻辑
        // ...

        // 发布自定义事件
        CustomEvent event = new CustomEvent(this, "Custom event message");
        eventPublisher.publishEvent(event);
    }
}

运行结果:

收到事件:com.xx.MyApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@cb0ed20, started on Sat May 16 16:32:04 CST 2020]

通过实例可以看出,上面两种方式都可正常发布和接收事件。

利用@TransactionalEventListener实现监听事件时的事务隔离

很多时候,只有事务提交之后我们才会发布相应的事件处理其他逻辑,比如用户注册之后,发送邮件或者短信。这时候就可以用注解@TransactionalEventListener。

@TransactionalEventListener和@EventListener都可以监听事件,但前者可以对发布事件和监听事件进行一些事务上的隔离。

@TransactionalEventListener是对@EventListener的一个扩展,允许将事件的监听器绑定到事务的某个阶段。可以绑定到以下事务阶段:

  • AFTER_COMMIT (默认),事务提交后
  • AFTER_ROLLBACK ,事务回滚后
  • AFTER_COMPLETION ,事务完成,包括提交后和回滚后
  • BEFORE_COMMIT ,事务提交前

@TransactionalEventListener指不和发布事件的方法在同一个事务内,发布事件的方法事务结束后才会执行本监听方法,监听逻辑内发生异常不会回滚发布事件方法的事务。

@TransactionalEventListener有一个属性为fallbackExecution,默认为false,指发布事件的方法没有事务控制时,监听器不进行监听事件,此为默认情况!fallbackExecution=true,则指发布事件的方法没有事务控制时,监听方法仍可以监听事件进行处理。

@TransactionalEventListener@EventListener是Spring Framework中用于处理事件的注解,它们在功能和使用方式上有一些区别:

  1. 事务支持:最显著的区别是@TransactionalEventListener注解提供了对事务的支持。当使用@TransactionalEventListener注解标记事件处理方法时,如果当前存在活动的事务,则事件处理方法将在事务内执行;如果没有事务,则会创建一个新的事务来执行事件处理方法。这确保了事件处理方法与其他数据库操作之间的一致性。而@EventListener注解的事件处理方法默认不受事务控制。
  2. 事务传播行为:@TransactionalEventListener注解还允许指定事务的传播行为(propagation)和事务的隔离级别(isolation level)。可以通过设置transactional属性来指定所需的事务传播行为和隔离级别。这使得可以在事件处理方法中定义更复杂的事务语义。
  3. 监听器顺序:@TransactionalEventListener注解还允许设置监听器的执行顺序(order)。可以使用phase属性来指定监听器方法的执行顺序,按照数字从小到大的顺序执行。而@EventListener注解的事件处理方法没有直接支持指定执行顺序的功能,但可以通过实现Ordered接口或使用@Order注解来达到相同的效果。

综上所述,@TransactionalEventListener注解相比于@EventListener注解提供了更强大的事务支持和控制。如果你需要在事件处理方法中保证事务的一致

Spring 中的事件机制 ApplicationEventPublisher

ApplicationContext 通过 ApplicationEvent 类和 ApplicationListener 接口进行事件处理。 如果将实现 ApplicationListener 接口的 bean 注入到上下文中,则每次使用 ApplicationContext 发布 ApplicationEvent 时,都会通知该 bean。本质上,这是标准的观察者设计模式。

  • 启动类添加注解@EnableAsync,开启 Spring 异步的功能
  • 创建用户注册事件类(UserRegisterEvent):可以自定义复杂对象
java 复制代码
public class UserRegisterEvent extends ApplicationEvent {
	
   /**
    * 用户名
    */
    private String username;

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

    public String getUsername() {
        return username;
    }
}
  • 创建用户 Service(UserService )
java 复制代码
@Service
@Slf4j
public class UserService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

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

    @Async
    public void register(String username) {
        log.info("新用户:" + username + ",进行注册...");
        System.out.println("------------------------------------------------------------------------------------------------------------------------------");
        //触发(发布)事件
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }

}

需要注意的是,服务必须交给 Spring 容器托管。ApplicationEventPublisherAware 是由 Spring 提供的用于为 Service 注入 ApplicationEventPublisher 事件发布器的接口,使用这个接口,我们自己的 Service 就拥有了发布事件的能力。用户注册后,不再是显示调用其他的业务 Service,而是发布一个用户注册事件。

创建邮件服务,积分服务,其他服务(事件订阅者)等:

java 复制代码
@Service
@Slf4j
public class EmailService implements ApplicationListener<UserRegisterEvent> {

    @Override
    @Async
    public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
        log.info("给用户 " + userRegisterEvent.getUsername() + " 发送邮箱...");
        System.out.println("------------------------------------------------------------------------------------------------------------------------------");
    }
}


@Service
@Slf4j
public class MsgService {

    @EventListener
    @Async
    public void addCoupon(UserRegisterEvent userRegisterEvent) {
        log.info("给用户 " + userRegisterEvent.getUsername() + " 发送短信...");
        System.out.println("------------------------------------------------------------------------------------------------------------------------------");
    }

}

事件订阅者的服务同样需要托管于 Spring 容器,ApplicationListener 接口是由 Spring 提供的事件订阅者必须实现的接口,我们一般把该 Service 关心的事件类型作为泛型传入。处理事件,通过 event.getSource() 即可拿到事件的具体内容

实现原理

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

java 复制代码
public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {
      protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        ...
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        ...
      }
}

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

java 复制代码
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster

Spring 中事件发布都是通过SimpleApplicationEventMulticaster来实现的

java 复制代码
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<MyApplicationEvent>

java 复制代码
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			listener.onApplicationEvent(event);

最后就使用对应的ApplicationListener进行接收和处理就行了,那么ApplicationListener是什么时候注册的呢?

如何添加ApplicationListener?

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

第三种实现的源码如下(EventListenerMethodProcessor中):

java 复制代码
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事件基础上进行的封装

java 复制代码
public abstract class SpringApplicationEvent extends ApplicationEvent

事件对象改为SpringApplicationEvent,事件源为SpringApplication(Spring事件源为Context);

底层发布事件还是使用

SimpleApplicationEventMulticaster 对象,不过有点需要说明的是,Spring Boot 1.4开始,SpringApplication和ApplicationContext使用的都是

SimpleApplicationEventMulticaster实例,但是两者属于不同的对象(1.0 ~ 1.3版本是同一个对象);

相关推荐
用户908324602731 天前
Spring AI 1.1.2 + Neo4j:用知识图谱增强 RAG 检索(上篇:图谱构建)
java·spring boot
用户8307196840822 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
Java水解2 天前
Spring Boot 视图层与模板引擎
spring boot·后端
Java水解2 天前
一文搞懂 Spring Boot 默认数据库连接池 HikariCP
spring boot·后端
洋洋技术笔记2 天前
Spring Boot Web MVC配置详解
spring boot·后端
初次攀爬者3 天前
Kafka 基础介绍
spring boot·kafka·消息队列
用户8307196840823 天前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
Java水解3 天前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端