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

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的包


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-aopartifactId>
dependency>

2.编写自定义注解

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

3.编写切面处理类

import com.alibaba.fastjson2.JSON;
import com.sky.base.annotation.LogOperation;
import com.sky.base.constant.DataStatus;
import com.sky.base.security.pojo.LoginUser;
import com.sky.model.SysLogOperation;
import com.sky.service.SysLogOperationService;
import com.sky.utils.IpUtils;
import com.sky.utils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Objects;

/**
 * 操作日志,切面处理类
 * @author 尹稳健~
 * @version 1.0
 * @time 2022/9/5
 */
@Aspect
@Component
@Slf4j
public class LogOperationAspect {

    @Autowired
    private SysLogOperationService sysLogOperationService;

    /** 切入点 */
    @Pointcut("@annotation(com.sky.base.annotation.LogOperation)")
    public void logPointCut(){}

    /** 环绕通知处理 */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable{
        long beginTime = System.currentTimeMillis();
        try {
            // 调用目标方法,不写point.proceed(),方法不会调用,将结果返回
            Object result = point.proceed();

            // 计算执行时长
            long time = System.currentTimeMillis() - beginTime;

            // 保存日志
            saveLog(point,time, DataStatus.NORMAL);

            return result;
        } catch (Exception e) {
            // 计算执行时长(毫秒)
            long time = System.currentTimeMillis() - beginTime;
            // 保存日志
            saveLog(point,time, DataStatus.REMOVE);
            throw e;
        }
    }

    // 保存日志到数据库
    public void saveLog(ProceedingJoinPoint joinPoint, long time, Short status) throws Exception {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 通过反射获取目标方法
        Method method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(),signature.getParameterTypes());
        // 获取方法上的注解
        LogOperation annotation = method.getAnnotation(LogOperation.class);
        // 获取操作日志对象
        Class<SysLogOperation> sysLogOperationClass = SysLogOperation.class;
        SysLogOperation sysLogOperation = sysLogOperationClass.newInstance();
        if (!Objects.isNull(annotation)){
            sysLogOperation.setOperation(annotation.value());
        }
        // 获取当前登录用户
        LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        
        sysLogOperation.setUsername(loginUser.getUser().getUsername());
        sysLogOperation.setRequestTime((int) time);
        sysLogOperation.setStatus(status);
        sysLogOperation.setCreateId(loginUser.getUser().getId());
        sysLogOperation.setUpdateId(loginUser.getUser().getId());
        sysLogOperation.setCreateTime(LocalDateTime.now());
        sysLogOperation.setUpdateTime(LocalDateTime.now());
        HttpServletRequest request = ServletUtils.getRequest();
        sysLogOperation.setIp(IpUtils.getIpAddr(request));
        sysLogOperation.setRequestUri(request.getRequestURI());
        sysLogOperation.setRequestMethod(request.getMethod());

        // 获取参数
        Object[] args = joinPoint.getArgs();
        String params = JSON.toJSONString(args[0]);
        sysLogOperation.setRequestParams(params);

        // 保存数据
        sysLogOperationService.save(sysLogOperation);
    }
}

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

import com.sky.base.annotation.LogOperation;
import com.sky.base.constant.Constants;
import com.sky.utils.R;
import com.sky.api.sys.param.request.LoginBody;
import com.sky.base.security.service.SysLoginService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 尹稳健~
 * @version 1.0
 * @time 2022/8/9
 */
@Api(tags = "系统管理-登录")
@RestController
public class SysLoginController {

    @Autowired
    private SysLoginService sysLoginService;

    @LogOperation("登录")
    @ApiOperation("登录接口")
    @PostMapping("/login")
    public R login(@RequestBody @Valid LoginBody loginBody){
        // Todo 校验验证码

        String token = sysLoginService.login(loginBody.getUsername(), loginBody.getPassword());
        Map<String, Object> map = new HashMap<>();
        map.put(Constants.TOKEN,token);
        return R.ok(map,"登录成功");
    }
}

总结

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

你可能感兴趣的:(SpringBoot,spring,boot,java)