python

springboot系列——自定义注解

2020-10-12  本文已影响0人  Caooz

项目需要实现对登录用户的实时监控,对用户部分相关操作进行埋点统计。
这里我使用了自定义注解,对需要监控的接口使用注解进行切点切入,然后处理相关逻辑即可。
自定义注解类:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperLog {
    /**
     *  介绍
     */
    String title();  
    /**
     * 日志类型
      */
    //String operation(); 
}

@Target——定义注解使用范围
Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)
在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
1.TYPE——用于描述类、接口(包括注解类型) 或enum声明
2.FIELD——用于字段声明(包括枚举常量)
3.METHOD——用于方法声明
4.PARAMETER——用于参数声明
5.CONSTRUCTOR——用于构造函数声明
6.LOCAL_VARIABLE——用于本地变量声明
7.ANNOTATION_TYPE——用于注解类型声明
8.PACKAGE——用于包声明
9.TYPE_PARAMETER—— 用于类型参数声明,JavaSE8引进,可以应用于类的泛型声明之处
10.TYPE_USE——JavaSE8引进,此类型包括类型声明和类型参数声明,是为了方便设计者进行类型检查,例如,如果使用@Target(ElementType.TYPE_USE)对@NonNull进行标记,则类型检查器可以将@NonNull class C {...} C类的所有变量都视为非null
ps. 如果一个注解没有指定@Target注解,则此注解可以用于除了TYPE_PARAMETER和TYPE_USE以外的任何地方。
ElementType类型字典:

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

@Retention——定义注解保留阶段
1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
2.RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
3.RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用
RetentionPolicy类型字典:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

示例中 @Retention(RetentionPolicy.RUNTIME)注解表明 Test_Retention注解将会由虚拟机保留,以便它可以在运行时通过反射读取。

@Documented
注解标记的元素,Javadoc工具会将此注解标记元素的注解信息包含在javadoc中。默认,注解信息不会包含在Javadoc中。

自定义注解使用,本文栗子是举的接口调用日志,需要记录接口的入参和返回值,这里使用到spring的AOP

@Aspect
@Component
@Slf4j
public class SysLogAspect {

    /**
     * 定义切点 @Pointcut
     * 在注解的位置切入代码
     */
    @Pointcut("@annotation( xxx.xxx.xxx.xxx.xxx.OperLog)")
    public void logPointCut() {
    }

   /**
     * 环绕通知,获取接口调用前后入参及返回值数据
     * @param proceedingJoinPoint
     * @return
     */
    @Around("logPointCut()")
    public Object saveSysLog(ProceedingJoinPoint proceedingJoinPoint) {
        try {
            //保存日志
            SysOperLog operLog = new SysOperLog();
            //从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            //获取切入点所在的方法
            Method method = signature.getMethod();
            //获取操作
            OperLog myLog = method.getAnnotation(OperLog.class);
            if (myLog != null) {
                operLog.setTitle(myLog.title());//保存获取的操作
                operLog.setBusinessType(6);
            }
            //获取请求的类名
            String className = proceedingJoinPoint.getTarget().getClass().getName();
            //获取请求的方法名
            String methodName = method.getName();
            operLog.setMethod(className + "." + methodName+"()");

            //请求的参数
            Object[] args = proceedingJoinPoint.getArgs();
            //将参数所在的数组转换成json
            String params = JSON.toJSONString(args);
            JSONArray jsonArray = JSON.parseArray(params);
            operLog.setOperParam(params);
            UserInfo user = JSON.toJavaObject(jsonArray.getJSONObject(0),UserInfo.class);
            operLog.setOperName(user != null ? user.getUserName() != null ? user.getUserName() : user.getMobile() : "");
            operLog.setOperTime(new Date());
            //获取用户名
            //获取用户ip地址
            // 接收到请求,记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            // 记录下请求内容
            operLog.setOperIp(IpUtils.getIpAddr(request));
            operLog.setOperUrl(request.getRequestURI());
            //调用service保存SysLog实体类到数据库
            // 异步保存日志数据到数据库
            AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
            Object result = proceedingJoinPoint.proceed();
            return result;
        } catch (Throwable e) {
            log.error(e.getMessage());
        }
        return Result.failed( "系统异常");
    }
}

@Pointcut("@annotation( xxx.xxx.xxx.xxx.xxx.OperLog)"),使用springaop,对接口使用了@OperLog进行切点切入,记录接口的调用与返回数据。

@OperLog(title = "账号密码登陆")
@PostMapping(value = "/login")
@ApiOperation(value = "账号密码登陆", notes = "登陆成功返回token")
public Result<String> login(@RequestBody @Valid UsernameAndPasswordReq usernameAndPasswordReq) {
  final Result<String> accessToken = authService.login(usernameAndPasswordReq);
  return accessToken;
}

使用注解@OperLog(title='xxx'),即可完成对接口的调用切点切入。

ps,本文只是对自定义注解使用的大致演示,只提供了部分核心代码。如果有其他疑问,可留言,作者会耐心解答

上一篇下一篇

猜你喜欢

热点阅读