2

前言

在前端中我们经常使用事件机制,如鼠标点击事件等,而在java中,java也为我们设置了事件机制。
我们先模仿前端的事件机制,使用代码进行简单实现鼠标点击事件。

class Mouse {
  private List<mouselistener> listeners = new ArrayList<mouselistener>();
  private int listenerCallbacks = 0;
   
  public void addListenerCallback() {
    listenerCallbacks++;
  }
   
  public int getListenerCallbacks() {
    return listenerCallbacks;
  }
   
  public void addListener(MouseListener listener) {
    listeners.add(listener);
  }
   
  public void click() {
    System.out.println("Clicked !");
    for (MouseListener listener : listeners) {
      listener.onClick(this);
    }
  }
}
 
interface MouseListener {
  public void onClick(Mouse source);
}

public class EventBasedTest {
 
  @Test
  public void test() {
    Mouse mouse = new Mouse();
    mouse.addListener(new MouseListener() {
      @Override
      public void onClick(Mouse mouse) {
        System.out.println("Listener#1 called");
        mouse.addListenerCallback();
      }
    });
    mouse.addListener(new MouseListener() {
      @Override
      public void onClick(Mouse mouse) {
        System.out.println("Listener#2 called");
        mouse.addListenerCallback();
      }
    });
    mouse.click();
  }
}

打印输出如下所示

Clicked !
Listener#1 called
Listener#2 called

准备

实现java的Event需要实现三部分

  • 事件类:事件的载体。
  • 发布者:发布事件。
  • 监听者:监听并处理事件。

我们通过一个业务场景来具体实现java事件。假如我们有个需求,用户创建成功后给用户发送一个邮件。这里有两个事情要做:

  • 创建用户
  • 给用户发送邮件
    这个很简单。

    @Service
    public class EmailService {
    
        @Transactional
        public void sendEmail(String email) {
          //send email
        }
    }
    
    
    @Service
    public class UserService {
        private final EmailService emailService;
        private final UserRepository userRepository;
    
        public UserService(EmailService emailService, UserRepository userRepository) {
          this.emailService = emailService;
          this.userRepository = userRepository;
        }
    
        @Transactional
        public User createUser(User user) {
          User newUser = this.userRepository.save(user);
          this.emailService.sendEmail(user.getEmail());
          return newUser;
        }
    }

    但这种实现是有问题的。我们想一下,这个功能的核心是创建用户,而发送邮件是一个副作用(发送邮件不能影响用户的创建),如果把这两个操作放在一个事务中会有什么问题?其实很明显,如果创建用户时抛出异常,事务回滚,方法提前退出,那么也不会发送邮件,这是正常的。但是下面两个场景是不可接受的:

    • 如果邮件发送失败,事务发生回滚,用户创建失败。
    • 如果邮件发送成功后,事务提交失败,用户收到了邮件,可是用户创建失败,这不是我们所希望的。

我们先通过event机制进行业务解耦,同时达到易扩展的原则。

事件类

事件类为事件的载体,也就是所发布者发布的东西和监听者接收的东西都只能是事件,我们所要在发布者和监听者之间传递的东西,也定义在我们的自定义事件类里。
只要继承ApplicationEvent,便是一个事件类。

public class UserCreatedEvent {
    private final User user;

    public UserCreatedEvent(User user) {
        this.user = user;
    }
    public User getUser() {
        return user;
    }
}

发布者

发布者发布事件类,我们可以调用ApplicationEventPublisherpublishEvent()方法发布事件类。

@Service
public class CustomerService {

    private final UserRepository userRepository;
    private final ApplicationEventPublisher applicationEventPublisher;

    public CustomerService(UserRepository userRepository, ApplicationEventPublisher applicationEventPublisher) {
        this.userRepository = userRepository;
        this.applicationEventPublisher = applicationEventPublisher;
    }

    @Transactional
    public Customer createCustomer(User user) {
        User newUser = this.userRepository.save(user);
        final UserCreatedEvent event = new UserCreatedEvent(newUser);
        this.applicationEventPublisher.publishEvent(event);
        return newUser;
    }
}

监听者

监听者接受发布者发布的事件并处理。
实现也很简单,在方法上加入@EventListener注解并接收事件类参数。

@Component
public class UserCreatedEventListener {

    private final EmailService emailService;

    public UserCreatedEventListener(EmailService emailService) {
        this.emailService = emailService;
    }

    @EventListener
    public void processUserCreatedEvent(UserCreatedEvent event) {
        this.emailService.sendEmail(event.getUser().getEmail());
    }
}

如果我们有两个监听者,可以通过@Order注解来控制两个监听者的执行顺序。

虽然我们用EventListener的方式解耦了业务代码,看着很高大上,但是有兴趣研究源码就可以发现,这在底层还是一个普通的方法调用,这并没有达到我们的目的。

如果我们在Listener方法上加@Async让其异步执行,这跟不同的异步调用并无二异,可能会导致先发送短信后提交事务。这也不符合我们提出的要求。

事务隔离

使用@TransactionalEventListener注解,TransactionalEventListener注解是对EventListener的增强,被注解的方法可以在事务的不同阶段去触发执行,如果事件未在激活的事务中发布,除非显式设置了 fallbackExecution() 标志为true,否则该事件将被丢弃;如果事务正在运行,则根据其 TransactionPhase 处理该事件。

  • AFTER_COMMIT - 默认设置,在事务提交后执行
  • AFTER_ROLLBACK - 在事务回滚后执行
  • AFTER_COMPLETION - 在事务完成后执行(不管是否成功)
  • BEFORE_COMMIT - 在事务提交前执行
@Component
public class UserCreatedEventListener {

    private final EmailService emailService;

    public UserCreatedEventListener(EmailService emailService) {
        this.emailService = emailService;
    }

    @TransactionalEventListener
    public void processUserCreatedEvent(UserCreatedEvent event) {
        this.emailService.sendEmail(event.getUser().getEmail());
    }
}

这样,我们在事务提交后执行发送邮件方法。即保证了发送邮件功能不对保存用户功能进行影响,也不会使得发送邮件功能和保存用户功能没有关联。

总结

对修改关闭,对扩展开放。
参考:
Spring5源码解析-Spring框架中的事件和监听器TransactionalEventListener使用场景以及实现原理,最后要躲个大坑
Spring Event的初步讲解
订阅发布模式和观察者模式的区别


小强Zzz
1.2k 声望32 粉丝