【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;
    }
}
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 {};
}
4.线程池配置 application.properties
为自定义线程池做配置准备
 
#线程池配置
thread.pool.core.size=10
thread.pool.max.size=10
thread.pool.queue.capacity=10000
thread.pool.alive.seconds=1000
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;
    }
}
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);
    }
}
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;
    }
}
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);
    }
}
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);
}
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());
    }
}
 
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());
    }
}
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));
        }
    }
}
到这里,基本的配置定义就完成了
======================================
下面开始具体的使用。
12.controller入口
 
@RestController
public class FirstController {
    @Autowired
    BaseService baseService;
    @RequestMapping(value = "/aoptest", method = {RequestMethod.GET})
    public String myAopTest(){
        baseService.saveBaseInfo("方法入参");
        return "测试成功";
    }
}
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 "方法出参";
    }
    
}
三.启动验证
1.PostMan 发起请求

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

 
                    
                
 

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号