Spring、ORM 和测试框架常在运行时读取类型信息并包装方法调用。理解注解、反射和代理后,你能判断框架行为来自普通 Java 机制,而不是“魔法”。
Table of contents
注解只是元数据
**注解(Annotation)**是附着在代码元素上的结构化元数据。它本身不会执行行为;必须有编译器、框架或你的代码读取它。
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface 记录耗时 {}
RUNTIME 表示运行时仍可通过反射读取;METHOD 限制只能标记方法。如果只是源码检查,可用 SOURCE,不必把元数据
留到运行时。
用动态代理实现拦截
import java.lang.reflect.*;
interface 问候服务 { String 问候(String name); }
final class 默认问候服务 implements 问候服务 {
@记录耗时 public String 问候(String name) { return "你好," + name; }
}
static <T> T 创建代理(Class<T> type, T target) {
Object proxy = Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type},
(ignored, method, args) -> {
Method implementation = target.getClass().getMethod(method.getName(), method.getParameterTypes());
long started = System.nanoTime();
try {
return implementation.invoke(target, args);
} catch (InvocationTargetException e) {
throw e.getCause();
} finally {
if (implementation.isAnnotationPresent(记录耗时.class)) {
System.out.println(method.getName() + " 耗时(ns): " + (System.nanoTime() - started));
}
}
});
return type.cast(proxy);
}
**反射(Reflection)**允许程序在运行时检查类、方法和注解。**动态代理(Dynamic Proxy)**创建一个实现指定接口的代理
对象,把方法调用转发给 InvocationHandler。JDK 动态代理只能代理接口;代理具体类通常需要字节码生成库。
最小依赖注入思路
依赖注入容器的核心步骤并不神秘:扫描候选类型、检查构造器、递归创建依赖、缓存实例。真正困难的是循环依赖、作用域、 生命周期、并发安全和错误诊断。因此学习示例可以几十行,生产容器不应该重复自研。
反射的边界
- 反射错误多在运行时出现,必须在启动阶段验证并快速失败。
- 不要随意
setAccessible(true)绕过模块与封装边界。 - 缓存已解析的
Method和元数据,但先测量,不要猜性能瓶颈。 - 代理内部必须正确展开
InvocationTargetException,保留真实业务异常。 - 自调用可能绕过代理;这也是事务注解在某些同类方法调用中不生效的原因。
下一步
阅读时间、金额、精度与序列化,处理跨进程数据时避免隐式假设。