一.写在开始
源码地址
做业务系统过程中,难免出现非主线业务逻辑,如果长久将非主线业务代码 塞入主线业务代码中,最终会导致主线业务代码冗余又繁琐。后期难以维护不说,看起来也不够优雅清晰。
监听者模式,可以将主线与非主线逻辑 实现优雅的隔离。而今天 就是用 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;
/**
* 自定义注解
*/
@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 监听者模式
*
*/
@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.异步完成 补充逻辑处理