业务解耦-Spring事件监听的三种实现方式

实现ApplicationListener

步骤如下:

1.写Event类,需要继承Spring的ApplicationEvent类

2.写监听类,需要实现Spring的ApplicationListener接口,加上@Component注解

3.监听类实现onApplicationEvent方法

4.通过ApplicationContext.publishEvent(Event)发布事件

Event的代码如下:

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

@Getter
@Setter
public class CustomEvent extends ApplicationEvent  {

    private String message;

    public CustomEvent(Object source) {
        super(source);
    }

}

两个Listener的代码如下。这里定义了两个Listener,@Order定义了执行顺序,其中CustomEventListener2先执行,CustomEventListener1后执行

java 复制代码
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
 
@Component
@Slf4j
@Order(2)
public class CustomEventListener1 implements ApplicationListener<CustomEvent> {

    @Override
    public void onApplicationEvent(CustomEvent event) {
        log.info ("CustomEventListener1 received: {}", JSON.toJSONString(event));
    }

}
java 复制代码
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Component
@Slf4j
@Order(1)
public class CustomEventListener2  implements ApplicationListener<CustomEvent> {

    @Override
    public void onApplicationEvent(CustomEvent event) {
        log.info ("CustomEventListener2 received: {}", JSON.toJSONString(event));
    }
}

发布事件的方法如下:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;


@Service
public class SpringListenerService {

    @Autowired
    private ApplicationContext applicationContext;


    public void customerListener() {
        CustomEvent event = new CustomEvent("CustomEventSource");
        event.setMessage("CustomEvent");
        applicationContext.publishEvent(event);
    }
}

启动项目调用SpringListenerService.customerListener方法后日志打印如下图,可以看到是按照Order定义的顺序执行的

需要注意的是:整个调用链都是同步执行的,如果某个Listener抛出了异常,那么后续的Listener也不会继续执行,而发布事件所在的方法也会受影响抛出异常。

如果某个Listener想要异步执行,可以在相应的onApplicationEvent方法上加上@Async注解(应用启动类上需要加上@EnableAsync注解来启用异步)

@EventListener

步骤如下:

1.写Event类,普通的POJO即可。

2.写监听类,加上@Component注解,即需要被Spring扫描到。

3.在监听类上写监听方法,需要加上@EventListener注解。方法的参数是Event类。

4.通过ApplicationContext.publishEvent(Event)发布事件,通过ApplicationEventPublisher发布事件也行

)。

Listener代码如下:定义了两个加@EventListener注解的方法,这两个方法都是监听的UserEvent,所以用@Order注解定义了顺序,数字越小优先级越高,越优先执行

java 复制代码
import com.alibaba.fastjson2.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Component
@Slf4j
public class UserEventListener {

    //使用注解
    @EventListener
    //定义执行顺序
    @Order(2)
    public void receiveEvent2(UserEvent userEvent) {
        log.info("receiveEvent Order 2: {}", JSON.toJSONString(userEvent));
    }

    @EventListener
    @Order(1)
    public void receiveEvent(UserEvent userEvent) {
        log.info("receiveEvent Order 1 end: {}", JSON.toJSONString(userEvent));
    }


    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class UserEvent {
        private String name ;
    }
}

发布事件的方法如下:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

/**
 * @Description
 * @ClassName SpringListenerService
 * @Date 2024/8/29 23:01
 */
@Service
public class SpringListenerService {

    @Autowired
    private ApplicationContext applicationContext;

    public void eventListener() {
        UserEventListener.UserEvent eventListener = UserEventListener.UserEvent.builder().name("eventListener").build();
        applicationContext.publishEvent(eventListener);
    }
}

启动项目调用SpringListenerService.eventListener方法后日志打印如下图,可以看到是按照Order定义的顺序执行的

需要注意的是:整个调用链同样都是同步执行的,如果某个Listener抛出了异常,那么后续的Listener也不会继续执行,而发布事件所在的方法也会受影响抛出异常。

如果某个Listener想要异步执行,可以在相应的方法上加上@Async注解(应用启动类上需要加上@EnableAsync注解来启用异步)

java 复制代码
@EventListener
//异步执行
@Async
@Order(1)
public void receiveEvent(UserEvent userEvent) {
    log.info("receiveEvent Order 1 end: {}", JSON.toJSONString(userEvent));
}

@TransactionalEventListener

以上介绍的两种实现方式,在处理某些场景的时候会有问题:如果Listener的代码异常不需要影响发布事件所在方法,就需要采用异步的方式。但是,如果发布事件所在的方法中存在事务,那么,Listener执行的时候,该事物可能还未提交,那么Listener中就会查不到相应的数据。

这时候@TransactionalEventListener就能完美解决这个问题,它可以控制在事务的哪个阶段去执行监听。需要注意的是在Spring4.2+才有

先看下@TransactionalEventListener注解的内容:

java 复制代码
/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.transaction.event;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AliasFor;

/**
 * An {@link EventListener} that is invoked according to a {@link TransactionPhase}.
 *
 * <p>If the event is not published within an active transaction, the event is discarded
 * unless the {@link #fallbackExecution} flag is explicitly set. If a transaction is
 * running, the event is processed according to its {@code TransactionPhase}.
 *
 * <p>Adding {@link org.springframework.core.annotation.Order @Order} to your annotated
 * method allows you to prioritize that listener amongst other listeners running before
 * or after transaction completion.
 *
 * @author Stephane Nicoll
 * @author Sam Brannen
 * @since 4.2
 */
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {

	/**
	 * Phase to bind the handling of an event to.
	 * <p>The default phase is {@link TransactionPhase#AFTER_COMMIT}.
	 * <p>If no transaction is in progress, the event is not processed at
	 * all unless {@link #fallbackExecution} has been enabled explicitly.
	 */
	TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;

	/**
	 * Whether the event should be processed if no transaction is running.
	 */
	boolean fallbackExecution() default false;

	/**
	 * Alias for {@link #classes}.
	 */
	@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
	 */
	String condition() default "";

}

如果英语好的可以看源码的代码注释。

首先该注解添加了@EventListener注解,可见它是@EventListener的加强版

下面对一些重要的属性做解释。

phase:

这个注解取值有:BEFORE_COMMIT(事务提交前)、AFTER_COMMIT(事务提交后)、AFTER_ROLLBACK(事务回滚后)、AFTER_COMPLETION(事务完成时,无论是事务成功提交还是事务回滚)。默认值是BEFORE_COMMIT。

所以刚才提到的那种场景,在事务提交后才触发事件监听,我们可以用phase的默认属性AFTER_COMMIT即可。用该属性值,方法里有异常也不会影响发布事件所在的代码。

需要注意的是:BEFORE_COMMIT是在事务提交前执行的,所以如果出现了异常,也会影响发布事件所在的代码。如果BEFORE_COMMIT所指定的方法是异步执行的,那么可能出现在监听处查不到数据的情况,因为事务还可能未提交。

fallbackExecution:

用于指定:如果没有事务,是否执行相应的事务事件监听器。这个属性用处还是比较大的,如果在发布事件的位置没有事务,就可以指定该属性值为true。该属性默认值为false,如果你发布的事件,监听不到,请仔细检查发布事件位置是否存在事务!!!!但是需要注意:如果指定为true,且发布事件的位置没有事务,监听的异常是会影响发布事件所在方法的代码的(此时等同于 @EventListener**)!!!!如果同时指定了异步,就不会影响!!!**

代码使用方式跟@EventListener一致,只不过将注解换成了@TransactionalEventListener,这里不在做@TransactionalEventListener的代码展示

相关推荐
WaaTong3 分钟前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
m0_743048443 分钟前
初识Java EE和Spring Boot
java·java-ee
AskHarries5 分钟前
Java字节码增强库ByteBuddy
java·后端
小灰灰__25 分钟前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
夜雨翦春韭28 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
程序媛小果1 小时前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林1 小时前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨1 小时前
El表达式和JSTL
java·el
duration~2 小时前
Maven随笔
java·maven
zmgst2 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql