Spring Event事件驱动

2020-02-10 16:09:25来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

Spring Event事件驱动

Spring事件驱动模型,简单来说类似于Message-Queue消息队列中的Pub/Sub发布/订阅模式,也类似于Java设计模式中的观察者模式。

自定义事件

Spring的事件接口位于org.springframework.context.ApplicationEvent,源码如下:

public abstract class ApplicationEvent extends EventObject {
    private static final long serialVersionUID = 7099057708183571937L;
    private final long timestamp;
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }
    public final long getTimestamp() {
        return this.timestamp;
    }
}

继承了Java的事件对象EventObject,所以可以使用getSource()方法来获取到事件传播对象。

自定义Spring事件

public class CustomSpringEvent extends ApplicationEvent {
    private String message;

    public CustomSpringEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

然后定义事件监听器,该监听器实际上等同于消费者,需要交给Spring容器管理。

@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
    @Override
    public void onApplicationEvent(CustomSpringEvent event) {
        System.out.println("Received spring custom event - " + event.getMessage());
    }
}

最后定义事件发布者

@Component
public class CustomSpringEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void doStuffAndPublishAnEvent(final String message) {
        System.out.println("Publishing custom event. ");
        CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }
}

创建测试类

@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomSpringEventPublisherTest {

    @Autowired
    private CustomSpringEventPublisher publisher;

    @Test
    public void publishStringEventTest() {
        publisher.doStuffAndPublishAnEvent("111");
    }
}

运行测试类,可以看到控制台打印了两条重要信息

//发布事件
Publishing custom event. 
//监听器得到了事件,并相应处理
Received spring custom event - 111

由于Spring事件是发布/订阅的模式,而发布订阅模式有以下三种情况

  1. 1生产者 - 1消费者
  2. 1生产者 - 多消费者
  3. 多生产者 - 多消费者

上面举的例子是第一种情况,我们来试试其他两个情况

继续创建一个事件监听器作为消费者:

@Component
public class CustomSpringEventListener2 implements ApplicationListener<CustomSpringEvent> {
    @Override
    public void onApplicationEvent(CustomSpringEvent event) {
        System.out.println("CustomSpringEventListener2 Received spring custom event - " + event.getMessage());
    }
}

运行测试类后,可以观察到,控制台顺序打印了两条消费信息:

Publishing custom event. 
CustomSpringEventListener1 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 111

说明,Spring的发布订阅模式是广播模式,所有消费者都能接受到消息,并正常消费

再试试第三种多生产者 - 多消费者的情况

继续创建一个发布者,

@Component
public class CustomSpringEventPublisher2 {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void doStuffAndPublishAnEvent(final String message) {
        System.out.println("CustomSpringEventPublisher2 Publishing custom event. ");
        CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }
}

控制台输出:

CustomSpringEventPublisher Publishing custom event. 
CustomSpringEventListener1 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 111
CustomSpringEventPublisher2 Publishing custom event. 
CustomSpringEventListener1 Received spring custom event - 222
CustomSpringEventListener2 Received spring custom event - 222

从以上输出内容,我们可以猜测到,Spring的事件发布订阅机制是同步进行的,也就是说,事件必须被所有消费者消费完成之后,发布者的代码才能继续往下走,这显然不是我们想要的效果,那有没有异步执行的事件呢?

Spring中的异步事件

要使用Spring 的异步事件,我们需要自定义异步事件配置类

@Configuration
public class AsynchronousSpringEventsConfig {
    @Bean(name = "applicationEventMulticaster")
    public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster
                = new SimpleApplicationEventMulticaster();

        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return eventMulticaster;
    }
}

发布和订阅的代码不用变动,直接运行测试类,控制台将打印出:

CustomSpringEventPublisher Publishing custom event. 
CustomSpringEventPublisher2 Publishing custom event. 
CustomSpringEventListener1 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 222
CustomSpringEventListener1 Received spring custom event - 222

可以看到,两个发布者几乎同时运行,证明监听器是异步执行的,没有阻塞住发布者的代码。准确的说,监听器将在一个单独的线程中异步处理事件。

Spring自带的事件类型

事件驱动在Spring中是被广泛采用的,我们查看ApplicationEvent的子类可以发现许多Event事件,在此不赘述。

注解驱动的监听器

从Spring 4.2开始,事件监听器不需要是实现ApplicationListener接口的bean,它可以通过@EventListener注解在任何被Spring容器管理的bean的公共方法上。

@Component
public class AnnotationDrivenContextStartedListener {
    @EventListener
    public void handleContextStart(CustomSpringEvent cse) {
        System.out.println("Handling Custom Spring Event.");
    }
}

控制台输出结果:

CustomSpringEventPublisher Publishing custom event.
Handling Custom Spring Event.
CustomSpringEventPublisher2 Publishing custom event. 
Handling Custom Spring Event.

同样的,我们可以看出,这个事件监听器是同步执行的,如果要改为异步监听器,在事件方法上加上@Async,并且在Spring应用中开启异步支持(在SpringBootApplication上添加@EnableAsync)。

@Component
public class AnnotationDrivenContextStartedListener {
    @Async
    @EventListener
    public void handleContextStart(CustomSpringEvent cse) {
        System.out.println("Handling Custom Spring Event.");
    }
}

再次运行测试类:

CustomSpringEventPublisher Publishing custom event. 
CustomSpringEventPublisher2 Publishing custom event. 
Handling Custom Spring Event.
Handling Custom Spring Event.

泛型支持

创建一个通用泛型事件模型

@Data
public class GenericSpringEvent<T> {
    private T message;
    protected boolean success;

    public GenericSpringEvent(T what, boolean success) {
        this.message = what;
        this.success = success;
    }
}

注意GenericSpringEventCustomSpringEvent之间的区别。我们现在可以灵活地发布任何任意事件,并且不再需要从ApplicationEvent扩展。

这样的话,我们无法像之前一样,通过继承ApplicationListener的方式来定义一个监听器,因为ApplicationListener定义了事件必须是ApplicationEvent的子类。所以,我们只能使用注解驱动的监听器。

通过在@EventListener注释上定义布尔SpEL表达式,也可以使事件监听器成为条件。在这种情况下,只会为成功的String的GenericSpringEvent调用事件处理程序:

@Component
public class AnnotationDrivenEventListener {
    @EventListener(condition = "#event.success")
    public void handleSuccessful(GenericSpringEvent<String> event) {
        System.out.println("Handling generic event (conditional).");
    }
}

定义具体类型的事件:

public class StringGenericSpringEvent extends GenericSpringEvent<String> {
    public StringGenericSpringEvent(String message, boolean success) {
        super(message, success);
    }
}

定义发布者:

@Component
public class StringGenericSpringEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void doStuffAndPublishAnEvent(final String message, final boolean success) {
        System.out.println("CustomSpringEventPublisher Publishing custom event. ");
        StringGenericSpringEvent springEvent = new StringGenericSpringEvent(message, success);
        applicationEventPublisher.publishEvent(springEvent);
    }
}

测试类:

@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomSpringEventPublisherTest {

    @Autowired
    private StringGenericSpringEventPublisher publisher;

    @Test
    public void publishStringEventTest() {
        publisher.doStuffAndPublishAnEvent("success", true);
        publisher.doStuffAndPublishAnEvent("failed", false);
    }
}

运行结果:

CustomSpringEventPublisher Publishing custom event. 
Handling generic event (conditional) success
CustomSpringEventPublisher Publishing custom event. 

监听器只处理了成功的事件,成功忽略掉了失败的事件。这样的好处是,可以为同一个事件定义成功和失败不同的操作。

Spring事件的事务绑定

从Spring 4.2开始,框架提供了一个新的@TransactionalEventListener注解,它是@EventListener的扩展,允许将事件的侦听器绑定到事务的一个阶段。绑定可以进行以下事务阶段:

  • AFTER_COMMIT(默认的):在事务成功后触发
  • AFTER_ROLLBACK:事务回滚时触发
  • AFTER_COMPLETION:事务完成后触发,不论是否成功
  • BEFORE_COMMIT:事务提交之前触发

总结

  1. Spring中处理事件的基础知识:创建一个简单的自定义事件,发布它,然后在监听器中处理它。
  2. 在配置中启用事件的异步处理。
  3. Spring 4.2中引入的改进,例如注释驱动的侦听器,更好的泛型支持以及绑定到事务阶段的事件。

?? 本文代码地址


原文链接:https://www.cnblogs.com/gcdd/p/12292214.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:Spring-Cloud微服务踩坑

下一篇:基于SSH开发银行个人业务管理系统 附源码