【AOP】【Publish-Event】AOP 切面编程 + Spring的publish-event 实现 监听者模式,实现补充业务逻辑开发

 

 

 

一.写在开始

源码地址

做业务系统过程中,难免出现非主线业务逻辑,如果长久将非主线业务代码 塞入主线业务代码中,最终会导致主线业务代码冗余又繁琐。后期难以维护不说,看起来也不够优雅清晰。

监听者模式,可以将主线与非主线逻辑 实现优雅的隔离。而今天 就是用 AOP的方式,来实现Spring的publish-event  监听者模式。

===============================

 

二.设计代码

1.大体结构

 

 

 

 

 

 

 

 

 

2.自定义枚举 MyEnum

该枚举的定义,即不同的 非主线的补充逻辑业务,就定义一个枚举值。

package com.sxd.swapping.enums;

import java.io.Serializable;

/**
 * 自定义枚举
 */
public enum MyEnum implements Serializable {

    ONE(1,"第一种补偿业务"),
    TWO(2,"第二种补偿业务");


    //
    int value;

    //描述
    String desc;

    MyEnum(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }

    public int getValue() {
        return value;
    }

    public String getDesc() {
        return desc;
    }


    /**
     * 根据值  获取 枚举
     * @param value
     * @return
     */
    public static MyEnum valueOf(int value) {
        MyEnum[] instances = MyEnum.values();
        for(MyEnum instance : instances){
            if(instance.getValue() ==  value){
                return instance;
            }
        }

       return null;
    }
}
View Code

 

 

3.自定义注解 @MyAnno

该注解就是在 主线流程的 被切面的方法上加的注解。

这里定义了两个属性,例如 MyEnum 即代表被切面的方法 需要做一种或多种 非主线的补充逻辑,就传入一个或多个枚举值即可。

package com.sxd.swapping.annotation;

import com.sxd.swapping.enums.MyEnum;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解
 * 1.运行时注解   https://blog.csdn.net/github_35180164/article/details/52118286
 * 2.可用在方法的注解 https://blog.csdn.net/qq_37126357/article/details/101196335
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAnno {

    String myName();

    MyEnum[] myEnums() default {};
}
View Code

 

 

4.线程池配置  application.properties

为自定义线程池做配置准备

#线程池配置
thread.pool.core.size=10
thread.pool.max.size=10
thread.pool.queue.capacity=10000
thread.pool.alive.seconds=1000
View Code

 

 

5.自定义线程池  ThreadPoolConfiguration

定义多种 独立的线程池。 在不同业务块种,使用各自独立的线程池,完成异步业务的执行。【在不同实例部署,即不同业务块在各自的线程池种获取线程完成业务执行,尽量避免线程资源的争抢】

这里需要定义线程池,是使用在 做非主线的补充逻辑时,进行异步的逻辑执行。

package com.sxd.swapping.thread;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@ConditionalOnProperty(name = "thread.pool.core.size")
public class ThreadPoolConfiguration {


    @Value("${thread.pool.core.size}")
    private Integer coreSize;

    @Value("${thread.pool.max.size}")
    private Integer maxSize;

    @Value("${thread.pool.queue.capacity}")
    private Integer queueCapacity;

    @Value("${thread.pool.alive.seconds}")
    private Integer keepAliveSeconds;

    /**
     * 业务1专用线程池
     * @return
     */
    @Bean(name = "taskExecutor1")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
        poolTaskExecutor.setCorePoolSize(coreSize);
        poolTaskExecutor.setMaxPoolSize(maxSize);
        poolTaskExecutor.setQueueCapacity(queueCapacity);
        poolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
        return poolTaskExecutor;
    }

    /**
     * 业务2专用线程池
     * @return
     */
    @Bean(name = "taskExecutor2")
    public ThreadPoolTaskExecutor taskExecutor2() {
        ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
        poolTaskExecutor.setCorePoolSize(coreSize);
        poolTaskExecutor.setMaxPoolSize(maxSize);
        poolTaskExecutor.setQueueCapacity(queueCapacity);
        poolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
        return poolTaskExecutor;
    }
}
View Code

 

 

6.自定义监听上下文MyOperationHandlerContext

该上下文其实很简单,就是将 不同业务枚举 和 对应得实现handler关系建立起来,以提供给 监听器 根据枚举上得 传入枚举项 ,找对应handler去执行具体实现。

package com.sxd.swapping.aop.context;

import com.sxd.swapping.aop.handler.MyAbstractOperationHandler;
import com.sxd.swapping.aop.handler.MyOneOperationHandler;
import com.sxd.swapping.aop.handler.MyTwoOperationHandler;
import com.sxd.swapping.enums.MyEnum;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * 获取业务补充逻辑  上下文
 */
@Component
public class MyOperationHandlerContext  implements ApplicationContextAware {

    private Map<MyEnum, MyAbstractOperationHandler> operationHandlerMap;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (operationHandlerMap == null) {
            operationHandlerMap = new HashMap<>();
            operationHandlerMap.put(MyEnum.ONE,applicationContext.getBean(MyOneOperationHandler.class));
            operationHandlerMap.put(MyEnum.TWO,applicationContext.getBean(MyTwoOperationHandler.class));
        }
    }

    public MyAbstractOperationHandler getOperationHandler(MyEnum myEnum){
        return operationHandlerMap.get(myEnum);
    }

}
View Code

 

 

7.自定义监听事件 MyAspectEvent

该事件,即作为中间传递得过程变量,需要继承自 ApplicationEvent, 将注解定义属性 和 被切方法得入参 出参 等全部都作为承载体去流转

package com.sxd.swapping.aop.doamin;

import com.sxd.swapping.enums.MyEnum;
import org.springframework.context.ApplicationEvent;

public class MyAspectEvent  extends ApplicationEvent {


    MyEnum[] myEnums;

    String myName;

    Object[] requertParams;

    Object returnVal;

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




    public MyEnum[] getMyEnums() {
        return myEnums;
    }

    public void setMyEnums(MyEnum[] myEnums) {
        this.myEnums = myEnums;
    }

    public String getMyName() {
        return myName;
    }

    public void setMyName(String myName) {
        this.myName = myName;
    }

    public Object[] getRequertParams() {
        return requertParams;
    }

    public void setRequertParams(Object[] requertParams) {
        this.requertParams = requertParams;
    }

    public Object getReturnVal() {
        return returnVal;
    }

    public void setReturnVal(Object returnVal) {
        this.returnVal = returnVal;
    }
}
View Code

 

 

 

8.AOP切面编程 + Spring的publish-event  核心实现  MyAspect

该示例中,是定在 被切方法执行后,进入切面, 组装监听事件 MyAspectEvent, 最后通过 Spring的publish-event 机制,将监听事件推送出去。

package com.sxd.swapping.aop.aspect;

import com.sxd.swapping.annotation.MyAnno;
import com.sxd.swapping.aop.doamin.MyAspectEvent;
import com.sxd.swapping.enums.MyEnum;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * AOP 切面编程
 * Spring的publish-event  监听者模式
 *
 * 另一种方案:https://blog.csdn.net/weixin_43770545/article/details/105971971
 */
@Component
@Aspect
public class MyAspect implements ApplicationContextAware {

    private ApplicationContext applicationContext;

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


    //切点在加了该注解的方法上
    @Pointcut("@annotation(com.sxd.swapping.annotation.MyAnno)")
    public void myCutPoint(){
    }

    /**
     * 方法执行后  进入切面
     *
     * 监听者模式 将事件发布出去
     *
     * @param joinPoint 切点
     * @param returnVal 被切方法的范围值
     */
    @AfterReturning(value = "myCutPoint()", returning = "returnVal")
    public void afterMethodDo(JoinPoint joinPoint,Object returnVal){
        //被切方法的入参
        Object[] args = joinPoint.getArgs();
        //被代理方法信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //获取方法上的自己的注解 及 信息
        MyAnno annotation = method.getAnnotation(MyAnno.class);
        MyEnum[] myEnums = annotation.myEnums();
        String myName = annotation.myName();


        MyAspectEvent event = new MyAspectEvent(applicationContext);
        event.setRequertParams(args);
        event.setReturnVal(returnVal);
        event.setMyName(myName);
        event.setMyEnums(myEnums);

        // 发布事件对象
        applicationContext.publishEvent(event);
    }


}
View Code

 

 

 

9.自定义抽象逻辑处理类MyAbstractOperationHandler

抽象类,具体实现类下一点

package com.sxd.swapping.aop.handler;

import com.sxd.swapping.aop.doamin.MyAspectEvent;

/**
 * 抽象的业务补充逻辑类
 */
public abstract class MyAbstractOperationHandler {

    /**
     * 具体的业务处理方法
     * 交由不同业务的具体子类去 实现各自的业务逻辑
     *
     * @param operateEvent
     */
    public abstract void handleBusiness(MyAspectEvent operateEvent);
}
View Code

 

 

 

10.具体各种补充业务的逻辑处理类

每种补充逻辑业务  具体的代码实现handler

package com.sxd.swapping.aop.handler;

import com.sxd.swapping.aop.doamin.MyAspectEvent;
import org.springframework.stereotype.Component;

/**
 * 第一种业务上补充逻辑
 */
@Component
public class MyOneOperationHandler extends MyAbstractOperationHandler {

    @Override
    public void handleBusiness(MyAspectEvent operateEvent) {

        String myName = operateEvent.getMyName();

        //获取到切面方法的入参
        Object[] requertParams = operateEvent.getRequertParams();

        //获取到切面方法的出参
        Object returnVal = operateEvent.getReturnVal();

        System.out.println("第一种补充业务-打印方法入参:" + requertParams[0].toString());
        System.out.println("做第一种补充业务:"+myName);
        System.out.println("第一种补充业务-打印方法出参:"+ returnVal.toString());


    }

}
View Code
package com.sxd.swapping.aop.handler;

import com.sxd.swapping.aop.doamin.MyAspectEvent;
import org.springframework.stereotype.Component;

/**
 * 第二种业务上的补充逻辑
 */
@Component
public class MyTwoOperationHandler  extends MyAbstractOperationHandler {


    @Override
    public void handleBusiness(MyAspectEvent operateEvent) {


        String myName = operateEvent.getMyName();

        //获取到切面方法的入参
        Object[] requertParams = operateEvent.getRequertParams();

        //获取到切面方法的出参
        Object returnVal = operateEvent.getReturnVal();

        System.out.println("第二种补充业务-打印方法入参:" + requertParams[0].toString());
        System.out.println("做第二种补充业务:"+myName);
        System.out.println("第二种补充业务-打印方法出参:"+ returnVal.toString());

    }
}
View Code

 

 

 

11.自定义监听器MyEventListener

即Spring的publish-event 的监听接收者。接收到 push出来的event之后,根据不同枚举,从上下文中取到对应实现的handler,异步 执行具体的业务逻辑。

package com.sxd.swapping.aop.listener;


import com.sxd.swapping.aop.context.MyOperationHandlerContext;
import com.sxd.swapping.aop.doamin.MyAspectEvent;
import com.sxd.swapping.aop.handler.MyAbstractOperationHandler;
import com.sxd.swapping.enums.MyEnum;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 *监听者模式  的监听者对象
 */
@Component
public class MyEventListener {

    @Resource
    private MyOperationHandlerContext operationHandlerContext;

    @Resource
    private ThreadPoolTaskExecutor taskExecutor1;

    //使用@Async指定线程池 的 异步调用 https://www.jb51.net/article/105214.htm
    @Async("taskExecutor1")
    @EventListener
    public void listener(MyAspectEvent myAspectEvent){
        //获取到 自定义注解上的属性
        MyEnum[] myEnums = myAspectEvent.getMyEnums();

        //根据自定义注解 使用处 指定的补充事件枚举项,分别执行 对应的具体补充事件handler实现的逻辑
        for (MyEnum myEnum : myEnums) {
            MyAbstractOperationHandler operationHandler = operationHandlerContext.getOperationHandler(myEnum);
            taskExecutor1.execute(() -> operationHandler.handleBusiness(myAspectEvent));
        }

    }
}
View Code

 

 

 

到这里,基本的配置定义就完成了

======================================

下面开始具体的使用。

 

12.controller入口

@RestController
public class FirstController {

    @Autowired
    BaseService baseService;

    @RequestMapping(value = "/aoptest", method = {RequestMethod.GET})
    public String myAopTest(){
        baseService.saveBaseInfo("方法入参");
        return "测试成功";
    }

}
View Code

 

 

13.Service 实际被自定义注解切面的方法

切面上,通过自定义注解,将 不同业务对应的枚举定义好,进入切面后,取枚举,做不同业务。

@Service
public class BaseServiceImpl implements BaseService {



    @Override
    @MyAnno(myName = "德玛西亚", myEnums = {MyEnum.ONE,MyEnum.TWO})
    public String saveBaseInfo(String  param) {
        System.out.println("进入被切方法");
        return "方法出参";
    }
    
}
View Code

 

 

 

 

三.启动验证

1.PostMan 发起请求

 

 

 

2.异步完成 补充逻辑处理

 

posted @ 2021-03-29 11:30  Angel挤一挤  阅读(778)  评论(0编辑  收藏  举报