spring模块(六)spring event事件(3)广播与异步问题
发布事件和监听器之间默认是同步的;监听器则是广播形式。demo:
event:
package com.listener.demo.event;
import com.listener.demo.dto.UserLogDTO;
import org.springframework.context.ApplicationEvent;
public class MyLogEvent extends ApplicationEvent {
public MyLogEvent(UserLogDTO log) {
super(log);
}
public UserLogDTO getSource() {
return (UserLogDTO) super.getSource();
}
}
producer:
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Resource
private ApplicationContext applicationContext;
@MyLog(url = "/user/add",
detail = "addUser")
@RequestMapping("/add")
public String add(UserDTO userDTO) {
this.notifyEvent(userDTO);
log.info("请求成功,返回");
return "add success";
}
private void notifyEvent(UserDTO userDTO) {
//触发listener
UserLogDTO userLogDTO = UserLogDTO.builder()
.detail("新增"+userDTO.getUserAccount())
.url("/user/add")
.build();
applicationContext.publishEvent(new MyLogEvent(userLogDTO));
}
@MyLog(url = "/user/update",detail = "updateUser")
@RequestMapping("/update")
public String update() {
return "update success";
}
}
监听器:
package com.listener.demo.listener;
import com.listener.demo.dto.UserLogDTO;
import com.listener.demo.event.MyLogEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MyListenerOne {
@EventListener
public void myEventListener(MyLogEvent event) {
UserLogDTO source = event.getSource();
log.info("监听到:url={},detail={}",source.getUrl(),source.getDetail());
//其他处理,比如存储日志
}
@EventListener
public void contextRefreshedEventListener(ContextRefreshedEvent event) {
log.info("监听到内置事件ContextRefreshedEvent...");
}
}
目录
一、广播
二、监听器异常
三、验证同步和异步
1、默认同步
2、异步
一、广播
对于同一个Event,我们可以定义多个Listener,多个Listener之间可以通过@Order来指定顺序,order的Value值越小,执行的优先级就越高。
下面对同一个事件加上多个监听器,copy MyListenerOne为MyListenerTwo。访问接口日志打印:
2024-07-29T09:48:14.818+08:00 INFO 46376 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 4444 (http) with context path '/listenerDemo'
2024-07-29T09:48:14.824+08:00 INFO 46376 --- [ main] c.listener.demo.listener.MyListenerOne : 监听到内置事件ContextRefreshedEvent...
2024-07-29T09:48:14.824+08:00 INFO 46376 --- [ main] c.listener.demo.listener.MyListenerTwo : 监听到内置事件ContextRefreshedEvent...
2024-07-29T09:48:14.825+08:00 INFO 46376 --- [ main] com.listener.demo.ListenerApplication : Started ListenerApplication in 1.222 seconds (process running for 1.678)
2024-07-29T09:48:22.619+08:00 INFO 46376 --- [nio-4444-exec-1] o.a.c.c.C.[.[localhost].[/listenerDemo] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-07-29T09:48:22.619+08:00 INFO 46376 --- [nio-4444-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2024-07-29T09:48:22.620+08:00 INFO 46376 --- [nio-4444-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms
2024-07-29T09:48:22.646+08:00 INFO 46376 --- [nio-4444-exec-1] c.l.demo.controller.UserController : 请求成功,返回
2024-07-29T09:48:28.656+08:00 INFO 46376 --- [ task-1] c.listener.demo.listener.MyListenerOne : 监听到:url=/user/add,detail=新增zs
2024-07-29T09:48:28.656+08:00 INFO 46376 --- [ task-2] c.listener.demo.listener.MyListenerTwo : 监听到:url=/user/add,detail=新增zs
可以看到多个listener都监听到了,是广播的形式。
二、监听器异常
在某一个Listener加入异常代码
@EventListener
public void myEventListener(MyLogEvent event) throws InterruptedException {
//下游业务处理
//Thread.sleep(6000);
int a = 1/0;
UserLogDTO source = event.getSource();
log.info("监听到:url={},detail={}",source.getUrl(),source.getDetail());
}
接口调用也异常
对于事件监听器(EventListener)抛出异常导致接口异常,可以采取以下几种策略来解决:
1、监听器加异常处理
在事件监听器中添加try-catch块来捕获并处理可能发生的异常
@EventListener
public void handleEvent(SomeEvent event) {
try {
// 事件处理逻辑
} catch (Exception e) {
// 记录日志或者进行其他处理
}
}
2、阻止异常抛出
使用@TransactionalEventListener
时,设置fallbackExecution
属性为true
或false
来控制在事件监听器抛出异常时的行为。
@TransactionalEventListener(fallbackExecution = true)
public void handleEvent(SomeEvent event) {
// 事件处理逻辑
}
3、使用ApplicationEventMulticaster
的事件传播策略来控制事件监听器的异常行为。
@Autowired
private ApplicationEventMulticaster multicaster;
@PostConstruct
public void setTaskExecutionListenerMulticaster() {
multicaster.setErrorHandler(new ErrorHandler() {
@Override
public void handleError(Throwable t) {
// 处理异常
}
});
}
4、异步
使用@Async
注解来异步执行事件监听器,从而避免监听器内的异常影响主线程。
@Async
@EventListener
public void handleEvent(SomeEvent event) {
// 事件处理逻辑
}
三、验证同步和异步
1、默认同步
触发event,监听器和调用处是同步执行的,调用处-->listen执行-->调用处;
package com.listener.demo.controller;
import com.listener.demo.annotation.MyLog;
import com.listener.demo.dto.UserDTO;
import com.listener.demo.dto.UserLogDTO;
import com.listener.demo.event.MyLogEvent;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Resource
private ApplicationContext applicationContext;
/*@MyLog(url = "/user/add",
detail = "addUser")*/
@RequestMapping("/add")
public String add(UserDTO userDTO) {
this.notifyEvent(userDTO);
log.info("请求成功,返回");
return "add success";
}
private void notifyEvent(UserDTO userDTO) {
//触发listener
UserLogDTO userLogDTO = UserLogDTO.builder()
.detail("新增"+userDTO.getUserAccount())
.url("/user/add")
.build();
applicationContext.publishEvent(new MyLogEvent(userLogDTO));
}
@MyLog(url = "/user/update",detail = "updateUser")
@RequestMapping("/update")
public String update() {
return "update success";
}
}
package com.listener.demo.listener;
import com.listener.demo.dto.UserLogDTO;
import com.listener.demo.event.MyLogEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MyListenerOne {
@EventListener
public void myEventListener(MyLogEvent event) throws InterruptedException {
//下游业务处理
Thread.sleep(6000);
UserLogDTO source = event.getSource();
log.info("监听到:url={},detail={}",source.getUrl(),source.getDetail());
}
@EventListener
public void contextRefreshedEventListener(ContextRefreshedEvent event) {
log.info("监听到内置事件ContextRefreshedEvent...");
}
}
调用接口到返回的时间很长,日志打印
2024-07-29T09:42:02.161+08:00 INFO 29800 --- [nio-4444-exec-7] c.listener.demo.listener.MyListenerOne : 监听到:url=/user/add,detail=新增zs
2024-07-29T09:42:02.161+08:00 INFO 29800 --- [nio-4444-exec-7] c.l.demo.controller.UserController : 请求成功,返回
2、异步
如果需要异步执行,需要单独加上异步代码:
package com.listener.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@EnableAsync
@SpringBootApplication
public class ListenerApplication {
public static void main(String[] args) {
SpringApplication.run(ListenerApplication.class, args);
}
}
@EventListener
@Async()
public void myEventListener(MyLogEvent event) throws InterruptedException {
//下游业务处理
Thread.sleep(6000);
UserLogDTO source = event.getSource();
log.info("监听到:url={},detail={}",source.getUrl(),source.getDetail());
}
再次访问打印
2024-07-29T09:45:00.049+08:00 INFO 49128 --- [nio-4444-exec-3] c.l.demo.controller.UserController : 请求成功,返回
2024-07-29T09:45:06.059+08:00 INFO 49128 --- [ task-1] c.listener.demo.listener.MyListenerOne : 监听到:url=/user/add,detail=新增zs
这时候在某一个监听器加入异常代码:
@EventListener
@Async()
public void myEventListener(MyLogEvent event) throws InterruptedException {
//下游业务处理
//Thread.sleep(6000);
int a = 1/0;
UserLogDTO source = event.getSource();
log.info("监听到:url={},detail={}",source.getUrl(),source.getDetail());
}
接口可以正常访问
日志打印这一个监听器报错