Spring内置事件监听器

需要一个事件
需要一个监听器
需要将事件注册到监听器

在这里插入图片描述

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

实现一个事件监听的步骤:
第一步:创建事件,也就是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);
    }
}

第二步:定义监听器,也就是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());
    }
}

第三步:注册服务,也就是将事件注册到监听器

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


第四步:测试即可

@Autowired
private UserService userService;

@Test
public void testUserEvent(){
    userService.register();
}

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

}
三、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;
    }
}

另外两个监听器的改造省略,指定改造后的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());
    }
}

测试输出结果如下:

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

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

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

指定监听器的线程池

@Component
public class UserRegisterSmsListener{

    @Order(-2)
    @Async("asyncThreadPool")
    @EventListener
    public void handleUserEvent(UserDTO userDTO){
        System.out.println(Thread.currentThread().getName() + " 监听到用户注册,准备发送短信,user:"+userDTO.toString());
    }
}

三、测试输出结果

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)

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值