前言

本文主要是简略的叙述了Spring的事情机制,基本概念,叙述了事情机制的三要素事情、事情发布、事情监听器。怎么完结一个事情机制,运用的场景,调配@Async注解完结异步的操作等等。期望对咱们有所帮助。

Spring的事情机制的基本概念

Spring的事情机制是Spring框架中的一个重要特性,基于观察者模式完结,它能够完结运用程序中的解耦,提高代码的可维护性和可扩展性。Spring的事情机制包含事情、事情发布、事情监听器等几个基本概念。其间,事情是一个抽象的概念,它代表着运用程序中的某个动作或状态的产生。事情发布是事情产生的当地,它担任产生事情并告诉事情监听器。事情监听器是事情的接收者,它担任处理事情并履行相应的操作。在Spring的事情机制中,事情源和事情监听器之间经过事情进行通讯,然后完结了模块之间的解耦。

举个例子:用户修正暗码,修正完暗码后需求短信告诉用户,记载要害性日志,等等其他业务操作。

如下图,便是咱们需求调用多个服务来进行完结一个修正暗码的功用。

使用了Spring的事件机制真香!

运用了事情机制后,咱们只需求发布一个事情,无需关怀其扩展的逻辑,让咱们的事情监听器去处理,然后完结了模块之间的解耦。

使用了Spring的事件机制真香!

事情

经过承继ApplicationEvent,完结自界说事情。是对 Java EventObject 的扩展,表明 Spring 的事情,Spring 中的一切事情都要基于其进行扩展。其源码如下。

咱们能够获取到timestamp特点指的是产生时间。

使用了Spring的事件机制真香!

事情发布

事情发布是事情产生的当地,它担任产生事情并告诉事情监听器。ApplicationEventPublisher用于用于发布 ApplicationEvent 事情,发布后 ApplicationListener 才干监听到事情进行处理。源码如下。

需求一个ApplicationEvent,便是咱们的事情,来进行发布事情。

使用了Spring的事件机制真香!

事情监听器

ApplicationListener 是 Spring 事情的监听器,用来承受事情,一切的监听器都必须完结该接口。该接口源码如下。

使用了Spring的事件机制真香!

Spring的事情机制的运用办法

下面会给咱们演示怎么去运用Spring的事情机制。就拿修正暗码作为演示。

怎么界说一个事情

新增一个类,承继咱们的ApplicationEvent。

如下面代码,承继后界说了一个userId,有一个UserChangePasswordEvent办法。这儿就界说咱们监听器需求的业务参数,监听器需求那些参数,咱们这儿就界说那些参数。

/**
 * @Author JiaQIng
 * @Description 修正暗码事情
 * @ClassName UserChangePasswordEvent
 * @Date 2023/3/26 13:55
 **/
@Getter
@Setter
public class UserChangePasswordEvent extends ApplicationEvent {
    private String userId;
    public UserChangePasswordEvent(String userId) {
        super(new Object());
        this.userId = userId;
    }
}

怎么监听事情

完结监听器有两种办法

  1. 新建一个类完结ApplicationListener接口,并且重写onApplicationEvent办法。注入到Spring容器中,交给Spring办理。如下代码。新建了一个发送短信监听器,收到事情后履行业务操作。****
/**
 * @Author JiaQIng
 * @Description 发送短信监听器
 * @ClassName MessageListener
 * @Date 2023/3/26 14:16
 **/
@Component
public class MessageListener implements ApplicationListener<UserChangePasswordEvent> {
    @Override
    public void onApplicationEvent(UserChangePasswordEvent event) {
        System.out.println("收到事情:" + event);
        System.out.println("开端履行业务操作给用户发送短信。用户userId为:" + event.getUserId());
    }
}
  1. 运用 @EventListener 注解标示处理事情的办法,此时 Spring 将创立一个 ApplicationListener bean 方针,运用给定的办法处理事情。源码如下。参数能够给指定的事情。这儿巧妙的用到了@AliasFor的能力,放到了@EventListener身上 留意:一般主张都需求指定此值,不然默许能够处理一切类型的事情,范围太广了。

使用了Spring的事件机制真香!

代码如下。新建一个事情监听器,注入到Spring容器中,交给Spring办理。在指定办法上增加@EventListener参数为监听的事情。办法为业务代码。运用 @EventListener 注解的优点是一个类能够写很多监听器,定向监听不同的事情,或者同一个事情。

/**
 * @Author JiaQIng
 * @Description 事情监听器
 * @ClassName LogListener
 * @Date 2023/3/26 14:22
 **/
@Component
public class ListenerEvent {
    @EventListener({ UserChangePasswordEvent.class })
    public void LogListener(UserChangePasswordEvent event) {
        System.out.println("收到事情:" + event);
        System.out.println("开端履行业务操作生成要害日志。用户userId为:" + event.getUserId());
    }
    @EventListener({ UserChangePasswordEvent.class })
    public void messageListener(UserChangePasswordEvent event) {
        System.out.println("收到事情:" + event);
        System.out.println("开端履行业务操作给用户发送短信。用户userId为:" + event.getUserId());
    }
}
  1. @TransactionalEventListener来界说一个监听器,他与@EventListener不同的便是@EventListener标记一个办法作为监听器,他默许是同步履行,假如发布事情的办法处于业务中,那么业务会在监听器办法履行结束之后才提交。事情发布之后就由监听器去处理,而不要影响原有的业务,也便是说期望业务及时提交。咱们就能够运用该注解来标识。留意此注解需求spring-tx的依赖。

注解源码如下:主要是看一下注释内容。

// 在这个注解上面有一个注解:`@EventListener`,所以标明其实这个注解也是个事情监听器。 
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {
	/**
	 * 这个注解取值有:BEFORE_COMMIT(指定方针办法在业务commit之前履行)、AFTER_COMMIT(指定方针办法在业务commit之后履行)、
	 *	AFTER_ROLLBACK(指定方针办法在业务rollback之后履行)、AFTER_COMPLETION(指定方针办法在业务完结时履行,这儿的完结是指无论业务是成功提交仍是业务回滚了)
	 * 各个值都代表什么意思表达什么功用,非常明晰,
	 * 需求留意的是:AFTER_COMMIT + AFTER_COMPLETION是能够一起收效的
	 * AFTER_ROLLBACK + AFTER_COMPLETION是能够一起收效的
	 */
	TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
	/**
	 * 标明若没有业务的时分,对应的event是否需求履行,默许值为false表明,没业务就不履行了。
	 */
	boolean fallbackExecution() default false;
	/**
	 *  这儿巧妙的用到了@AliasFor的能力,放到了@EventListener身上
	 *  留意:一般主张都需求指定此值,不然默许能够处理一切类型的事情,范围太广了。
	 */
	@AliasFor(annotation = EventListener.class, attribute = "classes")
	Class<?>[] value() default {};
	/**
	 * The event classes that this listener handles.
	 * <p>If this attribute is specified with a single value, the annotated
	 * method may optionally accept a single parameter. However, if this
	 * attribute is specified with multiple values, the annotated method
	 * must <em>not</em> declare any parameters.
	 */
	@AliasFor(annotation = EventListener.class, attribute = "classes")
	Class<?>[] classes() default {};
	/**
	 * Spring Expression Language (SpEL) attribute used for making the event
	 * handling conditional.
	 * <p>The default is {@code ""}, meaning the event is always handled.
	 * @see EventListener#condition
	 */
	@AliasFor(annotation = EventListener.class, attribute = "condition")
	String condition() default "";
	/**
	 * An optional identifier for the listener, defaulting to the fully-qualified
	 * signature of the declaring method (e.g. "mypackage.MyClass.myMethod()").
	 * @since 5.3
	 * @see EventListener#id
	 * @see TransactionalApplicationListener#getListenerId()
	 */
	@AliasFor(annotation = EventListener.class, attribute = "id")
	String id() default "";
}

运用方法如下。phase业务类型,value指定事情。

/**
 * @Author JiaQIng
 * @Description 事情监听器
 * @ClassName LogListener
 * @Date 2023/3/26 14:22
 **/
@Component
public class ListenerEvent {
    @EventListener({ UserChangePasswordEvent.class })
    public void logListener(UserChangePasswordEvent event) {
        System.out.println("收到事情:" + event);
        System.out.println("开端履行业务操作生成要害日志。用户userId为:" + event.getUserId());
    }
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT,value = { UserChangePasswordEvent.class })
    public void messageListener(UserChangePasswordEvent event) {
        System.out.println("收到事情:" + event);
        System.out.println("开端履行业务操作给用户发送短信。用户userId为:" + event.getUserId());
    }
}

怎么发布一个事情

  1. 运用ApplicationContext进行发布,由于ApplicationContext 已经承继了 ApplicationEventPublisher ,因此能够直接运用发布事情。源码如下

使用了Spring的事件机制真香!

  1. 直接注入咱们的ApplicationEventPublisher,运用@Autowired注入一下。

三种发布事情的办法,我给咱们演示一下@Autowired注入的方法发布咱们的事情。

@SpringBootTest
class SpirngEventApplicationTests {
    @Autowired
    ApplicationEventPublisher appEventPublisher;
    @Test
    void contextLoads() {
        appEventPublisher.publishEvent(new UserChangePasswordEvent("1111111"));
    }
}

咱们履行一下看一下接口。

使用了Spring的事件机制真香!

测试成功。

调配@Async注解完结异步操作

监听器默许是同步履行的,假如咱们想完结异步履行,能够调配@Async注解运用,可是前提条件是你真的懂@Async注解,运用不当会呈现问题的。 后续我会出一篇有关@Async注解运用的文章。这儿就不给咱们具体的解释了。有想了解的同学能够去网上学习一下有关@Async注解运用。

运用@Async时,需求配置线程池,不然用的仍是默许的线程池也便是主线程池,线程池运用不当会浪费资源,严重的会呈现OOM事端。

下图是阿里巴巴开发手册的强制要求。

使用了Spring的事件机制真香!

简略的演示一下:这儿声明一下俺没有运用线程池,仅仅简略的演示一下。

  1. 在咱们的启动类上增加@EnableAsync开启异步履行配置
@EnableAsync
@SpringBootApplication
public class SpirngEventApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpirngEventApplication.class, args);
    }
}
  1. 在咱们想要异步履行的监听器上增加@Async注解。
/**
 * @Author JiaQIng
 * @Description 事情监听器
 * @ClassName LogListener
 * @Date 2023/3/26 14:22
 **/
@Component
public class ListenerEvent {
    @Async
    @EventListener({ UserChangePasswordEvent.class })
    public void logListener(UserChangePasswordEvent event) {
        System.out.println("收到事情:" + event);
        System.out.println("开端履行业务操作生成要害日志。用户userId为:" + event.getUserId());
    }
}

这样咱们的异步履行监听器的业务操作就完结了。

Spring的事情机制的运用场景

  1. 告警操作,比方钉钉告警,反常告警,能够经过事情机制进行解耦。
  2. 要害性日志记载和业务埋点,比方说咱们的要害日志需求入库,记载一下操作时间,操作人,改变内容等等,能够经过事情机制进行解耦。
  3. 功能监控,比方说一些接口的时长,功能便利的埋点等。能够经过事情机制进行解耦。
  4. …….一切与主业务无关的操作都能够经过这种方法进行解耦,常用的场景大概就上述提到的,并且很多架构的源码都有运用这种机制,如GateWay,Spring等等。

Spring的事情机制的留意事项

  1. 关于同一个事情,有多个监听器的时分,留意能够经过@Order注解指定次序,Order的value值越小,履行的优先级就越高。
  2. 假如发布事情的办法处于业务中,那么业务会在监听器办法履行结束之后才提交。事情发布之后就由监听器去处理,而不要影响原有的业务,也便是说期望业务及时提交。咱们就能够 @TransactionalEventListener来界说一个监听器
  3. 监听器默许是同步履行的,假如咱们想完结异步履行,能够调配@Async注解运用,可是前提条件是你真的懂@Async注解,运用不当会呈现问题的。
  4. 关于同一个事情,有多个监听器的时分,假如呈现了反常,后续的监听器就失效了,由于他是把同一个事情的监听器add在一个调集里边循环履行,假如呈现反常,需求留意捕获反常处理反常

跋文

此文章主要是解说什么是Spring的事情机制,怎么运用Spring事情机制,工作中的场景有哪些。

源码: github.com/hujiaqing78…

后续或许会出一篇关于运用Spring的事情机制加自界说注解完结操作日志的搜集。也或许会出一篇@Async注解的运用沉淀,自己很懒,看心情啦。

期望您能给个三连,这对我很重要,您的支撑是我创作的动力。谢谢。