需要自定义注解的一些场景:

    • 收集上报指定关键方法的入参、执行时间、返回结果等关键信息,用作后期调优;
    • 关键方法在幂等性前置校验(基于本地消息表);
    • 类似于Spring-Retry模块,提供关键方法多次调用重试机制;
    • 提供关键方法自定义的快速熔断、服务降级等职责;
    • 关键方法的共性入参检验;
    • 关键方法在执行后裔的扩展行为,例如记录日志、启动其他任务等‘
    • ……..

    关键词:共性需求、关键方法

    首先需要添加aspectj依赖

    1. <dependency>
    2. <groupId>org.aspectj</groupId>
    3. <artifactId>aspectjweaver</artifactId>
    4. </dependency>

    自定义注解

    1. //记录接口的操作日志
    2. @Target({ElementType.METHOD})
    3. @Retention(RetentionPolicy.RUNTIME)
    4. @Inherited
    5. @Documented
    6. public @interface OperationLog {
    7. String value() default "";
    8. }
    9. @Retention(RetentionPolicy.RUNTIME)
    10. @Documented
    11. @Inherited
    12. @Target(ElementType.METHOD)
    13. public @interface ParamCheck {
    14. }

    切面类

    1. @Aspect
    2. @Component
    3. @Slf4j
    4. public class OperationLogAspect {
    5. @AfterReturning(pointcut = "@annotation(com.yu.annotation.OperationLog)")
    6. public void saveSysLog(JoinPoint joinPoint) {
    7. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    8. //获取切入点所在的方法
    9. Method method = signature.getMethod();
    10. //获取操作
    11. OperationLog operation = method.getAnnotation(OperationLog.class);
    12. if (operation != null) {
    13. String value = operation.value();
    14. //获取请求的类名
    15. String className = joinPoint.getTarget().getClass().getName();
    16. //获取请求的方法名
    17. String methodName = method.getName();
    18. //获取参数
    19. List<Object> allparams = new ArrayList<>(Arrays.asList(joinPoint.getArgs()));
    20. //打印日志
    21. log.info(className + "." + methodName + "参数信息:" + allparams.toString() + " "+value);
    22. }
    23. }
    24. }
    25. @Aspect
    26. @Component
    27. @Slf4j
    28. public class ParamCheckAspect {
    29. @Around("@annotation(com.yu.annotation.ParamCheck)")
    30. public Object paramCheckAspect(ProceedingJoinPoint point) throws Throwable {
    31. log.info("开始检查参数");
    32. Object[] args = point.getArgs();
    33. for (Object arg : args) {
    34. Assert.notNull(arg, "参数异常!");
    35. }
    36. long start = System.currentTimeMillis();
    37. Object proceed = point.proceed();
    38. long end = System.currentTimeMillis();
    39. log.info(point.getSignature().getName() + " 方法的执行时间:" + (end - start) + "ms");
    40. return proceed;
    41. }
    42. }

    使用

    1. @ParamCheck //自定义的参数检验注解
    2. public String getResultby2Param(String a, String b) throws InterruptedException {
    3. Thread.sleep(100);
    4. return a + b;
    5. }
    6. @OperationLog//自定义的参数检验注解
    7. public String simpleMethod(int a, String b , char c){
    8. return a + b + c;
    9. }

    测试

    1. @ParameterizedTest
    2. @DisplayName("自定义参数检验注解测试")
    3. @CsvSource({"i,", ",love", "yo,u"})
    4. void paramCheck(String a, String b) throws InterruptedException {
    5. String s = service.getResultby2Param(a, b);
    6. System.out.println(s);
    7. }
    8. @ParameterizedTest
    9. @DisplayName("自定义打印日志注解测试")
    10. @CsvSource({"99,cccc,c", "22,love,e", "33333,yo,u"})
    11. @OperationLog(value = "llllll")
    12. void operationLog(int a, String b, char c) {
    13. String s = service.simpleMethod(a, b, c);
    14. }

    其本质是AOP(动态代理)+反射

    1. //自定义注解
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Target(ElementType.METHOD)
    4. public @interface MyTransactional {
    5. }
    6. //动态代理必须要实现接口
    7. public interface ITest {
    8. public void manipulateDB1() throws Exception;
    9. public void manipulateDB2() throws Exception;
    10. }
    11. //为ITest的实现类创建代理
    12. class MyProxyHandler implements InvocationHandler {
    13. private ITest iTest;
    14. public MyProxyHandler(ITest iTest) {
    15. this.iTest = iTest;
    16. }
    17. @Override
    18. public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
    19. System.out.println("事务开始!");
    20. Object invoke = null;
    21. try {
    22. invoke = method.invoke(iTest, args);
    23. System.out.println("事务提交");
    24. } catch (Exception e) {
    25. System.out.println("操作失败,事务回滚!");
    26. throw new Exception();
    27. }
    28. return invoke;
    29. }
    30. public ITest getProxy() {
    31. return (ITest) Proxy.newProxyInstance(iTest.getClass().getClassLoader(), iTest.getClass().getInterfaces(), this);
    32. }
    33. }
    34. //使用注解
    35. public class Test implements ITest {
    36. @Override
    37. @MyTransactional//自定义注解,支持事务
    38. public void manipulateDB1() throws Exception {
    39. System.out.println("111111111模拟操作数据库!");
    40. int i = 0;
    41. // int a = 8 / i;
    42. UnitTest.map.get("manipulateDB2").manipulateDB2();
    43. }
    44. @Override//自定义注解,支持事务
    45. @MyTransactional
    46. public void manipulateDB2() throws Exception {
    47. System.out.println("2222222222模拟操作数据库!");
    48. //模拟异常
    49. int i = 0;
    50. // int a = 8 / i;
    51. }
    52. }
    53. //测试
    54. class UnitTest {
    55. static ITest test = new Test();
    56. //模拟IOC容器
    57. static Map<String, ITest> map = new HashMap<>();
    58. static {
    59. Class<? extends ITest> clzz = test.getClass();
    60. Method[] methods = clzz.getMethods();
    61. for (Method method : methods) {
    62. MyTransactional annotation = method.getAnnotation(MyTransactional.class);
    63. //有注解的方法,容器中则保存代理方法,否则保存原始方法
    64. if (annotation != null) {
    65. MyProxyHandler myProxy = new MyProxyHandler(test);
    66. map.put(method.getName(), myProxy.getProxy());
    67. } else {
    68. map.put(method.getName(), test);
    69. }
    70. }
    71. }
    72. public static void main(String[] args) throws Exception {
    73. //测试,模拟@Resource注解,根据name取组件
    74. map.get("manipulateDB1").manipulateDB1();
    75. System.out.println("------------");
    76. new Test().manipulateDB1();
    77. }
    78. }