Java 注解与反射:框架背后的"魔法"
【摘要】 Java 注解与反射:框架背后的"魔法"用 Spring 的时候,我们随手写个 @Autowired、@RequestMapping 就能完成依赖注入和路由映射,但有没有想过这些注解到底是怎么生效的?其实背后靠的就是 Java 的反射机制。今天来拆解一下这对"黄金搭档"。 什么是注解(Annotation)?注解本质上是一种元数据,就是"描述数据的数据"。它本身不影响代码逻辑,而是给类、方...
Java 注解与反射:框架背后的"魔法"
用 Spring 的时候,我们随手写个 @Autowired、@RequestMapping 就能完成依赖注入和路由映射,但有没有想过这些注解到底是怎么生效的?其实背后靠的就是 Java 的反射机制。今天来拆解一下这对"黄金搭档"。
什么是注解(Annotation)?
注解本质上是一种元数据,就是"描述数据的数据"。它本身不影响代码逻辑,而是给类、方法、字段打上标签,供其他程序在运行时读取和处理。
常见内置注解:
@Override:标记方法重写了父类方法,编译器会帮你检查。@Deprecated:标记已过时,使用时会有警告。@SuppressWarnings:抑制编译器警告。
自定义注解
创建一个注解用 @interface 关键字:
@Target(ElementType.METHOD) // 注解可以用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射读取
public @interface MyLog {
String value() default ""; // 注解的属性
}
两个关键元注解:
**@Target**:注解可以用在哪里(类、方法、字段等)。**@Retention**:注解保留到什么时候。RUNTIME表示运行时仍保留,这样才能被反射读取。
使用它:
@MyLog("查询用户")
public User getUser(Long id) {
return userMapper.selectById(id);
}
什么是反射(Reflection)?
反射允许程序在运行时动态地获取类的信息,并操作类的属性、方法和构造器,哪怕你在编译时根本不知道这个类的存在。
// 获取 Class 对象的三种方式
Class<?> clazz1 = Class.forName("com.example.User");
Class<?> clazz2 = User.class;
Class<?> clazz3 = user.getClass();
// 创建实例
Object obj = clazz1.getDeclaredConstructor().newInstance();
// 调用方法
Method method = clazz1.getMethod("setName", String.class);
method.invoke(obj, "张三");
// 获取字段
Field field = clazz1.getDeclaredField("name");
field.setAccessible(true); // 突破 private 限制
System.out.println(field.get(obj)); // 输出: 张三
注解 + 反射 = 框架的基石
Spring、MyBatis、JUnit 等框架的核心原理,本质上就是:扫描注解 → 反射获取信息 → 动态执行逻辑。
下面用一个极简的例子模拟 @MyLog 注解的处理:
public class LogProcessor {
public static void process(Object obj) throws Exception {
Class<?> clazz = obj.getClass();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(MyLog.class)) {
MyLog log = method.getAnnotation(MyLog.class);
System.out.println("[日志] 执行方法: " + method.getName()
+ ", 描述: " + log.value());
method.invoke(obj); // 实际执行方法
}
}
}
}
这就是 AOP(面向切面编程)的雏形——在不修改业务代码的前提下,额外织入日志、权限校验等逻辑。
反射的性能问题
反射比直接调用慢,因为涉及动态解析、安全检查等额外开销。但现代 JVM 做了很多优化(如方法内联、缓存等),对于大多数业务场景来说,这点性能损耗可以忽略不计。如果是在高频调用的热点路径上,可以考虑缓存 Method 对象来减少开销。
反射的常见用途
- 框架开发:Spring 的依赖注入、MyBatis 的 Mapper 映射。
- 序列化/反序列化:Jackson、Gson 通过反射读取字段。
- 单元测试:JUnit 通过反射发现并执行
@Test方法。 - 工具类:Apache Commons BeanUtils 的属性拷贝。
注意事项
setAccessible(true)可以突破 private 限制,但会破坏封装性,谨慎使用。- 反射调用时异常会被包装为
InvocationTargetException,需要 unwrap 才能拿到真实异常。 - 泛型在运行时会擦除,反射拿不到泛型的实际类型参数(除非通过继承保留)。
一句话总结
- 注解是"标签",反射是"读取标签并干活"的工具。
- 自定义注解需要配合
@Retention(RUNTIME)才能在运行时被读取。 - 几乎所有 Java 框架的底层都依赖注解 + 反射。
- 反射有性能开销,但日常业务场景无需过度担心。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)