Spring ApplicationEvent&Listener

233 阅读6分钟

技术背景

我们在传统项目中往往各个业务逻辑之间耦合性较强,因为我们在service层都是直接引用的关联service或者jpa来作为协作处理逻辑,然而这种方式在后期更新、维护性时难度较大。如果我们采用事件通知、事件监听形式来处理逻辑时耦合性则是可以降到最小。

  1. 事件发布方使用applicationContext#publishEvent发布事件
# 定义事件
@Getter
public class UserRegisterEvent extends ApplicationEvent
{
    //注册用户对象
    private UserBean user;

    /**
     * 重写构造函数
     * @param source 发生事件的对象
     * @param user 注册用户对象
     */
    public UserRegisterEvent(Object source,UserBean user) {
        super(source);
        this.user = user;
    }
}

# 发布事件
@Service
public class UserService
{
    @Autowired
    ApplicationContext applicationContext;

    /**
     * 用户注册方法
     * @param user
     */
    public void register(UserBean user)
    {
        //../省略其他逻辑

        //发布UserRegisterEvent事件
        applicationContext.publishEvent(new UserRegisterEvent(this,user));
    }
}
  1. 事件消费方
  • 方式一:使用@Component+@EventListener注册监听者
# 第一个监听者
@Component
public class AnnotationRegisterListener {

    /**
     * 注册监听实现方法
     * @param userRegisterEvent 用户注册事件
     */
    @EventListener
    public void register(UserRegisterEvent userRegisterEvent)
    {
        //获取注册用户对象
        UserBean user = userRegisterEvent.getUser();

        //../省略逻辑

        //输出注册用户信息
        System.out.println("@EventListener注册信息,用户名:"+user.getName()+",密码:"+user.getPassword());
    }
}

# 第二个监听者
@Component
public class RegisterUserEmailListener
{
    /**
     * 发送邮件监听实现
     * @param userRegisterEvent 用户注册事件
     */
    @EventListener
    public void sendMail(UserRegisterEvent userRegisterEvent)
    {
        System.out.println("用户注册成功,发送邮件。");
    }
}



  • 方式二:使用@Component+接口ApplicationListener<>实现
@Component
public class RegisterListener implements ApplicationListener<UserRegisterEvent>
{
    /**
     * 实现监听
     * @param userRegisterEvent
     */
    @Override
    public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
        //获取注册用户对象
        UserBean user = userRegisterEvent.getUser();

        //../省略逻辑

        //输出注册用户信息
        System.out.println("注册信息,用户名:"+user.getName()+",密码:"+user.getPassword());
    }
}

通过测试发现,使用@EventListener和接口ApplicationListener<>注册的多个事件监听者,被通知的顺序是随机的,无法指定顺序。所以Spring又提供了第三种方式使用接口SmartApplicationListener来定义事件监听者的顺序。

  • 方式三:使用@Component+接口SmartApplicationListener,可以覆盖supportsEventType,supportsSourceType,getOrder来精确指定事件类型、事件发起者、事件监听者被执行的顺序
# 第一个事件监听者
@Component
public class UserRegisterListener implements SmartApplicationListener
{
    /**
     *  该方法返回true&supportsSourceType同样返回true时,才会调用该监听内的onApplicationEvent方法
     * @param aClass 接收到的监听事件类型
     * @return
     */
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
        //只有UserRegisterEvent监听类型才会执行下面逻辑
        return aClass == UserRegisterEvent.class;
    }

    /**
     *  该方法返回true&supportsEventType同样返回true时,才会调用该监听内的onApplicationEvent方法
     * @param aClass
     * @return
     */
    @Override
    public boolean supportsSourceType(Class<?> aClass) {
        //只有在UserService内发布的UserRegisterEvent事件时才会执行下面逻辑
        return aClass == UserService.class;
    }

    /**
     * 同步情况下监听执行的顺序
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }

    /**
     *  supportsEventType & supportsSourceType 两个方法返回true时调用该方法执行业务逻辑
     * @param applicationEvent 具体监听实例,这里是UserRegisterEvent
     */
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {

        //转换事件类型
        UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent;
        //获取注册用户对象信息
        UserBean user = userRegisterEvent.getUser();
        //.../完成注册业务逻辑
        System.out.println("注册信息,用户名:"+user.getName()+",密码:"+user.getPassword());
    }
}

# 第二个事件监听者
@Component
public class UserRegisterSendMailListener implements SmartApplicationListener
{
    /**
     *  该方法返回true&supportsSourceType同样返回true时,才会调用该监听内的onApplicationEvent方法
     * @param aClass 接收到的监听事件类型
     * @return
     */
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
        //只有UserRegisterEvent监听类型才会执行下面逻辑
        return aClass == UserRegisterEvent.class;
    }

    /**
     *  该方法返回true&supportsEventType同样返回true时,才会调用该监听内的onApplicationEvent方法
     * @param aClass
     * @return
     */
    @Override
    public boolean supportsSourceType(Class<?> aClass) {
        //只有在UserService内发布的UserRegisterEvent事件时才会执行下面逻辑
        return aClass == UserService.class;
    }

    /**
     * 同步情况下监听执行的顺序
     * @return
     */
    @Override
    public int getOrder() {
        return 1;
    }

    /**
     *  supportsEventType & supportsSourceType 两个方法返回true时调用该方法执行业务逻辑
     * @param applicationEvent 具体监听实例,这里是UserRegisterEvent
     */
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        //转换事件类型
        UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent;
        //获取注册用户对象信息
        UserBean user = userRegisterEvent.getUser();
        System.out.println("用户:"+user.getName()+",注册成功,发送邮件通知。");
    }
}

测试输出如下

2017-07-21 13:40:43.104  INFO 10128 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-07-21 13:40:43.104  INFO 10128 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2017-07-21 13:40:43.119  INFO 10128 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 15 ms
注册信息,用户名:admin,密码:123456
用户:admin,注册成功,发送邮件通知。
  • 方式四:在resources/application.properties中注册事件监听者 DelegatingApplicationListener 由spring boot初始化时启动,然后监听ApplicationEnvironmentPreparedEvent事件,收到事件后,就找当前项目的properties文件,并加载里面context.listener.classes对应的监听器
context.listener.classes=\
  com.shahuwang.bootsample.listener.BlackListListener,\
  com.shahuwang.bootsample.listener.BlackListListener2

SpringBoot相关的事件

image.png

  1. 定义事件监听者:以ApplicationStartedEvent为例
public class ApplicationStartedEventListener implements SmartApplicationListener {
  @Override
  public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
    // 判断事件的类型,只监听ApplicationStartedEvent事件类型
    return eventType == ApplicationStartedEvent.class;
  }

  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    // 将ApplicationEvent转换为ApplicationStartedEvent实例
    ApplicationStartedEvent startedEvent = (ApplicationStartedEvent) event;
    ConfigurableEnvironment environment = startedEvent.getApplicationContext().getEnvironment();
    // 获取系统环境变量
    Map<String, Object> props = environment.getSystemEnvironment();
    Iterator<String> iterator = props.keySet().iterator();
    while (iterator.hasNext()) {
      String key = iterator.next();
      Object value = props.get(key);
      System.out.println("Key : " + key + " , Value : " + value);
    }
    System.out.println("启动成功了.");
  }
}
  1. 注册事件监听者
  • 方式一:在resources/META-INF/spring.factories文件中注册事件监听者
org.springframework.context.ApplicationListener=\
  org.minbox.chapter.developing.first.application.ApplicationStartedEventListener
  • 方式二:使用SpringApplidation#addListeners来注册
@SpringBootApplication
public class DevelopingFirstApplication {

  public static void main(String[] args) {
    // 注释掉原启动方式
    //SpringApplication.run(DevelopingFirstApplication.class, args);
    
    // 手动实例化SpringApplication方式
    SpringApplication application = new SpringApplication(DevelopingFirstApplication.class);
    // 添加注册监听器
    application.addListeners(new ApplicationStartedEventListener());
    // 启动应用程序
    application.run(args);

  }
}

异步执行事件监听者

@Aysnc其实是Spring内的一个组件,可以完成对类内单个或者多个方法实现异步调用,这样可以大大的节省等待耗时。内部实现机制是线程池任务ThreadPoolTaskExecutor,通过线程池来对配置@Async的方法或者类做出执行动作。

  • 线程任务池配置

我们创建一个ListenerAsyncConfiguration,并且使用@EnableAsync注解开启支持异步处理,具体代码如下所示:

@Configuration
@EnableAsync
public class ListenerAsyncConfiguration implements AsyncConfigurer
{
    /**
     * 获取异步线程池执行对象
     * @return
     */
    @Override
    public Executor getAsyncExecutor() {
        //使用Spring内置线程池任务对象
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //设置线程池参数
        taskExecutor.setCorePoolSize(5);
        taskExecutor.setMaxPoolSize(10);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }
}

我们自定义的监听异步配置类实现了AsyncConfigurer接口并且实现内getAsyncExecutor方法以提供线程任务池对象的获取。
我们只需要在异步方法上添加@Async注解就可以实现方法的异步调用,为了证明这一点,我们在发送邮件onApplicationEvent方法内添加线程阻塞3秒,修改后的代码如下所示:

    @Override
    @Async
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        try {
            Thread.sleep(3000);//静静的沉睡3秒钟
        }catch (Exception e)
        {
            e.printStackTrace();
        }
        //转换事件类型
        UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent;
        //获取注册用户对象信息
        UserBean user = userRegisterEvent.getUser();
        System.out.println("用户:"+user.getName()+",注册成功,发送邮件通知。");
    }

总结

我们在传统项目中往往各个业务逻辑之间耦合性较强,因为我们在service都是直接引用的关联service或者jpa来作为协作处理逻辑,然而这种方式在后期更新、维护性难度都是大大提高了。然而我们采用事件通知、事件监听形式来处理逻辑时耦合性则是可以降到最小。