Java 注解实战

基础概念

一:什么是注解

Java注解(Annotation)又称Java标注,是一种元数据机制,允许开发者在代码中添加描述性信息,这些信息不会直接影响程序逻辑,但可通过编译器或运行时反射机制进行处理。

二:注解定义

使用 @interface 关键字定义注解,成员变量以无参方法形式声明:

public @interface MyAnnotation {
    String value() default "default";
    int count() default 0;
}

注:单成员注解可省略属性名直接赋值。

三:Java元注解

元注解是指注解的注解,包括 @Retention、@Target、@Document、@Inherited、@Constraint 四种。

@Target
定义注解的使用位置,用来说明该注解可以被声明在那些元素之前。让一个注解的作用目标只能在指定目标上使用,这就叫作用目标限定。

Target类型说明:

  • ElementType.TYPE 接口、类、枚举、注解
  • ElementType.FIELD 字段、枚举的常量
  • ElementType.METHOD 方法
  • ElementType.PARAMETER 方法参数
  • ElementType.CONSTRUCTOR 构造函数
  • ElementType.LOCAL_VARIABLE 局部变量
  • ElementType.ANNOTATION_TYPE 注解
  • ElementType.PACKAGE 包

@Retention
定义注解的保留策略。

  • 源代码文件(RetentionPolicy.SOURCE):注解只在源代码中存在,编译时就被忽略。
  • 字节码文件(RetentionPolicy.CLASS):注解存在源代码中,编译时会把注解放到class文件中,但JVM加载类时,会忽略注解。
  • JVM中(RetentionPolicy.RUNTIME):注解存在源代码、字节码中,并且JVM加载类时,会把注解加载到JVM内存中(它是唯一可反射的注解!)。

@Document
说明该注解将被包含在 javadoc 中。

@Inherited
说明子类可以继承父类中的该注解。

@Constraint
通过使用validatedBy来指定与注解关联的验证器。

实战

应用场景一:自定义注解 + 拦截器 实现登录校验

首先定义一个LoginRequired注解:

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}

实现 Spring 的 HandlerInterceptor 类 LoginInterceptor拦截器:

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 排除非 Controller 请求
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;

        // 2. 获取注解信息(方法优先级高于类)
        LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);
        boolean needLogin = loginRequired != null;

        // 3. 校验登录状态
        if (needLogin && !isAuthenticated(request)) {
            handleUnauthorized(request, response);
            return false;
        }

        return true;
    }

    private boolean isAuthenticated(HttpServletRequest request) {
        String token = request.getHeader("token");
        return token != null;
    }

    private void handleUnauthorized(HttpServletRequest request,
                                    HttpServletResponse response) throws IOException {
        // 区分请求类型返回响应
        if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
            response.setContentType("application/json");
            response.getWriter().write("{\"code\":401,\"msg\":\"请先登录\"}");
        } else {
            response.sendRedirect("/login?redirect=" + URLEncoder.encode(request.getRequestURI(), "UTF-8"));
        }
    }
}

注册拦截器:

@Configuration
public class InterceptorConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor());
    }
}

编写Controller:

@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/order")
public class OrderController {
    /**
     * 查询订单列表
     * @param request
     * @return
     */
    @LoginRequired
    @PostMapping("/list")
    public Object list(@RequestBody QueryOrderRequest request) {
        // 查询..

        return "success";
    }
}

应用场景二:自定义注解 + AOP 实现方法耗时打印

先导入切面需要的依赖包

<dependency>
      <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义一个注解

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PrintMethodTime {
    // 耗时阈值(超过阈值才记录 单位:ms)
    long threshold() default 1000;

    // 是否记录慢日志
    boolean recordSlow() default true;
}

定义一个切面类

@Aspect
@Component
public class PrintMethodTimeAspect {
    // 使用ThreadLocal避免多线程干扰
    private static final ThreadLocal<Long> startTime = new ThreadLocal<>();

    @Pointcut("@annotation(com.tencent.qbilling.risk.server.controller.PrintMethodTime)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint jointPoint) throws Throwable {
        // 获取当前访问的class类及类名
        Class<?> clazz = jointPoint.getTarget().getClass();
        String clazzName = jointPoint.getTarget().getClass().getName();

        // 获取访问的方法名
        String methodName = jointPoint.getSignature().getName();

        // 获取方法所有参数及其类型
        Object[] args = jointPoint.getArgs();
        Class[] argClz = ((MethodSignature) jointPoint.getSignature()).getParameterTypes();

        // 获取访问的方法对象
        Method method = clazz.getDeclaredMethod(methodName, argClz);

        System.out.printf("[%s][%s] Request:[%s]%n", clazzName, methodName, JsonUtils.objectToJson(args));

        startTime.set(System.currentTimeMillis());

        // 执行目标方法
        Object result = jointPoint.proceed();

        long cost = System.currentTimeMillis() - startTime.get();

        // 判断当前访问的方法是否存在指定注解
        if (!method.isAnnotationPresent(PrintMethodTime.class)) {
            return result;
        }

        PrintMethodTime annotation = method.getAnnotation(PrintMethodTime.class);
        if (annotation.recordSlow() && cost > annotation.threshold()) {
            System.out.printf("[%s][%s] 慢方法警告 cost:%d ms Response:%s %n", clazzName, methodName, cost, JsonUtils.objectToJson(result));
        } else {
            System.out.printf("[%s][%s] cost:%d ms Response:%s %n", clazzName, methodName, cost, JsonUtils.objectToJson(result));
        }

        return result;
    }
}

编写Controller:

@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/order")
public class OrderController {
    /**
     * 查询订单列表
     * @param request
     * @return
     */
    @LoginRequired
    @PostMapping("/list")
    public Object list(@RequestBody QueryOrderRequest request) {
        // 查询..

        return "success";
    }

    @PrintMethodTime
    @PostMapping("/detail")
    public Object detail(@RequestBody QueryOrderRequest request) throws InterruptedException {
        // 查询..
        Thread.sleep(1000);
        return "success";
    }
}

应用场景三:自定义注解 + 验证器 实现入参校验

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StringParamCheckValidator.class)  // 关联验证器
public @interface StringParamCheck {
    String[] params();  // 允许的状态值数组

    String message() default "入参不合法";   // 默认错误消息

    Class<?>[] groups() default {}; // 校验分组

    Class<? extends Payload>[] payload() default {};    // 负载信息
}

验证器类

public class StringParamCheckValidator implements ConstraintValidator<StringParamCheck, String> {
    private List<String> validParams;

    @Override
    public void initialize(StringParamCheck constraintAnnotation) {
        this.validParams = Arrays.asList(constraintAnnotation.params());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (this.validParams.contains(value)) {
            return true;
        }

        return false;
    }
}

使用方式
定义一个实体类:

@Data
public class RegisterAccountRequest {
    private String name;

    private Integer age;

    @StringParamCheck(params = {"men", "women"})
    private String sex;
}

编写Controller:

@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/account")
public class AccountController {

    @PostMapping("/register")
    public Object register(@RequestBody @Validated RegisterAccountRequest request) {
        // 注册..

        return "success";
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容