springboot注解Aspect实现方案

本文提供一种自定义注解,来实现业务审批操作的DEMO,不包含审批流程的配置功能。对springboot注解Aspect实现方案感兴趣的朋友一起看看吧

目标

本文提供一种自定义注解,来实现业务审批操作的DEMO,不包含审批流程的配置功能。
具体方案是
自定义一个Aspect注解,拦截sevice方法,将拦截的信息持久化,待审批;审批时获取持久化数据,执行目标方法。

实现

POM

  4.0.0 org.springframework.bootspring-boot-starter-parent2.5.8com.procprocess-test1.0.0-SNAPSHOTprocess-testDemo project for Spring Boot 1.8  org.springframework.bootspring-boot-starter-web org.springframework.bootspring-boot-starter-testtest org.springframework.bootspring-boot-starter-aop org.springframework.bootspring-boot-configuration-processortrue com.alibabatransmittable-thread-local2.12.2   org.springframework.bootspring-boot-maven-plugin

一些实体类

CheckedParam

用于包装页面传进来的参数

package com.proc.model; import java.util.List; public class CheckedParam { //业务标记,由页面传入,用于审批时页面根据tagPageJs解析data,渲染到页面,审批管理员可看到审批的内容 private String tagPageJs; //页面传入的原始数据 private List data; public String getTagPageJs() { return tagPageJs; } public void setTagPageJs(String tagPageJs) { this.tagPageJs = tagPageJs; } public List getData() { return data; } public void setData(List data) { this.data = data; } }

ProcessDbModel

拦截的信息包装类,用于持久化数据

package com.proc.model; public class ProcessDbModel { //bean的目标类全限定名 private String targetClassName; //拦截到的service方法名 private String methodName; //页面传入的tagPageJs或Checked注解的tag private String tag; private String description; //拦截到的service入参类型,包含泛型信息 private String paramTypes; //拦截到的service入参值 private String paramArgs; //拦截到的service入参值或页面传入的原始数据 private String data; public String getTargetClassName() { return targetClassName; } public void setTargetClassName(String targetClassName) { this.targetClassName = targetClassName; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public String getTag() { return tag; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public void setTag(String tag) { this.tag = tag; } public String getParamTypes() { return paramTypes; } public void setParamTypes(String paramTypes) { this.paramTypes = paramTypes; } public String getParamArgs() { return paramArgs; } public void setParamArgs(String paramArgs) { this.paramArgs = paramArgs; } public String getData() { return data; } public void setData(String data) { this.data = data; } @Override public String toString() { return "ProcessDbModel [targetClassName=" + targetClassName + ", methodName=" + methodName + ", tag=" + tag + ", description=" + description + ", paramTypes=" + paramTypes + ", paramArgs=" + paramArgs + ", data=" + data + "]"; } }

测试用的入参对象

 package com.proc.model; import java.math.BigDecimal; public class Score { private BigDecimal langue; private BigDecimal math; private BigDecimal english; public BigDecimal getLangue() { return langue; } public void setLangue(BigDecimal langue) { this.langue = langue; } public BigDecimal getMath() { return math; } public void setMath(BigDecimal math) { this.math = math; } public BigDecimal getEnglish() { return english; } public void setEnglish(BigDecimal english) { this.english = english; } @Override public String toString() { return "Score [langue=" + langue + ", math=" + math + ", english=" + english + "]"; } }
 package com.proc.model; import java.util.List; public class Person { private String name; private String age; private String sex; private String testName; private String salary; private String work; private List grades; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getSalary() { return salary; } public void setSalary(String salary) { this.salary = salary; } public String getTestName() { return testName; } public void setTestName(String testName) { this.testName = testName; } public String getWork() { return work; } public void setWork(String work) { this.work = work; } public List getGrades() { return grades; } public void setGrades(List grades) { this.grades = grades; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", sex=" + sex + ", testName=" + testName + ", salary=" + salary + ", work=" + work + ", grades=" + grades + "]"; } }

一些工具类

JacksonCanonicalUtil

package com.proc.util; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.json.JsonMapper; public class JacksonCanonicalUtil { private static final JsonMapper MAPPER = new JsonMapper(); private JacksonCanonicalUtil () {} public static  String toCanonical (Class clazz) { return MAPPER.getTypeFactory().constructType(clazz).toCanonical(); } public static  String toCanonical (TypeReference tr) { return MAPPER.getTypeFactory().constructType(tr).toCanonical(); } //反序列化时从持久数据中获取JavaType public static JavaType constructFromCanonical (String canonical) { return MAPPER.getTypeFactory().constructFromCanonical(canonical); } }

StringZipUtil

用于压缩和解压字符串,减少持久数据占用空间

package com.proc.util; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterOutputStream; public class StringZipUtil { private StringZipUtil () {} public static String zipBase64(String text) { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { try (OutputStream os = new DeflaterOutputStream(out)) { os.write(text.getBytes(StandardCharsets.UTF_8)); } return Base64.getEncoder().encodeToString(out.toByteArray()); } catch (Exception e) { throw new RuntimeException("压缩字符串出错", e); } } public static String unzipBase64(String text) { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { try (OutputStream os = new InflaterOutputStream(out)) { os.write(Base64.getDecoder().decode(text)); } return new String(out.toByteArray(), StandardCharsets.UTF_8); } catch (Exception e) { throw new RuntimeException("解压字符串出错", e); } } }

Base64Util

一些参数值转为Base64后持久化

package com.proc.util; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.stream.Collectors; import com.fasterxml.jackson.databind.json.JsonMapper; public class Base64Util { private Base64Util () {} private static final JsonMapper MAPPER = new JsonMapper(); public static String[] toStrings (Object[] objs) { List list = new ArrayList<>(); try { for (Object obj : objs) { list.add(MAPPER.writeValueAsString(obj)); } } catch (Exception e) { throw new RuntimeException("序列化对象出错", e); } return list.toArray(new String[0]); } public static String encode (String[] strs) { List list = new ArrayList<>(); for (String str : strs) { list.add(Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8))); } String join = list.stream().collect(Collectors.joining("|")); return join; } public static String[] decode (String text) { String[] strs = text.split("\\|", -1); List list = new ArrayList<>(); for (String base64 : strs) { list.add(new String(Base64.getDecoder().decode(base64), StandardCharsets.UTF_8)); } return list.toArray(new String[0]); } public static String encodeZip (Object[] objs) { return encodeZip(toStrings(objs)); } public static String encodeZip (String[] strs) { List list = new ArrayList<>(); for (String str : strs) { list.add(StringZipUtil.zipBase64(str)); } String join = list.stream().collect(Collectors.joining("|")); return StringZipUtil.zipBase64(join); } public static String[] decodeZip (String text) { String str = StringZipUtil.unzipBase64(text); String[] strs = str.split("\\|", -1); List list = new ArrayList<>(); for (String base64 : strs) { list.add(StringZipUtil.unzipBase64(base64)); } return list.toArray(new String[0]); } }

SpringBootBeanUtil

package com.proc.util; import java.util.Map; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringBootBeanUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringBootBeanUtil.applicationContext = applicationContext; } public static ApplicationContext getApplicationContext() { return applicationContext; } public static Object getBean(String name) { return applicationContext.getBean(name); } public static  T getBean(Class clazz) { return (T) applicationContext.getBean(clazz); } public static  T getBean(String name, Class clazz) { return applicationContext.getBean(name, clazz); } public static  Map getBeansOfType(Class clazz) { return applicationContext.getBeansOfType(clazz); } }

ProcessBeanUtil

用于执行目标方法

package com.proc.util; import java.lang.reflect.Method; import org.springframework.util.ReflectionUtils; public class ProcessBeanUtil { private ProcessBeanUtil () {} public static Object excuteBeanMethod (String targetClassName, String methodName, Class[] parameterTypes, Object[] args) { Class targetClass; try { targetClass = Class.forName(targetClassName); } catch (ClassNotFoundException e) { throw new RuntimeException("未找到类", e); } return excuteBeanMethod(targetClass, methodName, parameterTypes, args); } public static Object excuteBeanMethod (Class targetClass, String methodName, Class[] parameterTypes, Object[] args) { Object bean = SpringBootBeanUtil.getBean(targetClass); Method method = ReflectionUtils.findMethod(targetClass, methodName, parameterTypes); return ReflectionUtils.invokeMethod(method, bean, args); } }

CheckedTransmitableUtil

用于传递业务参数

package com.proc.util; import com.alibaba.ttl.TransmittableThreadLocal; import com.proc.model.CheckedParam; public class CheckedTransmitableUtil { private static final TransmittableThreadLocal threadLocal = new TransmittableThreadLocal<>(); private CheckedTransmitableUtil () {} public static void set (CheckedParam checkedParam) { threadLocal.set(checkedParam); } public static CheckedParam getAndRemove () { CheckedParam checkedParam = threadLocal.get(); threadLocal.remove(); return checkedParam; } }

PrivateTransmitableUtil

为Aspect判断是否拦截提供依据

package com.proc.util; import com.alibaba.ttl.TransmittableThreadLocal; public class PrivateTransmitableUtil { private static final String CHECKED = "__CHECKED__"; private static final TransmittableThreadLocal threadLocal = new TransmittableThreadLocal<>(); private PrivateTransmitableUtil () {} public static void set () { threadLocal.set(CHECKED); } //是否执行的审批程序 public static boolean isCheck () { String checked = threadLocal.get(); threadLocal.remove(); return CHECKED.equals(checked); } }

一些Bean

PostProcess

用于拦截方法后做的个性处理

package com.proc.bean; public interface PostProcess { //返回说明内容,审批时在页面显示 String description(String tag, Class[] parameterTypes, Object[] args); //返回代替的返回值 T retObject(String tag, Class[] parameterTypes, Object[] args); }

TestCheckPostProcess

测试用

 package com.proc.bean; import org.springframework.stereotype.Component; @Component public class TestCheckPostProcess implements PostProcess { @Override public String description(String tag, Class[] parameterTypes, Object[] args) { return tag + "测试testCheck"; } @Override public String retObject(String tag, Class[] parameterTypes, Object[] args) { return tag + "返回拦截响应"; } }

Aspect注解

package com.proc.config; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; import com.proc.bean.PostProcess; @Retention(RUNTIME) @Target(METHOD) public @interface Checked { String tag() default ""; /** * @see com.proc.util.JacksonCanonicalUtil * @return */ String[] paramCanonical(); Class> postProcess(); }

切面类 CheckedAop

package com.proc.config; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.proc.bean.PostProcess; import com.proc.model.CheckedParam; import com.proc.model.ProcessDbModel; import com.proc.service.ProcessDbService; import com.proc.util.Base64Util; import com.proc.util.CheckedTransmitableUtil; import com.proc.util.PrivateTransmitableUtil; import com.proc.util.SpringBootBeanUtil; @Component @Aspect public class CheckedAop { @Autowired private ProcessDbService processDbService; //拦截Checked注释的方法 @Pointcut("@annotation(com.proc.config.Checked)") public void check() { } @Around(value = "com.proc.config.CheckedAop.check() && @annotation(checked)") public Object around(ProceedingJoinPoint joinPoint, Checked checked) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Class[] parameterTypes = signature.getParameterTypes(); String methodName = signature.getMethod().getName(); Object[] args = joinPoint.getArgs(); if (PrivateTransmitableUtil.isCheck()) { //审批后,执行业务代码 Object returnVal = joinPoint.proceed(); return returnVal; } else { //不是审批操作,拦截 Class> postProcess = checked.postProcess(); PostProcess bean = SpringBootBeanUtil.getBean(postProcess); //组装持久化数据 ProcessDbModel dbModel = new ProcessDbModel(); dbModel.setTargetClassName(joinPoint.getTarget().getClass().getName()); dbModel.setMethodName(methodName); String tag = checked.tag(); CheckedParam checkedParam = CheckedTransmitableUtil.getAndRemove(); if (checkedParam == null || checkedParam.getTagPageJs() == null || checkedParam.getTagPageJs().isEmpty()) { //不是页面调用的业务,使用注解的tag,data保存为service的参数,这时需要页面专门解析渲染 String[] argStrs = Base64Util.toStrings(args); dbModel.setParamArgs(Base64Util.encodeZip(argStrs)); dbModel.setData(Base64Util.encode(argStrs)); } else { tag = checkedParam.getTagPageJs(); dbModel.setParamArgs(Base64Util.encodeZip(args)); dbModel.setData(Base64Util.encode(checkedParam.getData().toArray(new String[0]))); } dbModel.setTag(tag); dbModel.setParamTypes(Base64Util.encodeZip(checked.paramCanonical())); dbModel.setDescription(bean.description(tag, parameterTypes, args)); //持久化数据 processDbService.save(dbModel); return bean.retObject(tag, parameterTypes, args); } } }

线程池配置

测试用

package com.proc.config; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import com.alibaba.ttl.threadpool.TtlExecutors; @Configuration public class TaskExecutePoolConfig { @Bean public Executor processExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //核心线程池大小 executor.setCorePoolSize(10); //最大线程数 executor.setMaxPoolSize(10); //队列容量 executor.setQueueCapacity(500); //活跃时间 executor.setKeepAliveSeconds(60); //线程名字前缀 executor.setThreadNamePrefix("ProcessExecutor-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); // 等待所有任务结束后再关闭线程池 executor.setWaitForTasksToCompleteOnShutdown(true); executor.initialize(); //用transmittable-thread-local包装,才可以正确给线程池中的线程传递数据 return TtlExecutors.getTtlExecutor(executor); } }

持久化service

为测试方便,未真正实现持久化

 package com.proc.service; import com.proc.model.ProcessDbModel; public interface ProcessDbService { void save (ProcessDbModel model); ProcessDbModel get (); }
 package com.proc.service.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.proc.model.ProcessDbModel; import com.proc.service.ProcessDbService; @Component public class ProcessDbServiceImpl implements ProcessDbService { private static final Logger log = LoggerFactory.getLogger(ProcessDbService.class); private volatile ProcessDbModel model; @Override public void save(ProcessDbModel model) { this.model = model; log.info(model.toString()); } @Override public ProcessDbModel get() { return this.model; } }

审批用的service

 package com.proc.service; import com.proc.model.ProcessDbModel; public interface ProcessCheckService { void process (ProcessDbModel model); }
 package com.proc.service.impl; import java.util.ArrayList; import java.util.List; import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.json.JsonMapper; import com.proc.model.ProcessDbModel; import com.proc.service.ProcessCheckService; import com.proc.util.Base64Util; import com.proc.util.JacksonCanonicalUtil; import com.proc.util.PrivateTransmitableUtil; import com.proc.util.ProcessBeanUtil; @Service public class ProcessCheckServiceImpl implements ProcessCheckService { private static final Logger log = LoggerFactory.getLogger(ProcessCheckServiceImpl.class); private static final JsonMapper MAPPER = new JsonMapper(); @Override public void process(ProcessDbModel model) { PrivateTransmitableUtil.set(); String[] paramArgs = Base64Util.decodeZip(model.getParamArgs()); String[] paramTypes = Base64Util.decodeZip(model.getParamTypes()); List> parameterTypes = new ArrayList<>(); List args = new ArrayList<>(); try { for (int i = 0; i [0]), args.toArray(new Object[0]) ); log.info(Objects.toString(ret)); } }

测试用的service

package com.proc.service; import com.proc.model.Person; import com.proc.model.Score; public interface TestService { String testCheck(Person person, String team); String testCheck2(Person person, String team); String testCheckAsync(Person person, String team); }
 package com.proc.service.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import com.proc.bean.TestCheckPostProcess; import com.proc.config.Checked; import com.proc.model.Person; import com.proc.model.Score; import com.proc.service.TestService; @Service public class TestServiceImpl implements TestService { private static final Logger log = LoggerFactory.getLogger(TestServiceImpl.class); //paramCanonical对应testCheck的参数类型 @Checked( paramCanonical = {"com.proc.model.Person", "java.lang.String"}, postProcess = TestCheckPostProcess.class) @Override public String testCheck(Person person, String team) { log.info(team + ">>>>" + person); return "target方法"; } @Checked( tag = "A1", paramCanonical = {"com.proc.model.Person", "java.lang.String"}, postProcess = TestCheckPostProcess.class) @Override public String testCheck2(Person person, String team) { log.info(team + ">>2>>" + person); return "target2方法"; } @Async("processExecutor") @Checked( paramCanonical = {"com.proc.model.Person", "java.lang.String"}, postProcess = TestCheckPostProcess.class) @Override public String testCheckAsync(Person person, String team) { log.info(team + ">>>>" + person); return "target方法"; } }

审批用的controller

 package com.proc.ctrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import com.proc.model.ProcessDbModel; import com.proc.service.ProcessCheckService; import com.proc.service.ProcessDbService; @RestController public class ProcessCheckController { private static final Logger log = LoggerFactory.getLogger(ProcessCheckController.class); @Autowired private ProcessDbService processDbService; @Autowired private ProcessCheckService processCheckService; @GetMapping(value = "process") public String process() { ProcessDbModel processDbModel = processDbService.get(); log.info(processDbModel.toString()); processCheckService.process(processDbModel); return "审批成功"; } }

测试用的controller

 package com.proc.ctrl; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import com.proc.model.CheckedParam; import com.proc.model.Person; import com.proc.model.Score; import com.proc.service.TestService; import com.proc.util.CheckedTransmitableUtil; @RestController public class TestController { @Autowired private TestService testService; //模拟页面调用 @GetMapping(value = "index") public String testCheck() { CheckedParam checkedParam = new CheckedParam(); checkedParam.setTagPageJs("01"); List data = new ArrayList<>(); data.add("前端传进来的数据1"); data.add("前端传进来的数据2"); checkedParam.setData(data); CheckedTransmitableUtil.set(checkedParam); Person person = new Person<>(); person.setName("一个人"); person.setAge("18"); person.setSex("1"); person.setSalary("20000.00"); person.setTestName("测试人"); person.setWork("工作"); Score score1 = new Score(); score1.setEnglish(new BigDecimal("12.4")); score1.setLangue(new BigDecimal("764")); score1.setMath(new BigDecimal("87.4")); Score score2 = new Score(); score2.setEnglish(new BigDecimal("12.4")); score2.setLangue(new BigDecimal("764")); score2.setMath(new BigDecimal("87.4")); List list = new ArrayList<>(); list.add(score1); list.add(score2); person.setGrades(list); testService.testCheck(person, "team>>>>>>>>"); return "12345"; } //模拟其他渠道调用 @GetMapping(value = "index2") public String testCheck2() { Person person = new Person<>(); person.setName("一个人"); person.setAge("18"); person.setSex("1"); person.setSalary("20000.00"); person.setTestName("测试人"); person.setWork("工作"); Score score1 = new Score(); score1.setEnglish(new BigDecimal("12.4")); score1.setLangue(new BigDecimal("764")); score1.setMath(new BigDecimal("87.4")); Score score2 = new Score(); score2.setEnglish(new BigDecimal("12.4")); score2.setLangue(new BigDecimal("764")); score2.setMath(new BigDecimal("87.4")); List list = new ArrayList<>(); list.add(score1); list.add(score2); person.setGrades(list); testService.testCheck2(person, "team>>>2>>>>>"); return "12345"; } //模拟调用异步方法 @GetMapping(value = "index3") public String testCheckAsync() { CheckedParam checkedParam = new CheckedParam(); checkedParam.setTagPageJs("01"); List data = new ArrayList<>(); data.add("前端传进来的数据1"); data.add("前端传进来的数据2"); checkedParam.setData(data); CheckedTransmitableUtil.set(checkedParam); Person person = new Person<>(); person.setName("一个人"); person.setAge("18"); person.setSex("1"); person.setSalary("20000.00"); person.setTestName("测试人"); person.setWork("工作"); Score score1 = new Score(); score1.setEnglish(new BigDecimal("12.4")); score1.setLangue(new BigDecimal("764")); score1.setMath(new BigDecimal("87.4")); Score score2 = new Score(); score2.setEnglish(new BigDecimal("12.4")); score2.setLangue(new BigDecimal("764")); score2.setMath(new BigDecimal("87.4")); List list = new ArrayList<>(); list.add(score1); list.add(score2); person.setGrades(list); testService.testCheckAsync(person, "team>>>3>>>>>"); return "12345"; } }

开启异步功能

package com.proc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class ProcessTestApplication { public static void main(String[] args) { SpringApplication.run(ProcessTestApplication.class, args); } }

测试

http://localhost:8080/indexhttp://localhost:8080/index2http://localhost:8080/index3
浏览器访问上面其中一个路径一次,再访问http://localhost:8080/process一次即可

到此这篇关于springboot注解Aspect的文章就介绍到这了,更多相关springboot注解Aspect内容请搜索0133技术站以前的文章或继续浏览下面的相关文章希望大家以后多多支持0133技术站!

以上就是springboot注解Aspect实现方案的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » Java