基础概念
一:什么是注解
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";
}
}