• 深入理解Spring事件机制
  • 发布于 2个月前
  • 106 热度
    0 评论
相关概念
1.ApplicationEventPublisherAware
ApplicationEventPublisherAware 是由 Spring 提供的用于为 Service 注入 ApplicationEventPublisher 事件发布器的接口,使用这个接口,我们自己的 Service 就拥有了发布事件的能力。
2.ApplicationListener
ApplicationListener接口是由 Spring 提供的事件订阅者必须实现的接口,我们一般把该 Service 关心的事件类型作为泛型传入。处理事件,通过 event.getSource() 即可拿到事件的具体内容
3.ApplicationEventPublisher
ApplicationEventPublisher是ApplicationContext的父接口之一。这接口的作用是:Interface that encapsulates event publication functionality.
功能就是发布事件,也就是把某个事件告诉的所有与这个事件相关的监听器。
4.TransactionalEventListener

Spring事务监听机制---使用@TransactionalEventListener处理数据库事务提交成功后再执行操作


@EventLister使用
基本使用
1.定义需要进行事件发布的接口
public interface StudentEventRegisterService {
   /**
    * 学生注册、发布事件
    */
   void register();
}
2.在实现类发布事件
@Service
public class StudentEventRegisterServiceImpl implements StudentEventRegisterService{
   @Autowired
   private ApplicationEventPublisher applicationEventPublisher;

   @Override
   public void register() {
       Student student = new Student();
       student.setId(1L);
       student.setName("张三");
       applicationEventPublisher.publishEvent(student);
       System.out.println("学生注册事件发送结束");
  }
}
3.监听事件
@Component
public class StudentEventListener {
   @EventListener(condition = "#student.id!=null")
   public void handleEvent(Student student) {
       System.out.println("接收到的学生对象:" + student);
  }
}
事件监听开启异步
1.开启异步并配置
方式一:
@Configuration
@EnableAsync
public class AsyncEventConfiguration implements AsyncConfigurer {
   @Override
   public Executor getAsyncExecutor() {
       return Executors.newFixedThreadPool(1);
  }
}
2. 监听事件并开启异步
@Component
public class StudentEventListener {
   @EventListener(condition = "#student.id!=null")
   @Async
   public void handleEvent(Student student) {
       System.out.println("接收到的学生对象:" + student);
  }
}   
注意:这种方式在阿里规约里已经不允许了,此处仅为演示使用。

阿里规约
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。            
Positive example 1:
   //org.apache.commons.lang3.concurrent.BasicThreadFactory
   ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
       new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
                 
Positive example 2:
   ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
      .setNameFormat("demo-pool-%d").build();
   //Common Thread Pool
   ExecutorService pool = new ThreadPoolExecutor(5, 200,
       0L, TimeUnit.MILLISECONDS,
       new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

   pool.execute(()-> System.out.println(Thread.currentThread().getName()));
   pool.shutdown();//gracefully shutdown
         
Positive example 3:
   <bean id="userThreadPool"
       class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
       <property name="corePoolSize" value="10" />
       <property name="maxPoolSize" value="100" />
       <property name="queueCapacity" value="2000" />

   <property name="threadFactory" value= threadFactory />
       <property name="rejectedExecutionHandler">
           <ref local="rejectedExecutionHandler" />
       </property>
   </bean>
   //in code
   userThreadPool.execute(thread);
优化后开启异步的配置
@Configuration
@EnableAsync
public class AsyncEventConfiguration implements AsyncConfigurer {
   @Override
   public Executor getAsyncExecutor() {
       ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
       taskExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
       taskExecutor.setMaxPoolSize(2*Runtime.getRuntime().availableProcessors());
       taskExecutor.setQueueCapacity(1024);
       taskExecutor.initialize();
       return taskExecutor;
  }
}
@TransactionalEventListener使用
使用背景
在项目中,往往需要执行数据库操作后,发送消息或事件来异步调用其他组件执行相应的操作,例如:
用户注册后发送激活码;
配置修改后发送更新事件等。
但是,数据库的操作如果还未完成,此时异步调用的方法查询数据库发现没有数据,这就会出现问题。
为了解决上述问题,Spring为我们提供了两种方式:
(1) @TransactionalEventListener注解
(2) 事务同步管理器TransactionSynchronizationManager
以便我们可以在事务提交后再触发某一事件。
使用示例
1.事务控制
@Transaction
public void saveUser(User u) {
   //保存用户信息
   userDao.save(u);
   //触发保存用户事件
   applicationContext.publishEvent(new SaveUserEvent(u.getId()));
}
2.事件监听
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
void onSaveUserEvent(SaveUserEvent event) {
   Integer id = event.getEventData();
   User u = userDao.getUserById(id);
   String phone = u.getPhoneNumber();
   MessageUtils.sendMessage(phone);
}
这样,只有当前事务提交之后,才会执行事件监听器的方法。其中参数phase默认为AFTER_COMMIT,共有四个枚举:
BEFORE_COMMIT, AFTER_COMMIT, AFTER_ROLLBACK,AFTER_COMPLETION

注意事项
1、如果2个事件之间是继承关系,会先监听到子类事件,处理完再监听父类。
2、监听器方法一定要try-catchy异常,否则会造成发布事件(有事务的)的方法进行回滚
3、可以使用@Order注解控制多个监听器的执行顺序,@Order 传入的值越小,执行顺序越高
用户评论