Java注解(Annotation)无处不在------@Override、@Test、@Autowired、@RequestMapping......天天在用,但注解到底是怎么工作的?怎么自定义一个?
今天从基础到实战,把注解讲透。
注解的本质
注解本身就是一个接口,继承自java.lang.annotation.Annotation。它不直接影响代码逻辑,只是标记信息,由编译器或框架在编译期/运行时读取。
java
// @Override的定义(源码)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
四个元注解
定义注解时用到的注解叫"元注解":
java
@Target(ElementType.METHOD) // 1. 注解能加在哪里
@Retention(RetentionPolicy.RUNTIME) // 2. 注解的生命周期
@Documented // 3. 是否出现在Javadoc中
@Inherited // 4. 是否被子类继承
public @interface MyAnnotation {
String value() default "";
int count() default 0;
}
@Target:注解可以修饰的目标
| 值 | 含义 |
|---|---|
| TYPE | 类/接口/枚举 |
| FIELD | 字段 |
| METHOD | 方法 |
| PARAMETER | 方法参数 |
| CONSTRUCTOR | 构造器 |
| LOCAL_VARIABLE | 局部变量 |
@Retention:注解的生命周期
| 值 | 含义 | 能否反射读取 |
|---|---|---|
| SOURCE | 只在源码中 | 否 |
| CLASS | 源码和class文件中 | 否 |
| RUNTIME | 运行时保留 | 是(最常用) |
自定义注解实战
场景:自定义一个权限校验注解
java
// 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
String value(); // 权限标识
boolean required() default true; // 是否必须
}
// 使用注解
@RestController
public class AdminController {
@RequirePermission("user:delete")
@DeleteMapping("/user/{id}")
public Result deleteUser(@PathVariable Long id) {
userService.delete(id);
return Result.ok();
}
@RequirePermission(value = "report:view", required = false)
@GetMapping("/report")
public Result viewReport() {
return Result.ok(reportService.getReport());
}
}
用AOP处理注解逻辑:
java
@Aspect
@Component
public class PermissionAspect {
@Autowired
private HttpServletRequest request;
@Around("@annotation(annotation)")
public Object checkPermission(ProceedingJoinPoint point,
RequirePermission annotation) throws Throwable {
String permission = annotation.value();
// 获取当前用户
String token = request.getHeader("Authorization");
User currentUser = TokenUtil.parseUser(token);
// 检查权限
boolean hasPermission = permissionService.hasPermission(
currentUser.getId(), permission);
if (!hasPermission) {
if (annotation.required()) {
throw new BusinessException(403, "没有权限: " + permission);
}
return Result.ok(); // 非必须,无权限返回空
}
return point.proceed(); // 有权限,继续执行
}
}
通过反射读取注解
java
// 读取类上的注解
Class<?> clazz = UserController.class;
RequestMapping classMapping = clazz.getAnnotation(RequestMapping.class);
if (classMapping != null) {
System.out.println("路径前缀: " + classMapping.value()[0]);
}
// 读取方法上的注解
Method method = clazz.getMethod("getUser", Long.class);
GetMapping getMapping = method.getAnnotation(GetMapping.class);
if (getMapping != null) {
System.out.println("GET路径: " + getMapping.value()[0]);
}
// 读取参数上的注解
Parameter[] params = method.getParameters();
for (Parameter param : params) {
PathVariable pv = param.getAnnotation(PathVariable.class);
if (pv != null) {
System.out.println("路径参数: " + pv.value());
}
}
注解和配置的区别
| 对比 | 注解 | 配置文件 |
|---|---|---|
| 位置 | 代码上 | 外部文件 |
| 修改 | 需要重新编译 | 运行时可改 |
| 适合 | 代码级标记(权限、路由) | 环境相关配置(数据库、密钥) |
| 灵活性 | 低 | 高 |
实际项目中两者配合使用:注解标记代码逻辑,配置文件管理环境变量。
注解是Java元编程的基础。Spring之所以能做到"约定大于配置",背后大量依赖注解和反射。
理解了注解的原理,看框架源码时就不会一头雾水了。
你们项目中自定义过什么注解?