JAVA重点基础、进阶知识及易错点总结(35)注解与反射

🚀 Java 巩固进阶 · 第 35 天

主题:注解与反射结合 ------ 让注解"活"起来

📅 进度概览 :继昨天学习注解定义之后,今天进入 注解的核心应用场景:注解 + 反射。单独的注解只是"标签",只有结合反射读取并处理,才能产生实际逻辑。这是 Spring、MyBatis、JUnit 等框架的底层核心。

💡 核心价值

  • 框架原理 :理解 Spring @Autowired 注入、MyBatis @Select 映射、SpringMVC @RequestMapping 绑定的底层实现。
  • 动态逻辑:运行时根据注解信息动态执行代码,实现"配置即代码"。
  • 工具开发:掌握自定义校验框架、简易 IOC 容器、自动化日志切面的实现思路。
  • 面试高频:反射读取注解流程、注解处理器(APT)概念是高级开发常考题。

一、核心原理:反射读取注解信息 🔍

1. 反射获取注解的 API

java 复制代码
Class<?> clazz = MyClass.class;

// ✅ 1. 判断是否有指定注解
if (clazz.isAnnotationPresent(MyAnnotation.class)) {
    // 有注解
}

// ✅ 2. 获取注解实例(读取属性值)
MyAnnotation ann = clazz.getAnnotation(MyAnnotation.class);
String value = ann.value();  // 读取注解属性

// ✅ 3. 获取所有注解
Annotation[] allAnns = clazz.getAnnotations();

// ✅ 4. 获取方法/字段上的注解
Method method = clazz.getMethod("doSomething");
Log logAnn = method.getAnnotation(Log.class);

⚠️ 关键前提

注解必须用 @Retention(RetentionPolicy.RUNTIME) 修饰,否则运行时反射读取不到!

2. 处理流程图解

复制代码
定义注解 (@Log) 
    ↓
标注在代码上 (UserService.createUser)
    ↓
反射扫描类 (Class.forName)
    ↓
获取方法 (getDeclaredMethods)
    ↓
检查注解 (isAnnotationPresent)
    ↓
读取属性 (getAnnotation)
    ↓
执行逻辑 (打印日志/校验/注入)

二、实战场景 1:简易参数校验框架 ✅

1. 定义校验注解

java 复制代码
// ✅ 定义 @NotNull 注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
    String message() default "字段不能为空";
}

// ✅ 定义 @Range 注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Range {
    int min() default 0;
    int max() default 100;
}

2. 标注在实体类上

java 复制代码
public class User {
    @NotNull(message = "用户名不能为空")
    private String name;
    
    @Range(min = 1, max = 150)
    private int age;
    
    // getter/setter
}

3. 实现校验器(核心逻辑)

java 复制代码
public class Validator {
    public static void validate(Object obj) throws Exception {
        Class<?> clazz = obj.getClass();
        // 1. 获取所有字段
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            Object value = field.get(obj);
            
            // 2. 检查 @NotNull
            if (field.isAnnotationPresent(NotNull.class)) {
                NotNull ann = field.getAnnotation(NotNull.class);
                if (value == null || "".equals(value)) {
                    throw new IllegalArgumentException(ann.message());
                }
            }
            
            // 3. 检查 @Range
            if (field.isAnnotationPresent(Range.class)) {
                Range ann = field.getAnnotation(Range.class);
                if (value instanceof Integer) {
                    int val = (Integer) value;
                    if (val < ann.min() || val > ann.max()) {
                        throw new IllegalArgumentException(
                            field.getName() + " 超出范围:" + ann.min() + "-" + ann.max());
                    }
                }
            }
        }
    }
}

// 测试
User user = new User();
user.setName("");  // 故意留空
Validator.validate(user);  // 抛异常:用户名不能为空

💡 框架关联

这就是 Hibernate Validator (JSR-303) 和 Spring @Valid 的底层原理!


三、实战场景 2:简易 AOP 日志切面 📝

1. 回顾 @Log 注解(第 34 天定义)

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
    String level() default "INFO";
}

2. 实现日志拦截器(模拟 Spring AOP)

java 复制代码
public class LogInterceptor {
    
    // ✅ 核心方法:处理带有 @Log 注解的方法
    public static void invokeWithLog(Object target, Method method, Object[] args) throws Exception {
        // 1. 检查方法是否有 @Log 注解
        if (method.isAnnotationPresent(Log.class)) {
            Log logAnn = method.getAnnotation(Log.class);
            
            // 2. 前置日志
            System.out.println("【日志】开始执行:" + logAnn.value() + 
                             " 级别:" + logAnn.level());
            long start = System.currentTimeMillis();
            
            // 3. 执行目标方法
            method.setAccessible(true);
            Object result = method.invoke(target, args);
            
            // 4. 后置日志
            long cost = System.currentTimeMillis() - start;
            System.out.println("【日志】执行完成,耗时:" + cost + "ms");
            
            return;
        }
        
        // 无注解,直接执行
        method.setAccessible(true);
        method.invoke(target, args);
    }
}

// 测试
UserService service = new UserService();
Method method = service.getClass().getMethod("createUser", String.class);
LogInterceptor.invokeWithLog(service, method, new Object[]{"Alice"});

💡 框架关联

Spring AOP 的 @Aspect 本质也是扫描注解 + 动态代理 + 反射调用。


四、实战场景 3:简易依赖注入(IOC 雏形)🔧

1. 定义 @MyAutowired 注解

java 复制代码
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
    String value() default "";  // 指定 Bean 名称
}

2. 实现简易容器

java 复制代码
public class SimpleContainer {
    private static Map<String, Object> beans = new HashMap<>();
    
    // ✅ 注册 Bean
    public static void registerBean(Object bean) {
        String name = bean.getClass().getSimpleName();
        beans.put(name, bean);
    }
    
    // ✅ 注入依赖
    public static void injectDependencies(Object bean) throws Exception {
        Class<?> clazz = bean.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            // 1. 检查是否有 @MyAutowired
            if (field.isAnnotationPresent(MyAutowired.class)) {
                field.setAccessible(true);
                
                // 2. 获取依赖类型
                Class<?> fieldType = field.getType();
                String beanName = fieldType.getSimpleName();
                
                // 3. 从容器获取 Bean
                Object dependency = beans.get(beanName);
                if (dependency == null) {
                    throw new RuntimeException("未找到依赖 Bean: " + beanName);
                }
                
                // 4. 注入字段
                field.set(bean, dependency);
            }
        }
    }
}

3. 测试注入

java 复制代码
// 1. 定义 Dao
class UserDao { public void save() { System.out.println("UserDao.save"); } }

// 2. 定义 Service
class UserService {
    @MyAutowired
    private UserDao userDao;  // 自动注入
    
    public void createUser() {
        userDao.save();
    }
}

// 3. 启动容器
UserDao dao = new UserDao();
UserService service = new UserService();
SimpleContainer.registerBean(dao);
SimpleContainer.registerBean(service);
SimpleContainer.injectDependencies(service);  // 执行注入

// 4. 调用
service.createUser();  // 输出:UserDao.save(userDao 已被自动赋值)

💡 框架关联

这就是 Spring IOC 容器 的核心逻辑:扫描 → 创建 Bean → 扫描字段 → 注入依赖!


五、🎯 今日实战任务:注解框架实战

任务 1:实现参数校验器

java 复制代码
/**
 * 要求:
 * 1. 定义 @NotNull 和 @Range 注解
 * 2. 创建 User 类,字段上添加注解
 * 3. 实现 Validator.validate() 方法
 * 4. 测试:传入合法/非法数据,观察是否抛异常
 * 
 * 💡 挑战:
 * - 支持嵌套对象校验(如 User 中有 Address 字段)
 * - 支持自定义校验规则接口
 */

任务 2:实现简易日志切面

java 复制代码
/**
 * 要求:
 * 1. 使用第 34 天定义的 @Log 注解
 * 2. 实现 LogInterceptor.invokeWithLog() 方法
 * 3. 统计方法执行耗时
 * 4. 测试:调用带注解和不带注解的方法,对比输出
 * 
 * 💡 思考:
 * - 这种方式需要手动调用拦截器,如何用动态代理自动拦截?
 * - 提示:结合第 33 天的动态代理知识
 */

任务 3:实现简易 IOC 容器

java 复制代码
/**
 * 要求:
 * 1. 定义 @MyAutowired 注解
 * 2. 实现 SimpleContainer 的 registerBean 和 injectDependencies
 * 3. 创建 Service + Dao 结构,测试依赖注入
 * 4. 验证:注入后的字段是否不为 null
 * 
 * 💡 挑战:
 * - 处理循环依赖问题(A 依赖 B,B 依赖 A)
 * - 支持按类型注入(不只按名称)
 */

任务 4:探索 Spring 源码

java 复制代码
/**
 * 要求:
 * 1. 打开 Spring 项目(或查看在线源码)
 * 2. 找到 @Autowired 注解定义,查看其元注解
 * 3. 找到 ClassUtils 或 ReflectionUtils 类,查看 Spring 如何读取注解
 * 4. 记录:Spring 用了哪些反射工具方法?
 * 
 * 💡 提示:
 * 关注 AnnotatedElementUtils 类
 */

📝 第 35 天 · 核心总结(极简背诵版)

  1. 反射读取注解

    java 复制代码
    clazz.isAnnotationPresent(Ann.class)
    clazz.getAnnotation(Ann.class)
    method.getAnnotation(Ann.class)
    // ⚠️ 必须 @Retention(RUNTIME)
  2. 三大应用场景

    复制代码
    参数校验:@NotNull/@Range → 反射检查字段值
    日志切面:@Log → 反射调用方法前后增强
    依赖注入:@Autowired → 反射设置字段值
  3. 框架原理映射

    复制代码
    Hibernate Validator ← 参数校验
    Spring AOP ← 日志切面
    Spring IOC ← 依赖注入
  4. 性能注意

    • ✅ 缓存反射对象(Class/Method/Field)
    • ✅ 缓存注解信息(避免重复读取)
    • ❌ 避免在高频循环中反射读取注解

明天预告 :🛠️ Lombok 实战 + 阶段总结 ------ 提升开发效率的利器!

  • Lombok 核心注解(@Data/@Builder/@Slf4j)
  • Lombok 原理(注解处理器 APT)
  • 注意事项与避坑指南
  • 第 3 阶段总结:设计模式与注解知识脑图

准备好了吗?明天我们用工具"解放双手",并结束本阶段学习! ✨🎉

相关推荐
邂逅星河浪漫1 小时前
【JavaScript】==和===区别详解
java·javascript·==·===
kvo7f2JTy2 小时前
吃透Linux/C++系统编程:文件与I/O操作从入门到避坑
java·linux·c++
Lzh编程小栈2 小时前
数据结构与算法之队列深度解析:循环队列+C 语言硬核实现 + 面试考点全梳理
c语言·开发语言·汇编·数据结构·后端·算法·面试
AbandonForce2 小时前
模拟实现vector
开发语言·c++·算法
TON_G-T2 小时前
useEffect为什么会触发死循环
java·服务器·前端
妙蛙种子3112 小时前
【Java设计模式 | 创建者模式】工厂方法模式
java·后端·设计模式·工厂方法模式
瞭望清晨2 小时前
Python多进程使用场景
开发语言·python
tHeya06II2 小时前
涵盖 Cursor、Claude Code、Skills
java·服务器
kim_puppy2 小时前
TCP的三次握手,四次挥手
java·网络·tcp