自定义注解结合Aop

尘微 2023-02-18 11:23:22 10478 0 0 0

应用场景有哪些?

(1)权限验证
(2)日志记录

接下来我展示一下日志记录怎么实现

自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @author 通窍
 * @interface用来声明一个注解
 * 其中的每一个方法实际上是声明了一个配置参数。
 * 方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。
 * 可以通过default来声明参数的默认值。
 * 不能定义 public @interface A extends B 这样的形式,必须错的,只能是 public @interface A 形式
 *
 * @Target表明是要在哪一种方法或者类上面添加注解,具体的类型有(可以选择多个,逗号隔开)
 * @Retention表明当前声明的自定义注解是在什么时期进行生效
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    
//    String value() default "自定义注解拦截";

    // 方法名称
    String methodName() default "";

    // 当前操作人
    String currentUser() default "";

    // 操作
    String operate() default "";
}

定义切面类

其中的重点是对doHandlerLog中的操作
要是有其他的业务需求,写法跟doHandlerLog一样,写个方法在其中增加业务就行.

import com.alibaba.fastjson.JSON;
import com.jy.utils.AspectUtil;
import com.jy.vo.MyOperationLogVo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
/**
 * @author 通窍
 * @Version 1.0
 * @description: SpringBoot 中使用 Aop 功能
 * @date 2023/2/18 7:57
 */
@Slf4j
@Aspect
@Component  /** 把这个类交给Spring管理 (不清除它是那个层的就给component)*/
public class AopAspectTest {

    @Autowired
    private AspectUtil aspectUtil;
    /**
     * 以下是两种方法
     *          1.直接在通知上加入切点 --> execution(public * com.jy.controller.TbUserController.*(..))
     *          2.把切入点抽出 (execution(切入点表达式)重复,提取出来 写切点Pointcut) -->value = "doPointcut()",
     * */

    @Pointcut("execution(public * com.jy.controller.TbUserController.*(..))")
    public void doPointcut(){

    }

    /**
     * joinpoint.getargs():获取带参方法的参数
     *
     * 注:就是获取组件中test方法中的参数,如果test方法中有多个参数,那么这个方法机会返回多个参数.想要哪个就通过for循环加上判断来筛选
     *
     * 2.joinpoint.getTarget():获取他们的目标对象信息
     *
     * 3..joinpoint.getSignature():(signature是信号,标识的意思):获取被增强的方法相关信息.其后续方法有两个
     *
     * getDeclaringTypeName:返回方法所在的包名和类名
     *
     * getname():返回方法名
     */
    @Before("execution(public * com.jy.controller.TbUserController.*(..))")
    public void doBefore(JoinPoint point){
        String methodName = point.getSignature().getName();
        List<Object> args = Arrays.asList(point.getArgs());
        log.info("调用前连接点方法为:" + methodName + ",参数为:" + JSON.toJSONString(args));
        doHandlerLog(point);
    }

    @AfterReturning(value = "doPointcut()", returning = "returnValue")
    public void doAfterReturning(JoinPoint point, Object returnValue){
        String methodName = point.getSignature().getName();
        List<Object> args = Arrays.asList(point.getArgs());
        log.info("调用后连接点方法为:" + methodName + ",参数为:" + JSON.toJSONString(args) + ",返回值为:" + JSON.toJSONString(returnValue));
    }

    private void doHandlerLog(JoinPoint joinPoint) {
        // 获取自定义注解@MyAnnotation
        MyAnnotation myOperationLogByJoinPoint = aspectUtil.getMyOperationLogByJoinPoint(joinPoint);
        if (null == myOperationLogByJoinPoint) {
            return;
        }
        // 获取签名
        String signature = joinPoint.getSignature().toString();
        System.err.println("签名:"+signature);
        // 获取方法名
        String methodName = signature.substring(signature.lastIndexOf(".") + 1, signature.indexOf("("));
        System.err.println("方法名:"+methodName);
        // 获取方法的execution()
        String longTemp = joinPoint.getStaticPart().toLongString();
        System.err.println("切点表达式:"+longTemp);
        //通过反射获取目标对象的类名  getTarget获取他们的目标对象信息
        String classType = joinPoint.getTarget().getClass().getName();
        System.err.println("目标对象的类名:"+classType);
        try {
            //通过反射获取到类中的所有方法
            Class<?> clazz = Class.forName(classType);
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                //isAnnotationPresent用于检查实现此接口的类中是否存在指定类型的注解
                //如果实现此接口的类有该类型的注解而且他的方法和获取到的方法名一样时
                if (method.isAnnotationPresent(MyAnnotation.class) && method.getName().equals(methodName)) {
                    // 解析
                    MyOperationLogVo myOperationLogVos = parseAnnotation(method);
                    // 日志添加
                   //logService.addLog(myOperationLogVos);
                    System.err.println(myOperationLogVos);
                }
            }
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
    }
    /**
     * 解析方法上注解中的值
     */
    public MyOperationLogVo parseAnnotation(Method method) {
        //获取注解对象
        MyAnnotation myOperationLog = method.getAnnotation(MyAnnotation.class);
        if (null == myOperationLog) {
            return null;
        }
        //加入vo类型中
        MyOperationLogVo myOperationLogVo = new MyOperationLogVo();
        myOperationLogVo.setMethodName(myOperationLog.methodName());
        myOperationLogVo.setCurrentUser(myOperationLog.currentUser());
        myOperationLogVo.setOperate(myOperationLog.operate());
        return myOperationLogVo;
    }
}

AspectUtil工具

import com.jy.config.MyAnnotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
 * @Author 通窍
 * @Version 1.0
 * @description: TODO
 * @date 2023/2/18 9:25
 */
@Component
public class AspectUtil {

    /**
     *
     * 功能描述:通过JoinPoint获取注解MyOperationLog
     *
     * 访问目标方法参数,有三种方法(实际有四种):
     * 1.joinpoint.getargs():获取带参方法的参数
     * 2.joinpoint.getTarget():.获取他们的目标对象信息
     * 3.joinpoint.getSignature():(signature是信号,标识的意思):获取被增强的方法相关信息
     * MethodSignature(方法签名)
     *
     */
    public MyAnnotation getMyOperationLogByJoinPoint(JoinPoint joinPoint) {
        /**
         * 从连接点获取signature,并将signature强转为MethodSignature,
         * 从而从MethodSignature对象可以获取拦截的方法对象以及方法参数注解
         */
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (null == method) {
            return null;
        }
        return method.getAnnotation(MyAnnotation.class);
    }
}

MyOperationLogVo类


import lombok.Data; /** * @Author 通窍 * @Version 1.0 * @description: TODO * @date 2023/2/18 9:39 */ @Data public class MyOperationLogVo { // 方法名称 private String methodName; // 当前操作人 private String currentUser; // 操作 private String operate; }

controller层

import com.jy.config.MyAnnotation;
import com.jy.service.TbUserService;
import com.jy.vo.ResultResponse;
import com.jy.vo.UserVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

/**
 * @Author 姜毅
 * @Version 1.0
 * @description: TODO
 * @date 2023/2/15 9:16
 */
@Api(tags = "User-Api")
@RestController
@RequestMapping("/user")
public class TbUserController {
    @Resource
    TbUserService tbUserService;

    @ApiOperation("登录")
    @PostMapping("/login")
    @MyAnnotation(methodName = "login",currentUser = "小慧",operate ="login:登录" )
    public ResultResponse login(@RequestBody UserVo userVo){
        return tbUserService.login(userVo);
    }
}

上效果

效果我只是输出了参数.大家可以根据切面类中的方法参考一下.
图片alt