目标
本文提供一种自定义注解,来实现业务审批操作的DEMO,不包含审批流程的配置功能。
具体方案是
自定义一个Aspect注解,拦截sevice方法,将拦截的信息持久化,待审批;审批时获取持久化数据,执行目标方法。
实现
POM
4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.8 com.proc process-test 1.0.0-SNAPSHOT process-test Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-configuration-processor true com.alibaba transmittable-thread-local 2.12.2 org.springframework.boot spring-boot-maven-plugin
一些实体类
CheckedParam
用于包装页面传进来的参数
package com.proc.model; import java.util.List; public class CheckedParam { //业务标记,由页面传入,用于审批时页面根据tagPageJs解析data,渲染到页面,审批管理员可看到审批的内容 private String tagPageJs; //页面传入的原始数据 private Listdata; 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 staticString 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) { Listlist = 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 staticT 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 TransmittableThreadLocalthreadLocal = 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 TransmittableThreadLocalthreadLocal = 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 extends PostProcess>> 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 extends PostProcess>> 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
测试用的service
package com.proc.service; import com.proc.model.Person; import com.proc.model.Score; public interface TestService { String testCheck(Personperson, 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"); Listdata = 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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!