基礎(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ù)載信息
}
@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";
}
}