Java 注解實戰(zhàn)

基礎(chǔ)概念

一:什么是注解

Java注解(Annotation)又稱Java標(biāo)注,是一種元數(shù)據(jù)機(jī)制,允許開發(fā)者在代碼中添加描述性信息,這些信息不會直接影響程序邏輯,但可通過編譯器或運行時反射機(jī)制進(jìn)行處理。

二:注解定義

使用 @interface 關(guān)鍵字定義注解,成員變量以無參方法形式聲明:

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

注:單成員注解可省略屬性名直接賦值。

三:Java元注解

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

@Target
定義注解的使用位置,用來說明該注解可以被聲明在那些元素之前。讓一個注解的作用目標(biāo)只能在指定目標(biāo)上使用,這就叫作用目標(biāo)限定。

Target類型說明:

  • ElementType.TYPE 接口、類、枚舉、注解
  • ElementType.FIELD 字段、枚舉的常量
  • ElementType.METHOD 方法
  • ElementType.PARAMETER 方法參數(shù)
  • ElementType.CONSTRUCTOR 構(gòu)造函數(shù)
  • ElementType.LOCAL_VARIABLE 局部變量
  • ElementType.ANNOTATION_TYPE 注解
  • ElementType.PACKAGE 包

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

  • 源代碼文件(RetentionPolicy.SOURCE):注解只在源代碼中存在,編譯時就被忽略。
  • 字節(jié)碼文件(RetentionPolicy.CLASS):注解存在源代碼中,編譯時會把注解放到class文件中,但JVM加載類時,會忽略注解。
  • JVM中(RetentionPolicy.RUNTIME):注解存在源代碼、字節(jié)碼中,并且JVM加載類時,會把注解加載到JVM內(nèi)存中(它是唯一可反射的注解?。?。

@Document
說明該注解將被包含在 javadoc 中。

@Inherited
說明子類可以繼承父類中的該注解。

@Constraint
通過使用validatedBy來指定與注解關(guān)聯(lián)的驗證器。

實戰(zhàn)

應(yīng)用場景一:自定義注解 + 攔截器 實現(xiàn)登錄校驗

首先定義一個LoginRequired注解:

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

實現(xiàn) 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. 獲取注解信息(方法優(yōu)先級高于類)
        LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);
        boolean needLogin = loginRequired != null;

        // 3. 校驗登錄狀態(tài)
        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 {
        // 區(qū)分請求類型返回響應(yīng)
        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";
    }
}

應(yīng)用場景二:自定義注解 + AOP 實現(xiàn)方法耗時打印

先導(dǎo)入切面需要的依賴包

<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 {
        // 獲取當(dāng)前訪問的class類及類名
        Class<?> clazz = jointPoint.getTarget().getClass();
        String clazzName = jointPoint.getTarget().getClass().getName();

        // 獲取訪問的方法名
        String methodName = jointPoint.getSignature().getName();

        // 獲取方法所有參數(shù)及其類型
        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());

        // 執(zhí)行目標(biāo)方法
        Object result = jointPoint.proceed();

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

        // 判斷當(dāng)前訪問的方法是否存在指定注解
        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";
    }
}

應(yīng)用場景三:自定義注解 + 驗證器 實現(xiàn)入?yún)⑿r?/h4>
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StringParamCheckValidator.class)  // 關(guān)聯(lián)驗證器
public @interface StringParamCheck {
    String[] params();  // 允許的狀態(tài)值數(shù)組

    String message() default "入?yún)⒉缓戏?;   // 默認(rèn)錯誤消息

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

    Class<? extends Payload>[] payload() default {};    // 負(fù)載信息
}

驗證器類

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";
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容