博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring事件通知机制
阅读量:6219 次
发布时间:2019-06-21

本文共 11344 字,大约阅读时间需要 37 分钟。

hot3.png

需求:首期账单支付后发起账单关联的合同的自动备案

阻碍: 1.首期账单支付成功后才发起合同自动备案,所以需要将触发开关(接口调用)放在支付服务这边,所以触发开关仅是触发,而不是调用,触发后的备案过程需要是异步执行的

2.因项目结构调整原因导致合同服务无法提供直接发起自动备案的service接口

3.自动备案接口引入了工作流引擎,调用前需先开启备案工作流,且备案本身业务代码复杂,涉及到多个service调用,若支付服务直接耦合上述业务代码则会导致支付相关接口耗时剧增

4.若合同自动备案采用定时任务,则需要扫描多张表,耗费资源大,实时性也不高

解决:采用spring事件通知机制

事件通知类:

public class PaymentEvent extends ApplicationEvent {     private static final long serialVersionUID = 1L;     /**     * 首期账单所属的合同id     */    private Long contractId;     public PaymentEvent(Object source, Long contractId) {        super(source);        this.contractId = contractId;    }     public Long getContractId() {        return contractId;    }     public void setContractId(Long contractId) {        this.contractId = contractId;    }}

发布类:

@Componentpublic class PaymentPublisher {     @Autowired    private ApplicationContext applicationContext;     /**     * 首期账单支付成功事件发布     *     * @param contractId     */    public void publish(Long contractId) {        PaymentEvent event = new PaymentEvent(this, contractId);        applicationContext.publishEvent(event);    }}

监听类:

@Componentpublic class ContractFillingListener implements ApplicationListener
{ private static final Logger LOG = LoggerFactory.getLogger(ContractFillingListener.class); @Autowired private ContractFillingBiz contractFillingBiz; @Override public void onApplicationEvent(PaymentEvent paymentEvent) { if (Objects.nonNull(paymentEvent) && Objects.nonNull(paymentEvent.getContractId())){ LOG.info("-----------------------ContractFillingListener.onApplicationEvent,ContractId:" + paymentEvent.getContractId()); contractFillingBiz.contractRecordOnlineStart(paymentEvent.getContractId()); } }}

具体发布:

@Transactional(rollbackFor = RuntimeException.class)public void afterPaySuccess(PayOrderDTO payOrderDTO) {    //更新bill表    Bill bill = newBillService.updateSuccessStatus(payOrderDTO);    //更新payorder表    payOrderService.updateSuccessStatus(payOrderDTO, OrderStatusEnum.PAY_SUCCESS.getValue());    //更新payorderbusiness表    payOrderBusinessService.updateSuccessStatus(payOrderDTO, OrderStatusEnum.PAY_SUCCESS.getValue());    if (bill.getBillOrder() == 1) {        LOGGER.info("paymentPublisher publish, contractId is " + bill.getContractId());        paymentPublisher.publish(bill.getContractId());    }}

遇到的问题: 因为账单支付成功与否是账单的状态,这个状态将在收银台付款后,被支付服务提供的回调接口更改的,所以需要将发事件通知放在更改账单状态成功后。我深以为事件通知机制类似于消息中间件可以用来异步处理的订阅发布功能,现发现当更改账单状态成功、备案失败时,回调接口不能正常返回,让我怀疑整个事件通知机制后的过程是同步执行的,也就是说上述onApplicationEvent()方法默认是同步的,查阅资料果不其然,若想异步执行则加@Async注解使其被异步执行拦截器拦截,从而异步执行,具体见AsyncExecutionInterceptor.java中的invoke()方法

@Overridepublic Object invoke(final MethodInvocation invocation) throws Throwable {   Class
targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass); final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod); if (executor == null) { throw new IllegalStateException( "No executor specified and no default executor set on AsyncExecutionInterceptor either"); } Callable task = new Callable() { @Override public Object call() throws Exception { try { Object result = invocation.proceed(); if (result instanceof Future) { return ((Future
) result).get(); } } catch (ExecutionException ex) { handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments()); } catch (Throwable ex) { handleError(ex, userDeclaredMethod, invocation.getArguments()); } return null; } }; return doSubmit(task, executor, invocation.getMethod().getReturnType());}

此外,因为是springboot项目,需要在启动类上加上@EnableAsync注解,上述方法才会异步执行才会生效。

spring的ApplicationContext提供了支持事件和代码中的监听功能。

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

spring提供了以下5种标准的事件:

1.上下文更新事件(ContextRefreshedEvent):ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext接口中使用 refresh() 方法来发生。

2.上下文开始事件(ContextStartedEvent):当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。

3.上下文停止事件(ContextStoppedEvent):当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。

4.上下文关闭事件(ContextClosedEvent):当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。

5.请求处理事件(RequestHandledEvent):这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。

ContextRefreshedEvent测试:

@Componentpublic class TestListener implements ApplicationListener
{ @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println("start..."); }}

项目启动后,会调用onApplicationEvent()方法

ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent测试:

public class CStartEventHandler implements ApplicationListener
{ @Override public void onApplicationEvent(ContextStartedEvent event) { System.out.println("start..."); }}
public class CStopEventHandler implements ApplicationListener
{ @Override public void onApplicationEvent(ContextStoppedEvent event) { System.out.println("stop..."); }}
public class CCloseEventHandler implements ApplicationListener
{ @Override public void onApplicationEvent(ContextClosedEvent event) { System.out.println("close..."); }}
public class HelloEvent {    private String message;    public void setMessage(String message){        this.message  = message;    }    public void getMessage(){        System.out.println("Your Message : " + message);    }}
public class EventApp {    public static void main(String[] args) {        ConfigurableApplicationContext context =                new ClassPathXmlApplicationContext("Beans.xml");         context.start();         HelloEvent obj = (HelloEvent) context.getBean("helloWorld");         obj.getMessage();         context.stop();        context.close();    }}

运行EventApp.java

上面是通过实现ApplicationListener泛型接口实现监听,还可以通过@EventListener注解、实现SmartApplicationListener接口方式实现:

通过@EventListener注解

public class UserBean {     private String name;    private String password;     public String getName() {        return name;    }     public void setName(String name) {        this.name = name;    }     public String getPassword() {        return password;    }     public void setPassword(String password) {        this.password = password;    }}
@Componentpublic class UserPublisher {     @Autowired    private ApplicationContext applicationContext;     public void register(UserBean user) {        //发布UserRegisterEvent事件        applicationContext.publishEvent(new UserRegisterEvent(this, user));    }}
@Componentpublic class AnnotationUserRegisterListener {     /**     * 注册监听实现方法     *     * @param userRegisterEvent 用户注册事件     */    @EventListener    public void register(UserRegisterEvent userRegisterEvent) {        UserBean user = userRegisterEvent.getUser();        System.out.println("@EventListener注册信息,用户名:" + user.getName() + ",密码:" + user.getPassword());    }}
@RestControllerpublic class UserController {     @Autowired    private UserPublisher userPublisher;         @RequestMapping(value = "/register")    public String register(UserBean user) {        userPublisher.register(user);        return "注册成功!";    }}

启动项目,访问 @EventListener注册信息,用户名:admin,密码:123456,可见事件发布后就不会考虑具体哪个监听去处理业务,甚至可以存在多个监听同时需要处理业务逻辑,并可以指定执行监听的顺序:

实现SmartApplicationListener接口

@Componentpublic class UserRegisterListener implements SmartApplicationListener {  /**     * 该方法返回true&supportsSourceType同样返回true时,才会调用该监听内的onApplicationEvent方法     *     * @param aClass 接收到的监听事件类型     * @return     */    @Override    public boolean supportsEventType(Class
aClass) { //只有UserRegisterEvent监听类型才会执行下面逻辑 return aClass == UserRegisterEvent.class; } /** * 该方法返回true&supportsEventType同样返回true时,才会调用该监听内的onApplicationEvent方法 * * @param aClass * @return */ @Override public boolean supportsSourceType(Class
aClass) { //只有在UserPublisher内发布的UserRegisterEvent事件时才会执行下面逻辑 return aClass == UserPublisher.class; } /** * supportsEventType & supportsSourceType 两个方法返回true时调用该方法执行业务逻辑 * * @param applicationEvent 具体监听实例,这里是UserRegisterEvent */ @Override public void onApplicationEvent(ApplicationEvent applicationEvent) { UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent; UserBean user = userRegisterEvent.getUser(); System.out.println("注册信息,用户名:" + user.getName() + ",密码:" + user.getPassword()); } @Override public int getOrder() { return 0; }}
@Componentpublic class UserRegisterSendMailListener implements SmartApplicationListener {    /**     * 该方法返回true&supportsSourceType同样返回true时,才会调用该监听内的onApplicationEvent方法     *     * @param aClass 接收到的监听事件类型     * @return     */    @Override    public boolean supportsEventType(Class
aClass) { //只有UserRegisterEvent监听类型才会执行下面逻辑 return aClass == UserRegisterEvent.class; } /** * 该方法返回true&supportsEventType同样返回true时,才会调用该监听内的onApplicationEvent方法 * * @param aClass * @return */ @Override public boolean supportsSourceType(Class
aClass) { //只有在UserPublisher内发布的UserRegisterEvent事件时才会执行下面逻辑 return aClass == UserPublisher.class; } /** * supportsEventType & supportsSourceType 两个方法返回true时调用该方法执行业务逻辑 * * @param applicationEvent 具体监听实例,这里是UserRegisterEvent */ @Override public void onApplicationEvent(ApplicationEvent applicationEvent) { //转换事件类型 UserRegisterEvent userRegisterEvent = (UserRegisterEvent) applicationEvent; UserBean user = userRegisterEvent.getUser(); System.out.println("用户:" + user.getName() + ",注册成功,发送邮件通知。"); } /** * 同步情况下监听执行的顺序 * * @return */ @Override public int getOrder() { return 1; }}

运行项目,继续访问 注册信息,用户名:admin,密码:123456 用户:admin,注册成功,发送邮件通知。

getOrder方法可以解决执行监听的顺序问题,return的数值越小证明优先级越高,执行顺序越靠前。

在使用org.springframework.context.ApplicationEventPublisher#publishEvent方法发布event的时候,最终会调用到spring中的org.springframework.context.event.SimpleApplicationEventMulticaster类的如下的一段代码:

public void multicastEvent(final ApplicationEvent event) {        for (final ApplicationListener listener : getApplicationListeners(event)) {            Executor executor = getTaskExecutor();            if (executor != null) {                executor.execute(new Runnable() {                    @SuppressWarnings("unchecked")                    public void run() {                        listener.onApplicationEvent(event);                    }                });            }            else {                listener.onApplicationEvent(event);            }        }    }``即getApplicationListeners()方法获得applicationContext中所有的listener,然后依次调用各个listener

转载于:https://my.oschina.net/hensemlee/blog/3006038

你可能感兴趣的文章
为Tomcat添加启动、停止、重启
查看>>
Nginx视频点播安装配置
查看>>
无状态地址自动配置
查看>>
各种排序算法的分析及java实现
查看>>
linux下退出VI的方法:不保存退出:q! 先保存后退出:wq
查看>>
小白之复习与提高3
查看>>
RAID技术
查看>>
centos6.5安装MySQL5.7(使用yum源安装方法)
查看>>
想要明白什么是key/value数据库
查看>>
模拟三次密码输入
查看>>
Linux系统下pid与pid文件及Hadoop更改pid文件存储位置
查看>>
POPTEST 150801 祝大家前途似锦
查看>>
htmlentities函数导致中文编码问题
查看>>
linux安装jdk7
查看>>
ntpdate时间同步遇到的错误
查看>>
我的友情链接
查看>>
WebRTC实现很难?让我们看看Mozilla是如何做的
查看>>
负载均衡调度器之haproxy
查看>>
一分钟设置Virtualbox桥接模式
查看>>
adb命令如何获取android手机屏幕分辨率
查看>>