SpringBoot 自定义注解 + SpringBoot Aop 实现切面日志处理

今天药忘吃喽~ 2024-04-07 13:20 253阅读 0赞

SpringBoot 自定义注解 + SpringBoot Aop 实现切面日志处理

    • 思考为什么需要自定义注解和AOP
    • 简介
      • 自定义注解
        • Documented:
        • Inherited :
        • Target:
        • Retention:
      • SpringAOP简介
    • 自定义注解定义规则
    • 实战
      • 1.先导入springboot aop的包
      • 2.编写自定义注解
      • 3.编写切面处理类
      • 4.将自定义注解写在连接点
    • 总结

思考为什么需要自定义注解和AOP

项目中常常要打印日志,尤其是在做接口开发中,因为要面临着对前台数据的检查。

这里我们使用的是自定义注解+AOP的方式实现日志

简介

自定义注解

注解是一种能被添加到java代码中的元数据(python中的函数装饰器),类、方法、参数、变量和包都可以用注解来修饰。用来定义一个类、属性或者一些方法,以便程序能被捕译处理。相当于一个说明文件,告诉应用程序某个被注解的类或者属性是什么,要怎么处理。对被修饰的代码本身没有直接影响。

Documented:

注解表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中

Inherited :

它指明被注解的类会自动继承. 更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类, 父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中

Target:
  • @Target(ElementType.TYPE) //接口、类、枚举、注解
  • @Target(ElementType.FIELD) //字段、枚举的常量
  • @Target(ElementType.METHOD) //方法 (一般都使用这个)
  • @Target(ElementType.PARAMETER) //方法参数
  • @Target(ElementType.CONSTRUCTOR) //构造函数
  • @Target(ElementType.LOCAL_VARIABLE)//局部变量
  • @Target(ElementType.ANNOTATION_TYPE)//注解
  • @Target(ElementType.PACKAGE) ///包
Retention:
  • RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略。
  • RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略。
  • RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。

SpringAOP简介

  • 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
  • 连接点(Join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
  • 切点(PointCut): 可以插入增强处理的连接点。
  • 切面(Aspect): 切面是通知和切点的结合。
  • 引入(Introduction):允许我们向现有的类添加新的方法或者属性。
  • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的代理对象。

这里AOP不做过多介绍,没有基础的请先学习AOP知识

自定义注解定义规则

  1. Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
  2. 参数成员只能用public或默认(default)这两个访问权修饰
  3. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
  4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
  5. 注解也可以没有定义成员, 不过这样注解就没啥用了

实战

1.先导入springboot aop的包

  1. <!-- aop和aspect -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-aop</artifactId>
  5. </dependency>

2.编写自定义注解

  1. import java.lang.annotation.*;
  2. /**
  3. * 操作日志注解
  4. * @author 尹稳健~
  5. * @version 1.0
  6. * @time 2022/9/5
  7. */
  8. @Documented
  9. @Retention(RetentionPolicy.RUNTIME)
  10. //注解,目标在方法上
  11. @Target(ElementType.METHOD)
  12. public @interface LogOperation {
  13. /** 直接使用value那么使用注解的时候可以不用写value= */
  14. String value() default "";
  15. }

3.编写切面处理类

  1. import com.alibaba.fastjson2.JSON;
  2. import com.sky.base.annotation.LogOperation;
  3. import com.sky.base.constant.DataStatus;
  4. import com.sky.base.security.pojo.LoginUser;
  5. import com.sky.model.SysLogOperation;
  6. import com.sky.service.SysLogOperationService;
  7. import com.sky.utils.IpUtils;
  8. import com.sky.utils.ServletUtils;
  9. import lombok.extern.slf4j.Slf4j;
  10. import org.aspectj.lang.ProceedingJoinPoint;
  11. import org.aspectj.lang.annotation.*;
  12. import org.aspectj.lang.reflect.MethodSignature;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.security.core.context.SecurityContextHolder;
  15. import org.springframework.stereotype.Component;
  16. import javax.servlet.http.HttpServletRequest;
  17. import java.lang.reflect.Method;
  18. import java.time.LocalDateTime;
  19. import java.util.Objects;
  20. /**
  21. * 操作日志,切面处理类
  22. * @author 尹稳健~
  23. * @version 1.0
  24. * @time 2022/9/5
  25. */
  26. @Aspect
  27. @Component
  28. @Slf4j
  29. public class LogOperationAspect {
  30. @Autowired
  31. private SysLogOperationService sysLogOperationService;
  32. /** 切入点 */
  33. @Pointcut("@annotation(com.sky.base.annotation.LogOperation)")
  34. public void logPointCut(){
  35. }
  36. /** 环绕通知处理 */
  37. @Around("logPointCut()")
  38. public Object around(ProceedingJoinPoint point) throws Throwable{
  39. long beginTime = System.currentTimeMillis();
  40. try {
  41. // 调用目标方法,不写point.proceed(),方法不会调用,将结果返回
  42. Object result = point.proceed();
  43. // 计算执行时长
  44. long time = System.currentTimeMillis() - beginTime;
  45. // 保存日志
  46. saveLog(point,time, DataStatus.NORMAL);
  47. return result;
  48. } catch (Exception e) {
  49. // 计算执行时长(毫秒)
  50. long time = System.currentTimeMillis() - beginTime;
  51. // 保存日志
  52. saveLog(point,time, DataStatus.REMOVE);
  53. throw e;
  54. }
  55. }
  56. // 保存日志到数据库
  57. public void saveLog(ProceedingJoinPoint joinPoint, long time, Short status) throws Exception {
  58. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  59. // 通过反射获取目标方法
  60. Method method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(),signature.getParameterTypes());
  61. // 获取方法上的注解
  62. LogOperation annotation = method.getAnnotation(LogOperation.class);
  63. // 获取操作日志对象
  64. Class<SysLogOperation> sysLogOperationClass = SysLogOperation.class;
  65. SysLogOperation sysLogOperation = sysLogOperationClass.newInstance();
  66. if (!Objects.isNull(annotation)){
  67. sysLogOperation.setOperation(annotation.value());
  68. }
  69. // 获取当前登录用户
  70. LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  71. sysLogOperation.setUsername(loginUser.getUser().getUsername());
  72. sysLogOperation.setRequestTime((int) time);
  73. sysLogOperation.setStatus(status);
  74. sysLogOperation.setCreateId(loginUser.getUser().getId());
  75. sysLogOperation.setUpdateId(loginUser.getUser().getId());
  76. sysLogOperation.setCreateTime(LocalDateTime.now());
  77. sysLogOperation.setUpdateTime(LocalDateTime.now());
  78. HttpServletRequest request = ServletUtils.getRequest();
  79. sysLogOperation.setIp(IpUtils.getIpAddr(request));
  80. sysLogOperation.setRequestUri(request.getRequestURI());
  81. sysLogOperation.setRequestMethod(request.getMethod());
  82. // 获取参数
  83. Object[] args = joinPoint.getArgs();
  84. String params = JSON.toJSONString(args[0]);
  85. sysLogOperation.setRequestParams(params);
  86. // 保存数据
  87. sysLogOperationService.save(sysLogOperation);
  88. }
  89. }

4.将自定义注解写在连接点

  1. import com.sky.base.annotation.LogOperation;
  2. import com.sky.base.constant.Constants;
  3. import com.sky.utils.R;
  4. import com.sky.api.sys.param.request.LoginBody;
  5. import com.sky.base.security.service.SysLoginService;
  6. import io.swagger.annotations.Api;
  7. import io.swagger.annotations.ApiOperation;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.web.bind.annotation.PostMapping;
  10. import org.springframework.web.bind.annotation.RequestBody;
  11. import org.springframework.web.bind.annotation.RestController;
  12. import javax.validation.Valid;
  13. import java.util.HashMap;
  14. import java.util.Map;
  15. /**
  16. * @author 尹稳健~
  17. * @version 1.0
  18. * @time 2022/8/9
  19. */
  20. @Api(tags = "系统管理-登录")
  21. @RestController
  22. public class SysLoginController {
  23. @Autowired
  24. private SysLoginService sysLoginService;
  25. @LogOperation("登录")
  26. @ApiOperation("登录接口")
  27. @PostMapping("/login")
  28. public R login(@RequestBody @Valid LoginBody loginBody){
  29. // Todo 校验验证码
  30. String token = sysLoginService.login(loginBody.getUsername(), loginBody.getPassword());
  31. Map<String, Object> map = new HashMap<>();
  32. map.put(Constants.TOKEN,token);
  33. return R.ok(map,"登录成功");
  34. }
  35. }

总结

该博客是为了记录博主项目中遇到的日志实现,所以记录一下,大家可以看看自定义注解和切面类的写法,可能代码中存在一定问题,希望大家可以找出!

发表评论

表情:
评论列表 (有 0 条评论,253人围观)

还没有评论,来说两句吧...

相关阅读