• Spring内置事件监听器


    在这里插入图片描述

    在 Spring 容器中通过ApplicationEvent类和 ApplicationListener接口来实现事件监听机制,每次Event 被发布到Spring容器中时都会通知该Listener。需要注意的是,Spring 的事件默认是同步的,调用 publishEvent 方法发布事件后,它会处于阻塞状态,直到Listener接收到事件并处理返回之后才继续执行下去。

    实现一个事件监听的步骤:
    (1)创建事件,也就是extends ApplicationEvent

    @Getter
    @Setter
    @ToString
    public class UserDTO extends ApplicationEvent{
        private Integer userId;
        private String name;
        private Integer age;
    
        public UserDTO(Object source){
            super(source);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (2)定义监听器,也就是implements ApplicationListener

    @Component
    public class UserRegisterSmsListener{
    
        // 通过注解实现监听器
        @EventListener
        public void handleUserEvent(UserDTO userDTO){
            System.out.println("监听到用户注册,准备发送短信,user:"+userDTO.toString());
        }
    }
    
    // 通过实现接口实现监听器
    @Component
    public class UserRegisterEmailListener implements ApplicationListener<UserDTO>{
        @Override
        public void onApplicationEvent(UserDTO userDTO){
            System.out.println("监听到用户注册,准备发送邮件,user:" + userDTO.toString());
        }
    }
    @Component
    public class UserRegisterMessageListener implements ApplicationListener<UserDTO>{
        @Override
        public void onApplicationEvent(UserDTO userDTO){
            System.out.println("监听到用户注册,给新用户发送首条站内短消息,user:" + userDTO.toString());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    (3)注册服务,也就是将事件注册到监听器

    public interface UserService{
        void register();
    }
    @Service
    public class UserServiceImpl implements UserService{
        @Autowired
        private ApplicationEventPublisher eventPublisher;
        @Override
        public void register(){
            UserDTO userDTO = new UserDTO(this);
            userDTO.setAge(18);
            userDTO.setName("精灵王jinglingwang.cn");
            userDTO.setUserId(1001);
            System.out.println("register user");
            eventPublisher.publishEvent(userDTO);
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    (4)测试即可

    @Autowired
    private UserService userService;
    
    @Test
    public void testUserEvent(){
        userService.register();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    spring提供了内置的几类的事件:
    1、ContextClosedEvent
    2、ContextRefreshedEvent
    3、ContextStartedEvent
    4、ContextStoppedEvent
    5、RequestHandleEvent

    在spring容器启动完成后会触发ContextRefreshedEvent事件。

    上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext被初始化或者更新时发布。也可以在调用
    ConfigurableApplicationContext 接口中的refresh()方法时被触发。

    上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。

    上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。

    上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。

    请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。

    上述Spring内置的事件,都已经注册到了监听器上,所以直接使用即可


    Spring项目启动后执行操作:ContextRefreshedEvent和ApplicationReadyEvent
    一、Spring boot运行时,会发送以下事件

    1. ApplicationStartingEvent

    2. ApplicationEnvironmentPreparedEvent:当Environment已经准备好,在context 创建前

    3. ApplicationContextInitializedEvent:在ApplicationContext 创建和ApplicationContextInitializer都被调用后,但是bean definition没有被加载前

    4. ApplicationPreparedEvent:bean definition已经加载,但是没有refresh

    5. ApplicationStartedEvent: context 已经被refresh, 但是application 和command-line 的runner都没有被调用

    6. AvailabilityChangeEvent

    7. ApplicationReadyEvent: application 和command-line 的runner都被调用后触发

    8. AvailabilityChangeEvent

    9. ApplicationFailedEvent: 启动失败触发

    另外,会在ApplicationPreparedEvent之后和ApplicationStartedEvent之前发送

    ContextRefreshedEvent

    二、项目启动后需要执行某个操作

    1. 实现ApplicationListener接口

    2. ApplicationEvent的子类可以是ApplicationReadyEvent或者ContextRefreshedEvent

    3. ApplicationReadyEvent的示例

    @Component
    @Slf4j
    public class ApplicationInit implements ApplicationListener {

    // 项目启动后预热JSON
    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(123L);
        userInfo.setChannel("hello");
        String userJson = JSON.toJSONString(userInfo);
        JSON.parseObject(userJson, UserInfo.class);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    }
    三、ContextRefreshedEvent多次执行的问题

    1. web应用会出现父子容器,这样就会触发两次

    ApplicationListener (spring-context包) - ApplicationEvent- EventObject

    ContextRefreshedEvent (spring-context包) - ApplicationContextEvent - ApplicationEvent - EventObject

    ApplicationReadyEvent (spring-boot包) - SpringApplicationEvent - ApplicationEvent - EventObject
    上述ApplicationListener、ContextRefreshedEvent、ApplicationReadyEvent都可以用来监听Spring容器是否启动完成,启动完成之后,可以进行某些操作。

    Spring通过ApplicationListener接口来触发contextrefreshedevent事件
    在开发时有时候需要在整个应用开始运行时执行一些特定代码,比如初始化环境,准备测试数据、加载一些数据到内存等等。


    Listener是JavaWeb的三大组件(Servlet、Filter、Listener)之一,JavaWeb中的监听器主要用于监听:ServletContext、HttpSession、ServletRequest 的生命周期以及属性变化;在spring中也提供了监听器公开发人员使用;

    其实现原理是设计模式之观察者模式,设计的初衷是为了系统业务之间进行解耦,以便提高系统可扩展性、可维护性。Listener 主要包括定义事件、事件监听、事件发布.

    Java 中的事件机制
    Java中提供了基本的事件处理基类:

    EventObject:所有事件状态对象都将从其派生的根类;
    EventListener:所有事件侦听器接口必须扩展的标记接口;


    指定监听器的顺序
    监听器的发布顺序是按照 bean 自然装载的顺序执行的,Spring 支持两种方式来实现有序

    一、实现SmartApplicationListener接口指定顺序。
    把上面三个Listener都改成实现SmartApplicationListener接口,并指定getOrder的返回值,返回值越小,优先级越高。

    @Component
    public class UserRegisterMessageListener implements SmartApplicationListener{
    
        @Override
        public boolean supportsEventType(Class<? extends ApplicationEvent> eventType){
            return eventType == UserDTO.class;
        }
    
        @Override
        public boolean supportsSourceType(Class<?> sourceType){
            return true;
        }
    
        @Override
        public void onApplicationEvent(ApplicationEvent event){
            System.out.println("监听到用户注册,给新用户发送首条站内短消息,user:" + event.toString());
        }
    
        @Override
        public int getOrder(){
            return -1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    另外两个监听器的改造省略,指定改造后的UserRegisterSmsListener返回order为0,UserRegisterEmailListener的getOrder返回1,测试输出结果如下:

    二、使用注解@Order()
    order的值越小,优先级越高
    order如果不标注数字,默认最低优先级,因为其默认值是int最大值

    @Component
    public class UserRegisterSmsListener{
    
        @Order(-2)
        @EventListener
        public void handleUserEvent(UserDTO userDTO){
            System.out.println("监听到用户注册,准备发送短信,user:"+userDTO.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    测试输出结果如下:

    register user
    监听到用户注册,准备发送短信,user:UserDTO(userId=1001, name=精灵王jinglingwang.cn, age=18)
    监听到用户注册,给新用户发送首条站内短消息,user:UserDTO(userId=1001, name=精灵王jinglingwang.cn, age=18)
    监听到用户注册,准备发送邮件,user:UserDTO(userId=1001, name=精灵王jinglingwang.cn, age=18)

    可以发现,短信监听器最先执行。

    异步支持
    Spring 事件机制默认是同步阻塞的,如果 ApplicationEventPublisher 发布事件之后他会一直阻塞等待listener 响应,多个 listener 的情况下前面的没有执行完后面的会一直被阻塞。这时候我们可以利用 Spring 提供的线程池注解 @Async 来实现异步线程.

    一、使用 @Async 之前需要先开启线程池,在 启动类上添加 @EnableAsync 注解即可。

    @EnableAsync
    @SpringBootApplication
    public class DemoApplication {
    	public static void main(String[] args) {
    		SpringApplication.run(DemoApplication.class, args);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    二、监听器使用异步线程
    自定义异步线程池

    @Configuration
    public class AsyncConfig{
    
        @Bean("asyncThreadPool")
        public Executor getAsyncExecutor(){
            System.out.println("asyncThreadPool init");
            Executor executor = new ThreadPoolExecutor(
                    10,20,60L,TimeUnit.SECONDS
                    ,new ArrayBlockingQueue<>(100),new MyThreadFactory());
            return executor;
        }
    
        class MyThreadFactory implements ThreadFactory{
            final AtomicInteger threadNumber = new AtomicInteger(0);
            @Override
            public Thread newThread(Runnable r){
                Thread t = new Thread(r);
                t.setName("async-thread-"+threadNumber.getAndIncrement());
                t.setDaemon(true);
                return t;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    指定监听器的线程池

    @Component
    public class UserRegisterSmsListener{
    
        @Order(-2)
        @Async("asyncThreadPool")
        @EventListener
        public void handleUserEvent(UserDTO userDTO){
            System.out.println(Thread.currentThread().getName() + " 监听到用户注册,准备发送短信,user:"+userDTO.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    三、测试输出结果

    register user
    监听到用户注册,给新用户发送首条站内短消息,user:UserDTO(userId=1001, name=admol, age=18)
    监听到用户注册,准备发送邮件,user:UserDTO(userId=1001, name=admol, age=18)
    async-thread-0 监听到用户注册,准备发送短信,user:UserDTO(userId=1001, name=admol, age=18)

  • 相关阅读:
    SQL如何导入数据以及第一次上机作业
    网络安全:常见的中间件以及环境搭建方法
    数据集笔记:T-drive 北京出租车轨迹数据
    python工作任务流flow实时框架:prefect
    策略路由(本地策略和接口策略)
    主播三维能力总览
    慢跑是早上跑好,还是晚上跑好?对的时间跑,效果可能还翻倍
    jQuery事件对象
    新荣耀两周年:一次隘口突围、一场创新盛宴、一场价值跃迁
    【VMware VCF】更新 VCF 5.1 至 VCF 5.2 版本。
  • 原文地址:https://blog.csdn.net/u014365523/article/details/125596294