业务解耦-Spring事件监听的三种实现方式
实现ApplicationListener
步骤如下:
1.写Event类,需要继承Spring的ApplicationEvent类
2.写监听类,需要实现Spring的ApplicationListener接口,加上@Component注解
3.监听类实现onApplicationEvent方法
4.通过ApplicationContext.publishEvent(Event)发布事件
Event的代码如下:
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后执行
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));
}
}
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));
}
}
发布事件的方法如下:
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注解定义了顺序,数字越小优先级越高,越优先执行
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 ;
}
}
发布事件的方法如下:
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注解来启用异步)
@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注解的内容:
/*
* 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的代码展示