spring event机制

萝らか妹 提交于 2020-01-31 15:49:45

Spring增加了event事件机制,方便了日常项目中需要业务解藕的开发场景,也支持异步和重试机制,很多场景都可以使用

目前开发的项目中需要记录一些异常单据信息的记录,主流程中正常单据接单的流程不动,在识别出某种异常后记录,但是这种记录不应该影响主流程,所以考虑用Spring的event异步事件处理

1.什么是事件机制

Java的设计模式中有两种设计模式,观察者模式和监听者模式

监听者模式:当有一个事件发生时,通知关注此事件的对象,告诉事件发生了就发布这个事件,那怎么知道通知谁呢,所以需要知道谁关心这个事件,那么就需要对这个事件关心的对象中定义一个事件,如果事件发生了,关心的这个对象就监听到了,可以执行相应的操作。

观察者模式:一对多的模式,一个被观察者Observable和多个观察者Observer,被观察者中存储了所有的观察者对象,当被观察者接收到一个外界的消息,就会遍历广播推算消息给所有的观察者
例如日常生活中的订阅报纸,报纸老板A,现在小明和老板打招呼说我要订报纸(这个过程就相当于观察者的注册),老板A就会拿出自己的小本本记下小明,下次小王、小芳也是类似的操作,现在老板A就有了三个观察者了,然后老板会自动的把报纸送到三位的家里,突然有一天小明说不想订报纸了,老板就在自己的小本本上划掉小明的名字(观察者的取消注册),等到送报纸的时候就不再送到小明家里。
 
2.怎么去使用事件机制
 
常规的使用事件需要有三个步骤:定义事件源,监听事件,发布事件
 
首先需要定义一个事件源,继承ApplicationEvent类,ApplicationEvent的父类是EventObject,说明这个类是一个事件:

 

 如果有其他要通过这个事件传递的对象,可以在定义事件的时候增加属性

 

 

 

创建事件监听者:
可以创建一个类直接实现ApplicationListener,ApplicationListener是继承的 EventListener,在实现类中重写onApplicationEvent方法去实现自己的业务逻辑

 

 

在我的项目中,我们使用了SmartApplicationListener,

SmartApplicationListener是高级监听器,是ApplicationListener的子类,能够实现有序监听。

 

 

public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

   /**
    * Determine whether this listener actually supports the given event type.
    */
   boolean supportsEventType(Class<? extends ApplicationEvent> eventType);

   /**
    * Determine whether this listener actually supports the given source type.
    */
   boolean supportsSourceType(Class<?> sourceType);

}

 

对上述对示例中对 RejectWaybillRecordEvent创建一个监听者,实现SmartApplicationListener接口:

@Component
public class RejectWaybillRecordEventListenerDemo implements SmartApplicationListener {
    
    @Override
    @AsyncEnabled
    public void onApplicationEvent(ApplicationEvent event) {
        //do something
    }
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return RejectWaybillRecordEvent.class.equals(eventType);
    }

    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
        return true;
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

最后发布事件,使用spring的话可以直接注入,所以我们直接注入ApplicationContext 对象调用发布方法,我们定义个类用于发布事件:

public class EventPublisherAssistant implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    public EventPublisherAssistant() {
    }

    public static void publishEvent(BaseApplicationEvent event) {
        applicationContext.publishEvent(event);
    }

    public void setApplicationContext(ApplicationContext context) throws BeansException {
        applicationContext = context;
    }
}

 

那么在需要发布事件的地方调用:
EventPublisherAssistant.publishEvent(new RejectWaybillRecordEvent(param1, waybillCode(), vendorOrderCode()));

 

 
那么整个事件就完成了
 
那怎么实现异步呢,我们在事件监听的类中增加了 @AsyncEnabled 注解
Spring event的异步实际上是通过多线程实现的,具体的是在org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent中
 
@Override
public void multicastEvent(ApplicationEvent event) {
   multicastEvent(event, resolveDefaultEventType(event));
}

@Override
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
   for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
      Executor executor = getTaskExecutor();
      if (executor != null) {
         executor.execute(new Runnable() {
            @Override
            public void run() {
               invokeListener(listener, event);
            }
         });
      }
      else {
         invokeListener(listener, event);
      }
   }
}

 

所以重点在是否配置了executor那么问题来了,怎么注入一个线程池呢,往哪个类注入呢最简单的方式是手动实现applicationEventMulticaster的bean,然后利用Set注入的方法注入了一个线程池,线程池也需要实例化,最简单的是直接使用spring自带的简单异步任务线程池
<!-- 任务执行器 --> 
<task:executor id="executor" pool-size="10" />
<!-- 名字必须是applicationEventMulticaster,因为AbstractApplicationContext默认找个 --> 
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster"> 
  <!-- 注入任务执行器 这样就实现了异步调用 -->
  <property name="taskExecutor" ref="executor"/> 
</bean> 

 

这种方式的配置缺点是全局的,要么全部异步,要么全部同步,如果想要同步,删除掉 <property name="taskExecutor" ref="executor"/> 这个属性那如何更优雅灵活的支持异步呢?Spring提供了@Aync注解来完成异步调用,我们在监听事件处理的onApplicationEvent方法上加上@Aync注解即可这个注解用于标注某个方法或某个类里面的所有方法都是需要异步处理的。被注解的方法被调用的时候,会在新线程中执行,而调用它的方法会在原来的线程中执行。这样可以避免阻塞、以及保证任务的实时性前提是:
<!-- 开启@AspectJ AOP代理 -->  
<aop:aspectj-autoproxy proxy-target-class="true"/> 
<task:executor id="executor" pool-size="10" />
<task:annotation-driven executor="executor" /> 

 

在我们的项目中的使用是通过定义bean的方式加载的首先定一个抽象基类实现SmartApplicationListener,定义方法onApplicationEvent
public abstract class BaseApplicationListener implements SmartApplicationListener, ApplicationContextAware, InitializingBean {
    protected Logger logger = LoggerFactory.getLogger(this.getClass());
    private static final String APPLICATION_EVENT_EXECUTOR_BEAN_NAME = "applicationEventExecutor";
    protected ApplicationContext applicationContext;
    private Executor defaultExecutor = null;
    private ConcurrentHashMap<String, Executor> asyncExecutorMap = new ConcurrentHashMap();

    public void onApplicationEvent(final ApplicationEvent applicationEvent) {
        // 1.获取异步执行线程池
        Executor asyncExecutor = this.getAsyncExecutor();
        // 2. 如果有异步线程池则使用异步模式,否则同步执行
        if (asyncExecutor != null) {
            asyncExecutor.execute(new Runnable() {
                public void run() {
                    doInternalEventResponse(applicationEvent);
                }
            });
        } else {
            doInternalEventResponse(applicationEvent);
        }
    }
 /**
     * 获取异步执行线程池
     *
     * @return
     */
    private Executor getAsyncExecutor() {
        try {
            Method onEventResponseMethod = this.getClass().getMethod("onEventResponse", ApplicationEvent.class);
            AsyncEnabled asyncAnnotation = onEventResponseMethod.getAnnotation(AsyncEnabled.class);
            if (asyncAnnotation == null) {
                return null;
            }
            String asyncExecutorName = asyncAnnotation.executor();
            // 如果指定线程池为空,则使用默认池
            if (asyncExecutorName == null || "".equals(asyncExecutorName.trim())) {
                return this.defaultExecutor;
            } else if (this.asyncExecutorMap.containsKey(asyncExecutorName)) {
                return asyncExecutorMap.get(asyncExecutorName);
            } else if (applicationContext.containsBean(asyncExecutorName)) {
                Executor asyncExecutor = this.applicationContext.getBean(asyncExecutorName, Executor.class);
                // 如果为找到指定的Executor,则使用默认线程池
                if (asyncExecutor == null) {
                    asyncExecutor = this.defaultExecutor;
                }
                this.asyncExecutorMap.put(asyncExecutorName, asyncExecutor);
                return asyncExecutor;
            } else {
                return this.defaultExecutor;
            }
        } catch (NoSuchMethodException e) {
            logger.info("基础Event-listener:getAsyncExecutor处理发生失败", e.getMessage());
        }
        return null;
    }

    private void doInternalEventResponse(ApplicationEvent applicationEvent){
        try {
            // 业务事件响应前-可扩展Event保存等操作
            onEventResponse(applicationEvent);
            // 业务事件响应后-可扩展Event处理标记等操作
        } catch (Exception e) {
            this.logger.error("Event Response 处理异常", e);
            throw new RuntimeException(e);
        } finally {
            Profiler.registerInfoEnd(callerInfo);
        }
    }

    /**
     * 事件监听处理
     */
    public abstract void onEventResponse(ApplicationEvent applicationEvent);

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void afterPropertiesSet() throws Exception {
        try {
            if (!applicationContext.containsBean(APPLICATION_EVENT_EXECUTOR_BEAN_NAME)) {
                this.logger.error("未初始化APPLICATION_EVENT_EXECUTOR,所有异步EventListener将自动调整为同步.");
                return;
            }
            this.defaultExecutor = this.applicationContext.getBean(APPLICATION_EVENT_EXECUTOR_BEAN_NAME, Executor.class);
        } catch (Exception e) {
            logger.error("从ApplicationContext中获取APPLICATION_EVENT_EXECUTOR异常,所有异步EventListener将自动调整为同步.");
        }
    }
}

 

定义的注解AsyncEnabled
/**
 * <p>
 * listener是否为异步执行注解描述
 * executor:异步执行是选择的线程池,如果为空,则采用默认线程池
 * 如果不存在默认线程池,则忽略异步设置,同步执行
 */
@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface AsyncEnabled {
    /**
     * 异步执行线程池名称
     *
     * @return
     */
    String executor() default "applicationEventExecutor";
}

 

通过名称找到对应的名叫 applicationEventExecutor的bean的线程池处理类,如果没有找到,则默认走同步执行逻辑
所以在项目中增加这个bean 的配置
<bean id="applicationEventExecutor" class="java.util.concurrent.ThreadPoolExecutor">
    <constructor-arg index="0" value="2"/>
    <constructor-arg index="1" value="5"/>
    <constructor-arg index="2" value="30"/>
    <constructor-arg index="3" value="MINUTES"/>
    <constructor-arg index="4" ref="eventDefaultQueue"/>
    <constructor-arg index="5" ref="eventDiscardPolicy"/>
</bean>

 

使用的是jdk工具类中的ThreadPoolExecutor,这个类有一个6个属性的构造方法
/**
 * Creates a new {@code ThreadPoolExecutor} with the given initial
 * parameters and default thread factory.
 *
 * @param corePoolSize the number of threads to keep in the pool, even
 *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
 * @param maximumPoolSize the maximum number of threads to allow in the
 *        pool
 * @param keepAliveTime when the number of threads is greater than
 *        the core, this is the maximum time that excess idle threads
 *        will wait for new tasks before terminating.
 * @param unit the time unit for the {@code keepAliveTime} argument
 * @param workQueue the queue to use for holding tasks before they are
 *        executed.  This queue will hold only the {@code Runnable}
 *        tasks submitted by the {@code execute} method.
 * @param handler the handler to use when execution is blocked
 *        because the thread bounds and queue capacities are reached
 * @throws IllegalArgumentException if one of the following holds:<br>
 *         {@code corePoolSize < 0}<br>
 *         {@code keepAliveTime < 0}<br>
 *         {@code maximumPoolSize <= 0}<br>
 *         {@code maximumPoolSize < corePoolSize}
 * @throws NullPointerException if {@code workQueue}
 *         or {@code handler} is null
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}

 


-- corePoolSize:最小的线程数--maximumPoolSize:最大的线程数--keepAliveTime:超过corePoolSize的那些线程,任务完成后,再经过这个时长会被结束掉--unit:keepAliveTime 对应的时间单位--workQueue:线程池所使用的缓冲队列,这个缓冲队列的长度决定了能够缓冲的最大数量,两种常用的队列方式ArrayBlockingQueue 和LinkedBlockingQueue 后面具体说明--handler:事件的兜底处理方案,默认的策略是AbortPolicy,对拒绝任务抛弃处理,并且抛出异常如果不想采用默认的策略,就配置其他的策略,也是需要实例化的:
<!--事件异步默认线程池 队列的方式:ArrayBlockingQueue-->
<bean id="eventDefaultQueue" class="java.util.concurrent.ArrayBlockingQueue">
    <constructor-arg index="0" value="1000"/>
</bean>
<!--事件异步默认线程池  拒绝策略:抛弃 -->
<bean id="eventDiscardPolicy" class="java.util.concurrent.ThreadPoolExecutor.DiscardPolicy"/>

 

到这里,通过自定义listener实现事件监听的异步处理就完成了下面是一些相关的知识点:
ArrayBlockingQueue 和LinkedBlockingQueu
ArrayBlockingQueue:是一个阻塞式的队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口。底层以数组的形式保存数据(实际上可看作一个循环数组)
LinkedBlockingQueu:也是一个阻塞式的队列,LinkedBlockingQueue保存元素的是一个链表。其内部有一个Node的内部类,其中有一个成员变量 Node next。就这样形成了一个链表的结构,要获取下一个元素,只要调用next就可以了。而ArrayBlockingQueue则是一个数组阻塞队列的特征:
* This queue orders elements FIFO (first-in-first-out).
区别:

1)LinkedBlockingQueue内部读写(插入获取)各有一个锁,而ArrayBlockingQueue则读写共享一个锁。

2)吞吐量,在源码中有一段说明:

* Linked queues typically have higher throughput than array-based queues but* less predictable performance in most concurrent applications.翻译一下:LinkedBlockingQueue比ArrayBlockingQueue有更高的吞吐量,但是性能表现更难预测(也就是说相比ArrayBlockingQueue性能表现不稳定,实际也很稳定了)

3)LinkedBlockingQueue创建时,默认会直接创建一个Integer.MAX_VALUE的数组,当插入少,读取多时,就会造成很大的空间浪费。但是Node节点的创建是根据需要动态创建的。

* <p>The optional capacity bound constructor argument serves as a* way to prevent excessive queue expansion. The capacity, if unspecified,* is equal to {@link Integer#MAX_VALUE}.  Linked nodes are* dynamically created upon each insertion unless this would bring the* queue above capacity.*
线程池对拒绝任务的处理策略:DiscardPolicy

拒绝任务是指当线程池里面的线程数量达到 maximumPoolSize 且 workQueue 队列已满的情况下被尝试添加进来的任务

在 ThreadPoolExecutor 里面定义了 4 种 handler 策略,分别是

1. CallerRunsPolicy :这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功。

2. AbortPolicy :对拒绝任务抛弃处理,并且抛出异常。

3. DiscardPolicy :对拒绝任务直接无声抛弃,没有异常信息。

4. DiscardOldestPolicy :对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。

一个任务通过 execute(Runnable) 方法被添加到线程池,任务就是一个 Runnable 类型的对象,任务的执行方法就是 Runnable 类型对象的 run() 方法。

当一个任务通过 execute(Runnable) 方法添加到线程池时,线程池采用的策略如下:

1. 如果此时线程池中的数量小于 corePoolSize ,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

2. 如果此时线程池中的数量等于 corePoolSize ,但是缓冲队列 workQueue 未满,那么任务被放入缓冲队列。

3. 如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量小于maximumPoolSize ,建新的线程来处理被添加的任务。

4. 如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量等于maximumPoolSize ,那么通过 handler 所指定的策略来处理此任务。

处理任务的优先级为:

核心线程 corePoolSize 、任务队列 workQueue 、最大线程 maximumPoolSize ,如果三者都满了,使用 handler处理被拒绝的任务。当线程池中的线程数量大于 corePoolSize 时,如果某线程空闲时间超过 keepAliveTime ,线程将被终止。这样,线程池可以动态的调整池中的线程数。


 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!