Java 注解与反射实战:自定义注解从入门到精通

Java 注解(Annotation)与反射(Reflection) 是 Java 高级编程的核心技能之一,尤其在框架开发(如 Spring、MyBatis)、AOP、参数校验、权限控制、序列化、测试等场景中大量使用。自定义注解 + 反射 是实现“元编程”和“声明式编程”的最常见组合。

这份手册从零基础实战精通,结构清晰、代码密集、层层递进,适合快速上手和面试/项目复习。

1. 注解核心概念(必背)

注解本质上是元数据(关于程序的额外信息),不直接影响代码执行,但可以通过反射在运行时读取并产生行为。

四大元注解(定义注解时必须掌握):

元注解作用常用值 / 默认值说明
@Target指定注解能用在什么地方ElementType.*(TYPE, METHOD, FIELD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE, RECORD_COMPONENT)不写默认所有位置;最常用:METHOD + FIELD + TYPE
@Retention指定注解的生命周期(保留到什么时候)RetentionPolicy.*(SOURCE, CLASS, RUNTIME)SOURCE:只在源码(Lombok)
CLASS:字节码(默认)
RUNTIME:运行时可反射读取(最常用)
@Documented是否将注解包含进javadoc一般用于公开API的注解(如 @Deprecated)
@Inherited子类是否能继承父类的注解(只对类注解有效)常用于配置类注解(如 Spring 的 @Component 系列)
@Repeatable(JDK8+)允许同一个位置重复使用同一个注解指定容器注解如 @RolesAllowed({“admin”, “user”})

一句话记忆
@Target 管位置,@Retention 管存活,@Inherited 管遗传,@Documented 管文档。

2. 自定义注解入门(最基础写法)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 最简单的自定义注解
@Target({ElementType.METHOD, ElementType.TYPE})           // 可用于方法和类
@Retention(RetentionPolicy.RUNTIME)                       // 运行时可读
public @interface MyFirstAnnotation {
    // 注解可以有属性(类似接口的抽象方法)
    String value() default "default";                     // 单值时常用 value
    int priority() default 1;
    String[] roles() default {};                          // 数组属性
}

使用方式:

@MyFirstAnnotation(value = "重要方法", priority = 3, roles = {"admin", "manager"})
public class UserService {
    @MyFirstAnnotation("必须登录")
    public void getUserInfo() {
        // ...
    }
}

3. 反射读取注解(核心操作)

反射获取注解 是自定义注解产生实际效果的关键。

import java.lang.reflect.Method;

public class AnnotationParser {

    public static void main(String[] args) throws Exception {
        Class<?> clazz = UserService.class;

        // 1. 获取类上的注解
        if (clazz.isAnnotationPresent(MyFirstAnnotation.class)) {
            MyFirstAnnotation ann = clazz.getAnnotation(MyFirstAnnotation.class);
            System.out.println("类注解: " + ann.value() + ", priority=" + ann.priority());
        }

        // 2. 获取方法上的注解
        Method method = clazz.getMethod("getUserInfo");
        if (method.isAnnotationPresent(MyFirstAnnotation.class)) {
            MyFirstAnnotation ann = method.getAnnotation(MyFirstAnnotation.class);
            System.out.println("方法注解: " + ann.value());
            System.out.println("所需角色: " + String.join(", ", ann.roles()));
        }

        // 3. 获取所有注解(包含继承的)
        for (java.lang.annotation.Annotation a : method.getAnnotations()) {
            System.out.println(a);
        }
    }
}

4. 实战案例1:自定义 @NotNull 参数校验(方法参数级别)

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
    String message() default "参数不能为空";
}

使用 + 反射校验:

public class Validator {

    public static void validate(Object obj, Method method, Object[] args) throws Exception {
        java.lang.reflect.Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].isAnnotationPresent(NotNull.class)) {
                if (args[i] == null) {
                    NotNull notNull = parameters[i].getAnnotation(NotNull.class);
                    throw new IllegalArgumentException(notNull.message());
                }
            }
        }
    }

    // 示例调用
    public void saveUser(@NotNull(message = "用户不能为空") User user) {
        // ...
    }
}

5. 实战案例2:自定义 @Log 方法耗时日志(AOP思想前置)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";           // 日志描述
    boolean showParams() default true;
    boolean showResult() default true;
}

实现(使用反射 + 动态代理思想):

public class LogHandler {

    public static Object invokeWithLog(Object target, Method method, Object[] args) throws Throwable {
        if (!method.isAnnotationPresent(Log.class)) {
            return method.invoke(target, args);
        }

        Log log = method.getAnnotation(Log.class);
        long start = System.currentTimeMillis();

        System.out.println("开始执行: " + log.value());
        if (log.showParams()) {
            System.out.println("参数: " + Arrays.toString(args));
        }

        Object result = method.invoke(target, args);

        long end = System.currentTimeMillis();
        System.out.println("执行完成: " + log.value() + ", 耗时: " + (end - start) + "ms");
        if (log.showResult()) {
            System.out.println("返回: " + result);
        }

        return result;
    }
}

6. 实战案例3:自定义 @Auth 权限控制(最常见企业需求)

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Auth {
    String[] roles() default {};           // 允许的角色
    String[] permissions() default {};     // 允许的权限码
}

拦截器 / AOP 风格实现(伪代码):

public void checkAuth(Method method, User currentUser) {
    if (!method.isAnnotationPresent(Auth.class)) return;

    Auth auth = method.getAnnotation(Auth.class);
    String[] requiredRoles = auth.roles();

    if (requiredRoles.length > 0) {
        boolean hasRole = Arrays.stream(requiredRoles)
                .anyMatch(role -> currentUser.getRoles().contains(role));
        if (!hasRole) {
            throw new SecurityException("无权限,缺少角色: " + Arrays.toString(requiredRoles));
        }
    }
    // 类似处理 permissions
}

7. 进阶要点 & 面试高频

问题核心回答要点
运行时注解 vs 编译时注解RUNTIME → 反射读取(Spring @Autowired)
SOURCE → 编译期处理(Lombok)
注解属性支持的类型基本类型、String、Class、enum、Annotation、以上类型的数组
注解可以没有属性吗?可以,称为标记注解(Marker Annotation),如 @Override、@Test
@Repeatable 怎么用?定义容器注解(如 @Roles),然后在 @Roles 上用 @Repeatable(容器.class)
Spring 中注解是怎么工作的?启动时扫描 → BeanDefinition → 反射读取注解 → AOP/BeanPostProcessor 处理
注解继承关系?只有类注解 + @Inherited 才会被子类继承

8. 总结:从入门到精通的进阶路线

  1. 掌握四大元注解 + 基本语法(1天)
  2. 能写出标记注解 + 单属性注解 + 数组属性注解(1天)
  3. 熟练用反射读取类/方法/字段/参数上的注解(2天)
  4. 实现 2-3 个实战场景:校验、日志、权限、配置映射(3-5天)
  5. 理解 Spring AOP + 自定义注解的原理(AspectJ / JDK动态代理)
  6. 尝试写类似 @Cacheable、@Transactional 的简易版(进阶)

一句话口诀
注解写元数据,反射读行为,元注解定规则,RUNTIME 才生效。

有具体场景想深入实现(比如参数校验完整版、AOP方式、@Repeatable实战、注解处理器APT等),随时告诉我,我可以继续给出更详细的代码和原理!

文章已创建 4401

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部