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版本是同一个对象);

相关推荐
小小药2 分钟前
009-spring-bean的实例化流程
java·数据库·spring
GraduationDesign34 分钟前
基于SpringBoot的蜗牛兼职网的设计与实现
java·spring boot·后端
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS安康旅游网站(JAVA毕业设计)
java·vue.js·spring boot·后端·kafka·开源·旅游
herogus丶2 小时前
【Spring AI】Spring AI Alibaba的简单使用
java·人工智能·spring·ai
罗政4 小时前
PDF书籍《手写调用链监控APM系统-Java版》第10章 插件与链路的结合:SpringBoot环境插件获取应用名
java·spring boot·pdf
sin22014 小时前
springboot测试类里注入不成功且运行报错
spring boot·后端·sqlserver
kirito学长-Java5 小时前
springboot/ssm网上宠物店系统Java代码编写web宠物用品商城项目
java·spring boot·后端
奋斗的老史5 小时前
Spring Retry + Redis Watch实现高并发乐观锁
java·redis·spring
键盘侠0075 小时前
springboot 上传图片 转存成webp
android·spring boot·okhttp
AI人H哥会Java6 小时前
【Spring】基于XML的Spring容器配置——<bean>标签与属性解析
java·开发语言·spring boot·后端·架构