SpringBoot Spring Event 业务解耦神器

介绍

Spring Event是Spring框架中的一个事件机制,主要用于实现应用程序内部的事件传递与处理,它允许不同组件之间通过发布-订阅机制进行解耦通信,比如用户注册,订单创建,订单状态变更,通知等。Spring Event也是观察者模式的一种实现,所以Spring Event主要是由三部分来实现,分别是:事件(ApplicationEvent)监听器(ApplicationListener)事件发布操作。

应用场景

  • 用户行为跟踪:可以使用 Spring Event 来跟踪用户在应用程序中的行为,例如登录、注册、购买等操作,从而实现用户行为分析和个性化推荐等功能。
  • 数据同步更新:可以使用 Spring Event 来实现数据的同步更新,例如,在分布式系统中,当某个节点的数据发生变化时,可以触发事件并将事件传递给其他节点,从而实现数据的同步更新。
  • 异步任务处理:可以使用 Spring Event 来实现异步任务的处理,例如,在 Web 应用程序中,可以使用异步 Servlet 和 Spring Event 来处理异步请求,从而提高应用程序的性能和并发能力。
  • 领域事件处理:可以使用 Spring Event 来实现领域事件的处理,例如,在订单管理系统中,可以使用 Spring Event 来处理订单状态的变化,从而触发相关的业务逻辑。
  • 消息通知和推送:可以使用 Spring Event 来实现消息的通知和推送,例如,在在线聊天应用程序中,可以使用 Spring Event 来实现消息的推送和实时通知。

Spring Event 可以在应用程序中实现事件的传递和处理,提高应用程序的性能、并发能力和灵活性,同时也能够实现一些复杂的业务逻辑和功能

优点

在代码中直接调用其他业务的接口是一种紧密耦合的方式,这会增加代码的复杂度、降低代码的可维护性和可扩展性。而使用 Spring Event 可以将不同业务之间的耦合度降到最低,提高应用程序的灵活性和可扩展性。具体来说,使用 Spring Event 有以下几个好处

  • 松散耦合:使用 Spring Event 可以将不同业务之间的耦合度降到最低,每个业务只需要关注自己感兴趣的事件,而不需要了解其他业务的具体实现。
  • 解耦业务逻辑:使用 Spring Event 可以将业务逻辑和事件处理逻辑解耦,每个业务只需要关注自己的业务逻辑,而不需要关注事件的处理逻辑,从而提高代码的可维护性和可扩展性。
  • 提高代码复用性:使用 Spring Event 可以将事件的处理逻辑封装成可重用的组件,多个业务可以共享同一个事件处理逻辑,从而提高代码的复用性。
  • 降低测试成本:使用 Spring Event 可以将不同业务之间的测试隔离开来,每个业务只需要关注自己的测试,而不需要关注其他业务的测试,从而降低测试成本。
  • 提高代码可读性:使用 Spring Event 可以将事件的触发和处理逻辑清晰地分离出来,从而提高代码的可读性和可理解性。

实例-同步事件

1、定义用户注册事件

注意,自定义事件需要继承自ApplicationEvent

public class UserRegisterEvent extends ApplicationEvent {

    private User user;

    public UserRegisterEvent(Object source, User user){
        super(source);
        this.user = user;
    }
}

2、定义用户事件监听

注意:事件监听需要在方法上面标注@EventListener注解

@Component
public class UserEventListener {

    @Resource
    private MailService mailService;

    @Resource
    private PointService pointService;

    @EventListener
    public void userRegisterEventLister(UserRegisterEvent userRegisterEvent){
        System.out.println("监听到用户注册");
        //进行邮件注册通知
        mailService.sendRegisterSuccessMail();
        //进行积分发放
        pointService.userRegisterPoint();
    }
}

3、用户注册成功并发布注册成功事件

注意:在springboot中需要使用ApplicationEventPublisher类来进行发布用户注册事件

@Service
public class UserService {

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    public void register(User user){
        System.out.println("用户注册成功");
        // 发布用户注册成功事件
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, user));
    }
}

4、用户注册调用与验证

@RestController
public class HomeController {

    @Resource
    private UserService userService;

    @PostMapping("/register")
    public String register(@RequestBody User user){
        userService.register(user);
        return "register success";
    }
}

访问输出日志为:

用户注册成功
监听到用户注册
发送注册成功邮件
用户注册成功发放积分

这样一个用户注册的应用场景就完成,使用了Spring Event来对主要用户注册主要逻辑与发送邮件,发送积分等次要逻辑进行解耦,从而形成了次要逻辑可拔插的效果。

实例-异步事件

上面实现了同步事件的逻辑,那这里就会出现一个问题,如果次要逻辑是否成功都不影响主逻辑的执行或者次要逻辑是较为耗时的操作,那么用户的请求等待时间将会比较长,因此可以将次要逻辑进行异步处理,将继续使用上面的例子,将其改造为异步事件,具体实现如下。

1、开启异步功能

需要在SpringBoot的启动类加上开启异步任务的注解:@EnableAsync

@SpringBootApplication
@EnableAsync
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}

2、自定义异步线程池

新建 一个异步线程池的配置类,AsyncConfig

@Configuration
public class AsyncConfig implements AsyncConfigurer {

    private static final Logger log = LoggerFactory.getLogger(AsyncConfig.class);

    /**
     * 自定义异步线程池,如不重写则使用默认线程池
     * @return
     */
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //设置核心线程数
        taskExecutor.setCorePoolSize(5);
        //设置最大线程数
        taskExecutor.setMaxPoolSize(10);
        //队列大小
        taskExecutor.setQueueCapacity(15);
        //设置线程前缀名
        taskExecutor.setThreadNamePrefix("async-thread-");
        //线程初始化
        taskExecutor.initialize();
        return taskExecutor;
    }

    /**
     * 捕捉IllegalArgumentException异常
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncException();
    }

    class AsyncException implements AsyncUncaughtExceptionHandler{

        @Override
        public void handleUncaughtException(Throwable ex, Method method, Object... params) {
            log.info("TASK EXCEPTION MESSAGE - " + ex.getMessage());
            log.info("METHOD NAME - " + method.getName());
            for (Object param : params) {
                log.info("Parameter value - " + param);
            }
        }
    }
}

注:也可以不使用自定义异步线程池,使用默认的线程池也是可以的,只不过默认的线程池的最大线程数为:Integer.MAX_VALUE,这其实是不合理,因此通常情况下,都使用自定义异步任务线程来使用。

3、事件监听执行异步任务

在事件监听事件处理的方法上面,添加注解:@Async,即可以将该事件变为异步执行任务,代码如下:

@Component
@Log4j2
public class UserEventListener {

    @Resource
    private MailService mailService;

    @Resource
    private PointService pointService;

    @Async
    @EventListener
    public void userRegisterEventLister(UserRegisterEvent userRegisterEvent){
        log.info("监听到用户注册");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //进行邮件注册通知
        mailService.sendRegisterSuccessMail();
        //进行积分发放
        pointService.userRegisterPoint();
    }
}

运行输出:

用户注册成功
2024-01-11 19:24:53.009  INFO 33219 --- [ async-thread-2] c.e.demo.listener.UserEventListener      : 监听到用户注册
2024-01-11 19:24:57.011  INFO 33219 --- [ async-thread-2] com.example.demo.service.MailService     : 发送注册成功邮件
2024-01-11 19:24:57.012  INFO 33219 --- [ async-thread-2] com.example.demo.service.PointService    : 用户注册成功发放积分

到这里为止,使用Spring Event的基本使用方式都已完成。

原创文章,作者:jiafegn,如若转载,请注明出处:https://www.techlearn.cn/archives/1999

Previous 2023年12月31日 下午1:58
Next 2024年1月15日

相关推荐

  • SpringBoot 全局异常统一处理

    前言 在实际的项目开发过程中,会有大量的异常发生,而我们并不能将异常信息反馈到用户,所以在返回结果的时候需要对异常进行处理,可是如果在每个Controller返回结果都需要进行异常…

    springboot 2023年4月29日
    125
  • springboot 注解 @ComponentScan

    作用 @ComponentScan用于批量注册bean,这个注解会让spring去扫描某些包及其子包中所有的类,然后将满足一定条件的类作为bean 注册到spring容器中。主要使…

    springboot 2022年11月8日
    218
  • Springboot注解-@Component

    作用 作用:@Component的作用是把普通的类实例化到Spring容器中。基于@Component注解有三个扩展,分别是:@Repository、@Service、@Contr…

    springboot 2022年8月3日
    184
  • springboot 注解 @Qualifier

    作用 可以在依赖注入查找候选者的过程中对候选者进行过滤,比如:在需要自动注入java bean时,如果注入的是一个接口,而这个接口又有多个实现类,则会报错,解决方法是在注入接口上增…

    springboot 2022年9月14日
    198
  • SpringBoot 打包

    SpringBoot 项目部署到服务器常见的方式就是,打包成 jar 包,通过 nohup java -jar 命令去运行项目,这也是官方推荐的一种方式。 导入依赖 打包 设置打包…

    springboot 2023年4月5日
    148
  • SpringBoot 整合RocketMQ

    简介 使用RocketMQ有两种方式,一种是引入rocketmq-client需要自己创建生产者和消费者,相对来说比较繁琐;另一种是引入rocketmq-spring-boot-s…

    springboot 2023年4月30日
    311
  • SpringBoot 整合Mybatis-Plus

    简介 MyBatis-Plus是一个MyBatis的增强工具,在Mybatis的基础上只做增强不做改变,为简化开发,提高效率而生。 添加依赖 注意:添加Mybatis-Plus即可…

    springboot 2023年3月26日
    131
  • SpringBoot 整合Log4j2日志框架

    简介 Apache Log4j 2是日志框架Log4j的升级,它比其前身Log4j 1.x提供了重要的改进, 并且参考了Logback中许多有用的改进,同时修复了Logback的一…

    springboot 2023年3月26日
    153
  • SpringBoot 缓存 – jetcache

    简介 JetCache是一个基于Java的缓存系统封装,提供统一的API和强大的注解来简化缓存的使用。原生支持TTL、两级缓存、分布式自动刷新,还提供了Cache接口用于手工缓存操…

    springboot 2023年4月23日
    336
  • SpringBoot 缓存 – Redis

    引入依赖 缓存配置 启用缓存 修改项目启动类,增加注解@EnableCaching,开启缓存功能,如下: 配置缓存类 新建Redis缓存配置类RedisConfig,如下: 添加缓…

    2023年4月23日
    160