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属性为truefalse来控制在事件监听器抛出异常时的行为。

@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());
    }

 接口可以正常访问

日志打印这一个监听器报错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w_t_y_y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值