转载

研磨Spring事件机制

Spring提供了应用程序事件特性为开发者提供了事件发布和接收事件的能力,它基于观察者模式实现,对于提升应用逻辑的松耦合很有意义。

本文就如何使用Spring事件机制进行较为详细的总结。

Spring事件概述

  • 每一个Spring事件都需要继承ApplicationEvent的类,ApplicationEvent又继承自java.util.EventObject
  • Spring中的任何bean都能够通过实现ApplicationListener 接口来监听事件;这里的泛型T就是上面提到的继承了ApplicationEvent的类
  • SpringContext会对实现ApplicationListener接口的任意bean进行自动注册
  • 发布事件是通过ApplicationEventPublisher.publishEvent方法实现的。应用中一般通过ApplicationContext进行事件发布,ApplicationContext实现了ApplicationEventPublisher接口
  • 另一种发布事件的方式是bean实现ApplicationContextAware接口,拿到ApplicationContext实例从而实现事件发布

Spring事件机制实战

我们通过一个简单的案例对Spring事件机制使用进行总结。

定义事件

首先需要对事件进行定义,事件可以理解为消息队列中的消息,即:当满足某个条件时候需要发布的一个实体,该实体中包含了我们需要事件监听器处理的内容。

比如:当数据库中的内容发生更新,我们需要同步对缓存中的数据进行更新,那么我们就可以在数据库更新成功之后发布一个缓存更新事件,将更新后的数据实体通过publishEvent发布出去,

在另一个监听器类中获取到该缓存更新事件对缓存进行更新即可。这是一个事件机制的典型应用。

一个事件是一个javabean,它需要继承ApplicationEvent。

public class DemoEvent extends ApplicationEvent {

    private String key;
    private String value;

    public DemoEvent(Object source) {
        super(source);
    }

    public String getKey() {
        return key;
    }

    public DemoEvent setKey(String key) {
        this.key = key;
        return this;
    }

    public String getValue() {
        return value;
    }

    public DemoEvent setValue(String value) {
        this.value = value;
        return this;
    }

    @Override
    public String toString() {
        return "DemoEvent{" +
                "key='" + key + '/'' +
                ", value='" + value + '/'' +
                '}';
    }
}

上述这个类继承了ApplicationEvent,key、value是它的属性,在实际应用中,属性可以使任意的应用数据。

需要注意的是,ApplicationEvent具有一个接受指向事件源的引用的构造函数,在DemoEvent的构造函数中,需要通过super(source);传入事件源的引用。

定义事件监听器

ApplicationListener接口定义了方法onApplicationEvent,当触发了一个事件的时候,Spring框架会回调onApplicationEvent方法。

通过实现ApplicationListener接口,应用事件监听器需要对接收到的事件类进行处理。

@Component
public class DemoEventListener implements ApplicationListener<DemoEvent> {

    private static final Map<String, String> CONTAINER = new ConcurrentHashMap<>();

    @Override
    public void onApplicationEvent(DemoEvent demoEvent) {
        CONTAINER.put(demoEvent.getKey(), demoEvent.getValue());
        System.out.println("[DemoEventListener]接受事件--" + demoEvent.toString());
        System.out.println(CONTAINER.toString());
    }
}

这里的逻辑是DemoEventListener实现了强类型的ApplicationListener接口,对我们发布的DemoEvent事件进行处理。

具体的处理逻辑为将DemoEvent的key、value属性添加到CONTAINER这个Map中,模拟缓存更新。

另外一种创建事件监听器的方式为可以使用注解 @EventListener :原理就是通过扫描这个注解,创建监听器并添加到ApplicationContext

发布事件

通过接口ApplicationEventPublisher的publishEvent方法我们能够完成发布事件的目的,在Spring框架中AbstractApplicationContext实现了ApplicationEventPublisher接口。

因此我们能够在应用中通过获取ApplicationContext实例使用事件发布能力。

也可以通过实现ApplicationEventPublisherAware接口来发布事件

@Component
public class DemoEventPublisher implements CommandLineRunner {

    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);

    private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);

    @Autowired
    ApplicationContext context;

    @Override
    public void run(String... args) throws Exception {
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                // 设置事件源
                DemoEvent demoEvent = new DemoEvent(this);
                demoEvent.setKey(String.valueOf(ATOMIC_INTEGER.getAndAdd(1))).setValue("snowalker");
                context.publishEvent(demoEvent);
                System.out.println("[DemoEventPublisher]发布事件:" + demoEvent.toString());
            }
        }, 0, 3000, TimeUnit.MILLISECONDS);
    }
}

我们通过context.publishEvent(demoEvent);发布了缓存更新事件,每3秒发布一次。另一种写法为

@Component
public class DemoEventPublisher2 implements CommandLineRunner, ApplicationEventPublisherAware {

    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);

    private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);

    ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void run(String... args) throws Exception {
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                // 设置事件源
                DemoEvent demoEvent = new DemoEvent(this);
                demoEvent.setKey(String.valueOf(ATOMIC_INTEGER.getAndAdd(1))).setValue("snowalker");
                System.out.println("[DemoEventPublisher]发布事件:" + demoEvent.toString());
                applicationEventPublisher.publishEvent(demoEvent);
            }
        }, 0, 3000, TimeUnit.MILLISECONDS);
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
}

这两种写法效果一致,最终都是通过applicationEventPublisher进行了事件的发布。

案例运行

[DemoEventPublisher]发布事件:DemoEvent{key='0', value='snowalker'}
[DemoEventListener]接受事件--DemoEvent{key='0', value='snowalker'}
{0=snowalker}
[DemoEventPublisher]发布事件:DemoEvent{key='1', value='snowalker'}
[DemoEventListener]接受事件--DemoEvent{key='1', value='snowalker'}
{0=snowalker, 1=snowalker}
[DemoEventPublisher]发布事件:DemoEvent{key='2', value='snowalker'}
[DemoEventListener]接受事件--DemoEvent{key='2', value='snowalker'}
{0=snowalker, 1=snowalker, 2=snowalker}
......

可以看到,通过事件机制,我们实现了进程内的事件通信,这种方式能够很好的对业务进行解耦合,更加灵活的进行业务处理。

事务绑定事件

另外需要注意的是,某些场景下,我们需要在事务提交之后再发布事件,这里就涉及到了事务绑定事件能力。

具体的方式为使用 @TransactionalEventListener注解 或者 TransactionSynchronizationManager 类来解决此类问题,也就是:事务成功提交之后,再发布事件。

当然我们也可以在事件提交之后将结果返回给调用方然后发布事件,但是这种方式不够优雅,因此还是建议使用事务绑定事件的方式进行基于事务的事件发布。

异步事件支持

上述的方式为同步事件,我们可以通过配置开启异步事件支持。

通过使用@Async注解事件监听器开启异步支持,需要要开启对异步注解的支持.

  • java配置通过@EnableAsync开启.
  • 如果是xml配置文件则需要配置:

版权声明:

原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。

原文  http://wuwenliang.net/2019/09/08/研磨Spring事件机制/
正文到此结束
Loading...