Java注解与元注解的实现原理

Java注解与元注解的实现原理

注解(Annotation)是 Java 5 引入的元数据机制,广泛应用于框架开发、代码生成和编译期检查。本文从基础用法到实现原理进行全面讲解。

注解的基本定义

// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
String value() default "";
boolean printParams() default true;
}

四大元注解

1. @Target:使用位置

public enum ElementType {
TYPE, // 类、接口、枚举
FIELD, // 字段
METHOD, // 方法
PARAMETER, // 参数
CONSTRUCTOR, // 构造器
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE,// 注解
PACKAGE, // 包
TYPE_PARAMETER, // 泛型参数(JDK 8+)
TYPE_USE // 类型使用(JDK 8+)
}

2. @Retention:保留策略

策略 说明 典型应用
SOURCE 编译期丢弃 @Override, @SuppressWarnings
CLASS 保留到字节码,运行期丢弃 默认策略,少见
RUNTIME 运行期保留,可通过反射读取 @Autowired, @RequestMapping

3. @Documented

包含在 Javadoc 中:

@Documented
public @interface ApiDoc {
String description();
}

4. @Inherited

子类自动继承父类注解:

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParentAnnotation { }

@ParentAnnotation
public class Parent { }

public class Child extends Parent {
// Child也拥有@ParentAnnotation
}

注解的底层实现

编译后的注解

注解本质是一个继承 java.lang.annotation.Annotation 的接口:

// 编译后
public interface LogExecution extends Annotation {
String value();
boolean printParams();
}

JDK 动态代理生成

当通过反射获取注解时,JDK 会生成动态代理:

Method method = clazz.getDeclaredMethod("doSomething");
LogExecution annotation = method.getAnnotation(LogExecution.class);

// 实际返回的是代理对象
System.out.println(annotation.getClass()); // class com.sun.proxy.$Proxy1

代理类将方法调用转发给 AnnotationInvocationHandler

class AnnotationInvocationHandler implements InvocationHandler {
private final Map<String, Object> memberValues;

public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Object value = memberValues.get(member);
// 返回注解属性值
return value;
}
}

反射读取注解

类级别注解

// 检查是否存在
boolean hasAnnotation = clazz.isAnnotationPresent(Service.class);

// 获取注解
Service service = clazz.getAnnotation(Service.class);
String name = service.value();

方法级别注解

for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Transactional.class)) {
Transactional tx = method.getAnnotation(Transactional.class);
System.out.println("超时时间: " + tx.timeout());
}
}

重复注解(JDK 8+)

// 定义容器注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
Role[] value();
}

// 定义可重复注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Roles.class)
public @interface Role {
String value();
}

// 使用
@Role("ADMIN")
@Role("USER")
public void doSomething() { }

实际应用场景

1. 日志切面

@Aspect
@Component
public class LogAspect {

@Around("@annotation(logExecution)")
public Object around(ProceedingJoinPoint pjp, LogExecution logExecution) throws Throwable {
long start = System.currentTimeMillis();
Object result = pjp.proceed();
long cost = System.currentTimeMillis() - start;

System.out.println("方法 " + pjp.getSignature().getName() +
" 执行耗时: " + cost + "ms");
return result;
}
}

2. 参数校验

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
String message() default "字段不能为空";
}

// 校验器
public class Validator {
public static void validate(Object obj) throws IllegalAccessException {
for (Field field : obj.getClass().getDeclaredFields()) {
field.setAccessible(true);
if (field.isAnnotationPresent(NotNull.class) && field.get(obj) == null) {
NotNull notNull = field.getAnnotation(NotNull.class);
throw new ValidationException(notNull.message());
}
}
}
}

3. ORM 映射

@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "user_name", length = 50)
private String username;
}

注解处理器(APT)

编译期处理注解,生成代码:

@SupportedAnnotationTypes("com.example.AutoGenerate")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class AutoGenerateProcessor extends AbstractProcessor {

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(AutoGenerate.class)) {
// 生成代码
generateCode(element);
}
return true;
}
}

知名应用

  • Lombok:编译期生成 getter/setter
  • Dagger:编译期生成依赖注入代码
  • MapStruct:编译期生成对象映射代码

总结

  1. 注解是接口:编译后继承 Annotation
  2. 运行期通过代理访问:JDK 动态代理生成注解实例
  3. 元注解控制行为:Target、Retention、Documented、Inherited
  4. 反射是读取注解的主要方式:getAnnotation、isAnnotationPresent
  5. APT 实现编译期处理:代码生成、编译期检查

注解机制是 Java 框架开发的基石,Spring、JPA、JUnit 等都重度依赖注解实现声明式编程。


   转载规则


《Java注解与元注解的实现原理》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录