spring event 的使用

ApplicationEvent以及Listener是Spring为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。事件发布者并不需要考虑谁去监听,监听具体的实现内容是什么,发布者的工作只是为了发布事件而已。

ApplicationContextAware 接口提供了publishEvent 方法,

方式一:

定义自己的event 事件继承 ApplicationEvent 类,

package com.tiefan.xff.nec.support.algorithm;

import org.springframework.context.ApplicationEvent;

/**
*
* Created by stefanxu on 2019/4/2.
*/
public class DemoEvent extends ApplicationEvent {

    private String name;

    /**
    * Create a new ApplicationEvent.    
    *
    * @param source the object on which the event initially occurred (never {@code null}) 
    */
    public DemoEvent(Object source) {
        super(source);
    }

    public DemoEvent(Object source, String name) {
        super(source);
        this.name = name;
    }

    public String getName() {
        return name;
    }

}

事件发布,

package com.tiefan.xff.nec.web.view;

import com.tiefan.fbs.fsp.base.core.utils.Response;
import com.tiefan.xff.nec.support.algorithm.DemoEvent;
import com.tiefan.xff.nec.support.algorithm.DemoEventOne;
import com.tiefan.xff.nec.support.utils.ResponseVoBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
*
* Created by stefanxu on 2019/4/2.
*/
@RestController
public class DemoEventPublisher {

    @Autowired
    private ApplicationContext applicationContext;

    @GetMapping("/event/{name}")
    public Response publishEvent(@PathVariable("name") String name){

        DemoEvent demoEvent = new DemoEvent(this, name);
        applicationContext.publishEvent(demoEvent);
        return ResponseVoBuilder.build(true, name);

    }

}

this 说明,自定义的DemoEvent 继承了 ApplicationEvent ,必须重载构造函数,构造函数的参数可以任意指定,source指的是发生时间的对象,个人理解应该是发布事件的所在的类,这里用this 关键字来替代类对象。

事件的监听,

package com.tiefan.xff.nec.support.algorithm;

import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
* Created by stefanxu on 2019/4/2.
*/
@Component
public class DemoEventListener implements ApplicationListener<DemoEvent> {

    @Async
    @Override
    public void onApplicationEvent(DemoEvent event) {
        System.out.println("receive: " + event.getName());
    }

}

@Async,开启方法的异步调用,需要spring 开启异步调用的支持,监听器这个类所在容器上添加 @EnableAsync 这样这个类会被代理,

方式二:

ApplicationEventPublisher 在spring 4.2 版本之后提供了一个新的方法,可以不用 传入自己定义的event,

定义事件,

package com.tiefan.xff.nec.support.algorithm;

/**
*
* Created by stefanxu on 2019/4/2.
*/
public class DemoEventOne {

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}

发布事件,

package com.tiefan.xff.nec.web.view;

import com.tiefan.fbs.fsp.base.core.utils.Response;
import com.tiefan.xff.nec.support.algorithm.DemoEvent;
import com.tiefan.xff.nec.support.algorithm.DemoEventOne;
import com.tiefan.xff.nec.support.utils.ResponseVoBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
*
* Created by stefanxu on 2019/4/2.
*/
@RestController
public class DemoEventPublisher {

    @Autowired
    private ApplicationContext applicationContext;

    @GetMapping("/event/{name}")
    public Response publishEvent(@PathVariable("name") String name){
        DemoEventOne demoEventOne = new DemoEventOne();
        demoEventOne.setName(name);
        applicationContext.publishEvent(demoEventOne);
        return ResponseVoBuilder.build(true, name);
    }

}

事件监听,

package com.tiefan.xff.nec.support.algorithm;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
*
* Created by stefanxu on 2019/4/2.
*/
@Component
public class DemoEventListenerOne {

    private static Logger LOG = LoggerFactory.getLogger(DemoEventListenerOne.class);

    @Async
    @EventListener
    public void getEvent(DemoEventOne event) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOG.info("receive one: {}", event.getName());
    }

    @Async
    @EventListener
    public void getEvent1(DemoEventOne event) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOG.info("receive one1: {}", event.getName());
    }

}

可以有多个监听者,就可以实现类似,订单成功之后,要发送各种消息、短信、积分、返现等,对接下来的业务进行处理,就可以对业务进行解耦了,

如果不使用异步方式的话,主线程会被会执行完所有的监听者的业务逻辑,通常我们可以开启异步的方式释放主业务线程,缩短主线程的时间

@Async

默认使用 SimpleAsyncTaskExecutor 来处理方法的调用,只不过从源码上来看并不是使用的线程池,而是每次都创建一个新的线程

@Override
public void execute(Runnable task, long startTimeout) {
    Assert.notNull(task, "Runnable must not be null");
    Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) :task);
    if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
        this.concurrencyThrottle.beforeAccess();
        doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
    } else {
        doExecute(taskToUse);
    }
}

protected void doExecute(Runnable task) {
    Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
    thread.start();
}

所以带来的一个问题,如果有大量的任务过来,就会创建大量的线程,线程使用完成之后需要销毁,极端情况下引起 OutOfMemoryError unable to create new native thread

为了处理缓解这个问题,spring 提供了限流的功能,

if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
    this.concurrencyThrottle.beforeAccess();
    doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
}

判断 是否启用限流功能,

开启限流,

package com.tiefan.xff.nec.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.Executor;

/**
*
* Created by stefanxu on 2019/4/3.
*/
@Configuration
@EnableAsync
public class AsyncCommonConfig extends AsyncConfigurerSupport {
    @Override
    public Executor getAsyncExecutor() {
        SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
        // 设置允许同时执行的线程数为10
        executor.setConcurrencyLimit(10);
        return executor;
    }
}

执行 concurrencyThrottle.beforeAccess()

protected void beforeAccess() {
    if (this.concurrencyLimit == NO_CONCURRENCY) {
        throw new IllegalStateException("Currently no invocations allowed - concurrency limit set to NO_CONCURRENCY");
    }
    if (this.concurrencyLimit > 0) {
        boolean debug = logger.isDebugEnabled();
        synchronized (this.monitor) {
            boolean interrupted = false;
            while (this.concurrencyCount >= this.concurrencyLimit) {
                if (interrupted) {
                    throw new IllegalStateException("Thread was interrupted while waiting for invocation access, " + "but concurrency limit still does not allow for entering");
                }
                if (debug) {
                    logger.debug("Concurrency count " + this.concurrencyCount + " has reached limit " + this.concurrencyLimit + " - blocking");
                }
                try {
                    this.monitor.wait();
                } catch (InterruptedException ex) {
                    // Re-interrupt current thread, to allow other threads to react.
                    Thread.currentThread().interrupt();
                    interrupted = true;
                }
            }
            if (debug) {
                logger.debug("Entering throttle at concurrency count " + this.concurrencyCount);
            }
            this.concurrencyCount++;
        }
    }
}

如果超过了限流的个数,则会调用 this.monitor.wait(); 释放之前获取的对象锁,进入等待状态。等待被唤醒,那么这个线程就被阻塞在这里了

其他任务执行完成之后,会释放唤醒等待 的对象锁线程,重新竞争对象锁,并开始执行任务

protected void afterAccess() {
    if (this.concurrencyLimit >= 0) {
        synchronized (this.monitor) {
            this.concurrencyCount--;
            if (logger.isDebugEnabled()) {
                logger.debug("Returning from throttle at concurrency count " + this.concurrencyCount);
            }
            this.monitor.notify();
        }
    }
}

如果开启了限流功能,那么可以控制线程数,但是执行效率会降低,会出现业务线程等待的情况,虽然开启了异步功能,但是这个时候还没有创建子线程,然后把任务交给子线程去处理,当然还有线程等待竞争锁的情况,这个场景适合业务处理很快的场景,一单出现处理较慢的请况,就有可能出现线程积压,甚至内存溢出

所以需要改写为一个线程池来处理异步的任务,

创建一个线程池,这里使用spring 提供的ThreadPoolTaskExecutor,当然也可以使用 jdk 自带的


@Bean
public Executor taskExecute(){
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    executor.setMaxPoolSize(20);
    executor.setQueueCapacity(200);
    executor.setKeepAliveSeconds(60);
    executor.setThreadNamePrefix("taskExecutor-");
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.initialize();
    return executor;
}

@Async 注解可以指定线程池,

@Async("taskExecute")
@EventListener
public void getEvent(DemoEventOne event) {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    LOG.info("receive one: {}", event.getName());
}

每个使用次线程池的异步方法都需要添加线程池的名字

如果不想每个都添加的话,与开启限流功能类似,复写AsyncConfigurer 或者 AsyncConfigurer 的 getAsyncExecutor() 方法,返回前面定义的线程池

package com.tiefan.xff.nec.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
*
* Created by stefanxu on 2019/4/3.
*/
@Configuration
@EnableAsync
public class AsyncCommonConfig extends AsyncConfigurerSupport {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("taskExecutor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

拒绝策略可以自定义,方便线程池满了之后任务被拒绝后可以记录日志或者采取其他的告警方式。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值