🚀 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 天 · 核心总结(极简背诵版)
-
反射读取注解:
javaclazz.isAnnotationPresent(Ann.class) clazz.getAnnotation(Ann.class) method.getAnnotation(Ann.class) // ⚠️ 必须 @Retention(RUNTIME) -
三大应用场景:
参数校验:@NotNull/@Range → 反射检查字段值 日志切面:@Log → 反射调用方法前后增强 依赖注入:@Autowired → 反射设置字段值 -
框架原理映射:
Hibernate Validator ← 参数校验 Spring AOP ← 日志切面 Spring IOC ← 依赖注入 -
性能注意:
- ✅ 缓存反射对象(Class/Method/Field)
- ✅ 缓存注解信息(避免重复读取)
- ❌ 避免在高频循环中反射读取注解
明天预告 :🛠️ Lombok 实战 + 阶段总结 ------ 提升开发效率的利器!
- Lombok 核心注解(@Data/@Builder/@Slf4j)
- Lombok 原理(注解处理器 APT)
- 注意事项与避坑指南
- 第 3 阶段总结:设计模式与注解知识脑图
准备好了吗?明天我们用工具"解放双手",并结束本阶段学习! ✨🎉