基本知识

在Java中,注解分为两种,元注解和自定义注解。
很多人误以为自定义注解就是开发者自己定义的,而其它框架提供的不算,但是其实上面我们提到的那几个注解其实都是自定义注解。
关于”元”这个描述,在编程世界里面有都很多,比如”元注解”、”元数据”、”元类”、”元表”等等,这里的”元”其实都是从meta翻译过来的。
一般我们把元注解理解为描述注解的注解元数据理解为描述数据的数据元类理解为描述类的类
所以,在Java中,除了有限的几个固定的”描述注解的注解”以外,所有的注解都是自定义注解。
在JDK中提供了4个标准的用来对注解类型进行注解的注解类(元注解),他们分别是:

@Target @Retention @Documented @Inherited

除了以上这四个,所有的其他注解全部都是自定义注解。
这里不准备深入介绍以上四个元注解的作用,大家可以自行学习。
本文即将提到的几个例子,都是作者在日常工作中真实使用到的场景,这例子有一个共同点,那就是都用到了Spring的AOP技术。
什么是AOP以及他的用法相信很多人都知道,这里也就不展开介绍了。

使用自定义注解做日志记录

不知道大家有没有遇到过类似的诉求,就是希望在一个方法的入口处或者出口处做统一的日志处理,比如记录一下入参、出参、记录下方法名、参数等。
如果在每一个方法中自己写这样的代码的话,一方面会有很多代码重复,另外也容易被遗漏。
这种场景,就可以使用自定义注解+切面实现这个功能。

首先我们自定义一个注解:

  1. package com.example.Aop.annotation;
  2. import java.lang.annotation.*;
  3. /**
  4. * @Description: 自定义注解
  5. * @Author: jinsilei
  6. * @Date: 2020/11/12
  7. */
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Target(ElementType.METHOD)
  10. @Documented
  11. public @interface EagleEye {
  12. String desc() default "";
  13. }

接下来我们编写自定义切面

  1. package com.example.Aop.Aspect;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.annotation.Around;
  4. import org.aspectj.lang.annotation.Aspect;
  5. import org.aspectj.lang.annotation.Pointcut;
  6. import org.springframework.stereotype.Component;
  7. import org.springframework.web.context.request.RequestContextHolder;
  8. import org.springframework.web.context.request.ServletRequestAttributes;
  9. import javax.servlet.http.HttpServletRequest;
  10. import java.util.Arrays;
  11. /**
  12. * @Description: 自定义切面
  13. * @Author: jinsilei
  14. * @Date: 2020/11/12
  15. */
  16. @Aspect
  17. @Component
  18. public class LogAspect {
  19. @Pointcut("@annotation(com.example.Aop.annotation.EagleEye)")
  20. public void eagleEye() {
  21. }
  22. // 利用环绕增强来实现我们的功能
  23. @Around("eagleEye()")
  24. public Object surroundInform(ProceedingJoinPoint proceedingJoinPoint) {
  25. System.out.println("环绕通知开始...");
  26. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  27. HttpServletRequest request = attributes.getRequest();
  28. System.out.println("请求路径 : " + request.getRequestURL());
  29. System.out.println("请求方式 : " + request.getMethod());
  30. System.out.println("方法名 : " + proceedingJoinPoint.getSignature().getName());
  31. System.out.println("类路径 : " + proceedingJoinPoint.getSignature().getDeclaringTypeName());
  32. System.out.println("参数 : " + Arrays.toString(proceedingJoinPoint.getArgs()));
  33. try {
  34. // 真实业务代码,这里是伪代码
  35. Object o = proceedingJoinPoint.proceed();
  36. System.out.println("方法环绕proceed,结果是 :" + o);
  37. return o;
  38. } catch (Throwable e) {
  39. e.printStackTrace();
  40. return null;
  41. }
  42. }
  43. }

接口测试

  1. package com.example.Aop.run;
  2. import com.example.Aop.annotation.EagleEye;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. public class TestAction {
  7. @EagleEye(desc = "测试接口")
  8. @RequestMapping(value = "/sayHello")
  9. public String test(String params)throws Exception{
  10. System.out.println("参数:" + params);
  11. return "hello "+ params;
  12. }
  13. }

运行结果

image.png
这样一个简单的自定义注解就完成了,代码其实都差不多,思路也比较简单,就是通过自定义注解来标注需要被切面处理的累或者方法,然后在切面中对方法的执行过程进行干预,比如在执行前或者执行后做一些特殊的操作。
使用这种方式可以大大减少重复代码,大大提升代码的优雅性,方便我们使用。