代码环境:WIN7+IDEA+JAD1.8+Spring Boot 2.0
首先说一下我为什么使用事件,比如现在创建一个订单但是我创建成功后要给客户发送一条短信和一个邮件提醒,本身没创建订单一系列操作就需要很多时间但是我还要去发送短信和邮件,期间还要调用其它服务来实现耗时比较长达不到客户的满意度,所以使用的方式可以说一下:
1:activeMQ(异步)
2:使用spring事件监听(同步+异步)
下面我们只说第二种方式
在谈Spring的事件监听之前,让我们先了解一下Spring容器,什么是ApplicationContext ?
它是Spring的核心,Context我们通常解释为上下文环境,但是理解成容器会更好些。
ApplicationContext则是应用的容器。
Spring把Bean(object)放在容器中,需要用就通过get方法取出来。
此接口提供给Spring应用配置的能力,当应用启动时,此接口的实现是只读的,但是如果该实现支持,其内容也是可以重新载入的。
ApplicationContext大概提功能如下能力:
1.获取应用组件的bean工厂方法,此能力继承自org.springframework.beans.factory.ListableBeanFactory。
2.加载资源文件的能力,此能力继承自org.springframework.core.io.ResourceLoader
3.发布事件到已注册的监听器,此能力继承自ApplicationEventPublisher
4.提供国际化的消息访问,此能力继承自MessageSource
好对ApplicationContext有一定了解之后我们再来看看Spring提供的事件监听。
为了实现事件监听的能力Spring为我们提供了两个顶层接口/抽象类
ApplcationEvent:是个抽象类,里面只有一个构造函数和一个长整型的timestamp。我们自定义的Application event 需要继承这个抽象类.
ApplicationListener:是一个接口,里面只有一个方法onApplicationEvent ,每一个实现改接口的类都要自己实现这个方法。
Spring的事件监听是基于标准的观察者模式的,如果在ApplicationContext部署了一个实现了ApplicationListener的bean,那么当一个ApplicationEvent发布到
ApplicationContext时,这个bean得到通知并作特定的处理。
从上面这段话我们很容易产生两点思考:1.实现了ApplicationListener的bean如何部署到ApplicationContext 2.一个ApplicationEvent如何发布到ApplicationContext
下面我们就通过具体的代码来看看这两个问题
废话少说,先看代码!
第一:先自定义一个MsgEvent,它本身提供一个print()方法:
public class MessageEvent extends ApplicationEvent {
private final String message;//事件交互信息
private final String JNDI;//过滤指定监听
private final String desc;//描述可传特殊参数不满足时扩展改成MAP/Object目前没遇到太特殊的
public MessageEvent(Object source,String message,String desc) {
super(source);
this.message = message;
this.JNDI = (String) source;
this.desc = desc;
}
public String getJNDI() {
return JNDI;
}
public String getMessage() {
return message;
}
public String getDesc() {
return desc;
}
第二:定义一个监听
@Component
public class MyLisenter implements ApplicationListener {
@Override
public void onApplicationEvent(MyEvent myEvent) {
myEvent.printMsg(myEvent.getMsg());
System.out.println("监听到。。。");
}
}
**第三:**现在自定义事件和监听器都好了,我们就来看看第一个问题,监听器如何部署到ApplicationContext,有四种方式可以实现,我们一个一个看:
1.应用启动之前调用SpringApplication的addListeners方法将自定义的监听器加入。
2.监听器部署到ApplicationContext,实际上就是将将监听器交给Spring 容器管理,所以最简单的方法只需在自定义的PrintListener上加上@Component注解就行了把上图//事件配置监听注掉就行了这个注解就可以实现。
3.在配置文件中加入context.listener.classes: com.snow.listener.PrintListener
注释掉PrintListener上的@Component注解
源码分析:
Spring内置DelegatingApplicationListener会监听ApplicationEnvironmentPreparedEvent事件(Environment已经准备好但是Context还没有创建事件),
读取Environment中的context.listener.classes属性值(监听器类全路径,多个以逗号隔开)来创建ApplicationListener,详情见如下源码中我的注释。
4.使用@EventListener注解,先看代码,建立一个普通的java类并交给spring容器,其中一个处理event的方法,加上该注解,删掉配置文件中的配置。
好以上四点就回答了我们开头提出的第一个问题:
实现了ApplicationListener的bean如何部署到ApplicationContext。接下来轮到第二个问题了,
一个ApplicationEvent如何发布到ApplicationContext。我们还是来看测试的主代码:
上面的这段代码可以看出ConfigurableApplicationContext具有发布事件的能力,而通过下面的类图我们知道ConfigurableApplicationContext是
ApplicationContext的子接口,文章开头对ApplicationContext的分析我们知道ApplicationContext具有发布事件的能力,这项能力是通过继承
ApplicationEventPublisher获得的。
通过以上分析,我们不难发现,只要我们在一个普通的java bean注入ApplicationContext的实例,那么这个bean就获得了发布事件的能力。
那么怎样才能在一个普通的bean中注入ApplicationContext的实例呢?看ApplicationContext的源码,源码中有这一段注释:
这段注释大概的意思是ApplicationContext的实例可以探测和唤起AppliationContextAware,ResourceLoaderAware,ApplicationEventPublisherAware,MessageSourceAware等,我们顺势看下AppliationContextAware的代码,如下:
我们注意到ApplicationContext的事件发布能力是继承自ApplicationEventPublisher,并且ApplicationContextAware中有这样一段注释:
这段注释可以看出,如果仅仅想获取发布事件的能力,只需要实现ApplicationEventPublisherAware就行了。
照着这样的思路代码如下:
定义一个事件发布器
测试代码:
**第一步:**在启动类添加注解@EnableAsync,自定义线程池类
创建一个配置类ExecutorConfig,用来定义如何创建一个ThreadPoolTaskExecutor,要使用@Configuration和@EnableAsync这两个注解,表示这是个配置类,并且是线程池的配置类
如下所示:
@Configuration
@EnableAsync
public class TaskExecutePool {
private static final Logger log = LoggerFactory.getLogger(TaskExecutePool.class);
@Bean("myTaskAsyncPool")
public Executor myTaskAsyncPool() {
log.info("start TaskExecutePool myTaskAsyncPool");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);//配置核心线程数
executor.setMaxPoolSize(20);//配置核心线程数
executor.setQueueCapacity(1000);//配置队列容量
executor.setKeepAliveSeconds(60);//设置线程活跃时间
executor.setThreadNamePrefix("myTaskAsyn-");//设置线程名
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //设置拒绝策略
executor.initialize();
return executor;
}
}
注意,上面的方法名称为asyncServiceExecutor,
@Async(“asyncServiceExecutor”):即配置线程池的方法名,此处如果不写自定义线程池的方法名,会使用默认的线程池
第二步:自定义事件发布类
public class MessageEvent extends ApplicationEvent {
private final String message;//事件交互信息
private final String JNDI;//过滤指定监听
private final String desc;//描述可传特殊参数不满足时扩展改成MAP/Object目前没遇到太特殊的
public MessageEvent(Object source,String message,String desc) {
super(source);
this.message = message;
this.JNDI = (String) source;
this.desc = desc;
}
public String getJNDI() {
return JNDI;
}
public String getMessage() {
return message;
}
public String getDesc() {
return desc;
}
第三步:抽取事件发布公共类
@Component
public class EventPublisher implements ApplicationEventPublisherAware {
private static ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public static void publishEvent(ApplicationEvent applicationEvent) {
applicationEventPublisher.publishEvent(applicationEvent);
}
}
第四步:编写监听事件
@EventListener有个参数condition进行event属性过滤。
比如我查的看的有这样的目前没研究太多:
@EventListener
@EventListener(condition = "#event.test == 'foo'")(我使用的这个)
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
@EventListener(classes = {BlackListEvent.class})
@Component
public class ProvNotifier {
private static final Logger log = LoggerFactory.getLogger(ProvNotifier.class);
@Autowired
private IProvSvc iProvSvc;
@Async
@EventListener( condition= "#event.JNDI == 'sendToProv'")
public void onApplicationEvent(MessageEvent event) {
log.info(" ==>异步事件启动 onApplicationEvent sendToProv Message:" + event.getMessage());
try {
iProvSvc.sendToProv(event.getMessage(),event.getDesc());
log.info(" ==>送开通成功!");
} catch (Exception e) {
log.error(" %%%%%% onApplicationEvent listener sendToProv:" + e.getMessage());
e.printStackTrace();
}
}
}
第五步:测试
结果:
这是异步的实现基本结束,也可以根据自己的业务规则实现配置化。
看了很多大神贴也有抄袭多见谅:https://blog.csdn.net/LouisQMei/article/details/79605590
如果有优化还会更细,也请有好的想法指教指教。