需要自定义注解的一些场景:
- 收集上报指定关键方法的入参、执行时间、返回结果等关键信息,用作后期调优;
- 关键方法在幂等性前置校验(基于本地消息表);
- 类似于Spring-Retry模块,提供关键方法多次调用重试机制;
- 提供关键方法自定义的快速熔断、服务降级等职责;
- 关键方法的共性入参检验;
- 关键方法在执行后裔的扩展行为,例如记录日志、启动其他任务等‘
- ……..
关键词:共性需求、关键方法
首先需要添加aspectj依赖
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency>
自定义注解
//记录接口的操作日志@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface OperationLog {String value() default "";}@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Target(ElementType.METHOD)public @interface ParamCheck {}
切面类
@Aspect@Component@Slf4jpublic class OperationLogAspect {@AfterReturning(pointcut = "@annotation(com.yu.annotation.OperationLog)")public void saveSysLog(JoinPoint joinPoint) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取切入点所在的方法Method method = signature.getMethod();//获取操作OperationLog operation = method.getAnnotation(OperationLog.class);if (operation != null) {String value = operation.value();//获取请求的类名String className = joinPoint.getTarget().getClass().getName();//获取请求的方法名String methodName = method.getName();//获取参数List<Object> allparams = new ArrayList<>(Arrays.asList(joinPoint.getArgs()));//打印日志log.info(className + "." + methodName + "参数信息:" + allparams.toString() + " "+value);}}}@Aspect@Component@Slf4jpublic class ParamCheckAspect {@Around("@annotation(com.yu.annotation.ParamCheck)")public Object paramCheckAspect(ProceedingJoinPoint point) throws Throwable {log.info("开始检查参数");Object[] args = point.getArgs();for (Object arg : args) {Assert.notNull(arg, "参数异常!");}long start = System.currentTimeMillis();Object proceed = point.proceed();long end = System.currentTimeMillis();log.info(point.getSignature().getName() + " 方法的执行时间:" + (end - start) + "ms");return proceed;}}
使用
@ParamCheck //自定义的参数检验注解public String getResultby2Param(String a, String b) throws InterruptedException {Thread.sleep(100);return a + b;}@OperationLog//自定义的参数检验注解public String simpleMethod(int a, String b , char c){return a + b + c;}
测试
@ParameterizedTest@DisplayName("自定义参数检验注解测试")@CsvSource({"i,", ",love", "yo,u"})void paramCheck(String a, String b) throws InterruptedException {String s = service.getResultby2Param(a, b);System.out.println(s);}@ParameterizedTest@DisplayName("自定义打印日志注解测试")@CsvSource({"99,cccc,c", "22,love,e", "33333,yo,u"})@OperationLog(value = "llllll")void operationLog(int a, String b, char c) {String s = service.simpleMethod(a, b, c);}
其本质是AOP(动态代理)+反射
//自定义注解@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyTransactional {}//动态代理必须要实现接口public interface ITest {public void manipulateDB1() throws Exception;public void manipulateDB2() throws Exception;}//为ITest的实现类创建代理class MyProxyHandler implements InvocationHandler {private ITest iTest;public MyProxyHandler(ITest iTest) {this.iTest = iTest;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Exception {System.out.println("事务开始!");Object invoke = null;try {invoke = method.invoke(iTest, args);System.out.println("事务提交");} catch (Exception e) {System.out.println("操作失败,事务回滚!");throw new Exception();}return invoke;}public ITest getProxy() {return (ITest) Proxy.newProxyInstance(iTest.getClass().getClassLoader(), iTest.getClass().getInterfaces(), this);}}//使用注解public class Test implements ITest {@Override@MyTransactional//自定义注解,支持事务public void manipulateDB1() throws Exception {System.out.println("111111111模拟操作数据库!");int i = 0;// int a = 8 / i;UnitTest.map.get("manipulateDB2").manipulateDB2();}@Override//自定义注解,支持事务@MyTransactionalpublic void manipulateDB2() throws Exception {System.out.println("2222222222模拟操作数据库!");//模拟异常int i = 0;// int a = 8 / i;}}//测试class UnitTest {static ITest test = new Test();//模拟IOC容器static Map<String, ITest> map = new HashMap<>();static {Class<? extends ITest> clzz = test.getClass();Method[] methods = clzz.getMethods();for (Method method : methods) {MyTransactional annotation = method.getAnnotation(MyTransactional.class);//有注解的方法,容器中则保存代理方法,否则保存原始方法if (annotation != null) {MyProxyHandler myProxy = new MyProxyHandler(test);map.put(method.getName(), myProxy.getProxy());} else {map.put(method.getName(), test);}}}public static void main(String[] args) throws Exception {//测试,模拟@Resource注解,根据name取组件map.get("manipulateDB1").manipulateDB1();System.out.println("------------");new Test().manipulateDB1();}}
