Java中自定义注解的使用

目录

1、为什么要使用自定义注解?

2、自定义注解

2.1、@interface定义注解接口

2.2、@Retention指定注解的保留策略

2.3、@Target指定注解的作用目标

2.4、注解处理

2.4.1、运行时注解处理

2.4.1.1 切面

2.4.1.2 拦截器

2.4.2、编译时注解处理

 2.5、使用自定义注解

3、总结


1、为什么要使用自定义注解?

以【记录操作日志】为例,一般在开发中为了记录用户操作,当系统出现问题时,可以追溯日志所提供详细的错误信息,帮助开发者快速定位问题的原因

最简单的方法:在每个方法中增加日志信息打印代码,来记录操作日志,但是这样重复代码也很多,也不易扩展,而且易出错

使用自定义注解就会方便很多,减少不必要的重复代码,减少非业务代码的侵入性,如果需要扩展操作用户的信息以及IP就会很方便,易于扩展和维护

2、自定义注解

2.1、@interface定义注解接口

public @interface LogOperation {
    String value() default "";
}

2.2、@Retention指定注解的保留策略

即注解在什么阶段可用,可选值有:

  • RetentionPolicy.SOURCE:注解仅在源码阶段保留,编译时丢弃。
  • RetentionPolicy.CLASS:注解在编译时保留,但在运行时丢弃(默认值)。
  • RetentionPolicy.RUNTIME:注解在运行时保留,可以通过反射获取
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation {
    String value() default "";
}

2.3、@Target指定注解的作用目标

最常用的元注解是@Target,使用@Target可以定义Annotation能够被应用于源码的哪些位置:

  • 类或接口:ElementType.TYPE

  • 字段:ElementType.FIELD

  • 方法:ElementType.METHOD

  • 构造方法:ElementType.CONSTRUCTOR

  • 方法参数:ElementType.PARAMETER

:在操作日志自定义注解@LogOperation可用在方法上,必须添加一个@Target(ElementType.METHOD)

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogOperation {
    String value() default "";
}

2.4、注解处理

运行时处理(常用)

  • 切面(AOP):通过@Aspect注解和Spring AOP的代理机制,利用反射机制在方法调用前后插入额外的逻辑
  • 拦截器:通过实现HandlerInterceptorMethodInterceptor接口,利用反射机制在方法调用前后执行额外逻辑

编译时处理

  • 注解处理器:通过实现 javax.annotation.processing.AbstractProcessor接口创建自定义的注解处理器,在编译阶段处理注解,可以生成额外的源代码或字节码文件

2.4.1、运行时注解处理

常见场景:日志记录、权限控制、缓存管理、事务管理、自定义逻辑注入等等

首先定义SysLogOperation,记录操作日志内容,其中包方法信息、操作用户信息、网络信息其他等等

@Data
public class SysLogOperation  {
    private Long id;
    private String module;
    private String operation;
    private String requestUri;
    private String requestMethod;
    private String requestParams;
    private Integer requestTime;
    private String userAgent;
    private String ip;
    private Integer status;
    private String creatorName;
    private Long creator;
    private Date createDate;
}
2.4.1.1 切面

定义记录日志信息的切面类LogOperationAspect


@Slf4j
@Aspect     // 声明这个类是一个 切面类
@Component。// 把类注册到 Spring 中
public class LogOperationAspect {
    @Autowired
    private ModuleConfig moduleConfig;
    @Autowired
    private LogProducer logProducer;

    public LogOperationAspect() {
    }

    // 将一个注解定义成 切点
    @Pointcut("@annotation(com.echola.log.annotation.LogOperation)")
    public void logPointCut() {
    }

    // 环绕通知,在切点方法执行前后进行拦截,保存日志
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();

        long time;
        try {
            Object result = point.proceed();
            time = System.currentTimeMillis() - beginTime;
            this.saveLog(point, time, OperationStatusEnum.SUCCESS.value());
            log.info(" message is : {}", message);
            return result;
        } catch (Exception var7) {
            time = System.currentTimeMillis() - beginTime;
            this.saveLog(point, time, OperationStatusEnum.FAIL.value());
            throw var7;
        }
    }

    private void saveLog(ProceedingJoinPoint joinPoint, long time, Integer status) {
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        SysLogOperation log = new SysLogOperation();
        LogOperation annotation = (LogOperation)method.getAnnotation(LogOperation.class);
        if (annotation != null) {
            log.setOperation(annotation.value());
        }

        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        UserDetail user = SecurityUser.getUser();
        if (user != null) {
            log.setCreator(user.getId());
            log.setCreatorName(user.getUsername());
        } else {
            String userId = request.getHeader("userId");
            if (userId != null) {
                log.setCreatorName(userId);
            }
        }
 
        log.setId(IdWorker.getId());
        log.setModule(this.moduleConfig.getName());
        log.setStatus(status);
        log.setRequestTime((int)time);
        log.setCreateDate(new Date());
        log.setIp(IpUtils.getIpAddr(request));
        log.setUserAgent(request.getHeader("User-Agent"));
        log.setRequestUri(request.getRequestURI());
        log.setRequestMethod(request.getMethod());
        Object[] args = joinPoint.getArgs();

        try {
            String params = JSON.toJSONString(args[0]);
            log.setRequestParams(params);
        } catch (Exception var13) {
        }
        
        //保存日志
        //此处推送日志到消息中间件,可以使用控制台打印、Redis存储日志其他方式保存日志
        this.logProducer.saveLog(log);
    }
}
2.4.1.2 拦截器

定义记录日志信息的拦截器类LogOperationInterceptor

@Component
public class LogOperationInterceptor implements HandlerInterceptor {

    @Autowired
    private ModuleConfig moduleConfig;
    @Autowired
    private LogProducer logProducer;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取当前请求的方法// 获取当前请求的方法
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        // 检查方法上是否有@LogOperation注解
        if (method.isAnnotationPresent(LogOperation.class)) {
            LogOperation logOperation = method.getAnnotation(LogOperation.class);
            String operation = logOperation.value();
            String param = JSON.toJSONString(request.getParameterMap());
            //获取请求参数
            Object[] args = request.getParameterMap().values().toArray();
            //方法耗时
            long time;
            long beginTime = System.currentTimeMillis();
            try {
                time = System.currentTimeMillis() - beginTime;
                this.saveLog(operation, param, time, OperationStatusEnum.SUCCESS.value());
                return true;
            } catch (Exception var7) {
                time = System.currentTimeMillis() - beginTime;
                this.saveLog(operation, param, time, OperationStatusEnum.FAIL.value());
                throw var7;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 处理完成后调用
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 请求完成后的回调
    }

    private void saveLog(String operation, String param, long time, Integer status) {
        SysLogOperation log = new SysLogOperation();
        log.setOperation(operation);
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        UserDetail user = SecurityUser.getUser();
        if (user != null) {
            log.setCreator(user.getId());
            log.setCreatorName(user.getUsername());
        } else {
            String userId = request.getHeader("userId");
            if (userId != null) {
                log.setCreatorName(userId);
            }
        }
        log.setId(IdWorker.getId());
        log.setModule(this.moduleConfig.getName());
        log.setRequestParams(param);
        log.setStatus(status);
        log.setRequestTime((int) time);
        log.setCreateDate(new Date());
        log.setIp(IpUtils.getIpAddr(request));
        log.setUserAgent(request.getHeader("User-Agent"));
        log.setRequestUri(request.getRequestURI());
        log.setRequestMethod(request.getMethod());
        
        //保存日志
        //此处推送日志到消息中间件,可以使用控制台打印、Redis存储日志其他方式保存日志
        this.logProducer.saveLog(log);
    }
}

配置拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoggingInterceptor loggingInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loggingInterceptor);
    }
}

2.4.2、编译时注解处理

编译时处理是指在编译阶段通过注解处理器(Annotation Processor)来处理注解。这种方式的优点是性能高,因为处理过程在编译阶段完成,不会影响运行时性能

常见场景:代码生成、代码检查、性能优化等等

例如:处理一个@CustoAnnotation的注解

@SupportedAnnotationTypes("CustomAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CustomAnnotationProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(MyCustomAnnotation.class)) {
            MyCustomAnnotation annotation = element.getAnnotation(MyCustomAnnotation.class);
            System.out.println("Annotation value: " + annotation.value());
            // 在这里可以生成新的源代码文件或其他处理逻辑
        }
        return true;
    }
}

 2.5、使用自定义注解

在需要使用自定义注解的地方使用该注解

public class TestClass {
    @LogOperation("新增")
    public void save() {
        // 方法实现
    }
}

3、总结

自定义注解被广泛应用于日常开发中,使用 @interface 关键字来定义自定义注解,之后再使用 AOP切面拦截器的方式实现自定义注解,可以实现日志记录、性能监控、权限判断和幂等性判断等功能,从而提高代码的复用性和可维护性

你可能感兴趣的:(Java,java,开发语言)