框架的魔法引擎 — Class 对象 → Method → invoke, JDK 代理只认接口, CGLIB 生成子类, Spring 靠这套在运行时织入 @Transactional
反射是把"类的结构当数据读": 拿到 Class<User> 就能在运行时列出字段/方法/注解, 甚至调用私有方法 — 代价是每次 invoke 都比直接调用多付访问检查、参数封装和无法内联的"三税"。动态代理则更进一步: 运行时直接生成一个新类($Proxy0 或子类), 在每个方法调用前后插入你的逻辑 — Spring 的 @Transactional、MyBatis 的 Mapper、RPC 客户端的桩, 全是这套机制的变体。理解了"容器里拿到的其实是代理", AOP 失效的玄学案例(自调用/静态方法/final 方法)就都有了机械解释。
User.class(类字面量, 不触发初始化)、user.getClass()(实例)、Class.forName("com.x.User")(按名加载并初始化, JDBC 老代码常见)。同一个类加载器里 Class 对象全局唯一。
Class<User> a = User.class; // 类字面量, 不触发 static 初始化 Class<?> b = new User().getClass(); // 从实例 Class<?> c = Class.forName("com.x.User"); // 按名加载并初始化 a == b && b == c; // → true 同加载器全局唯一
getSuperclass() 逐层遍历 — 框架扫描都这么写。
Class<?> c = Child.class; c.getDeclaredFields().length; // 只看本类 (含 private, 不含继承) c.getFields().length; // 只看 public, 含父类 public // 父类 private 字段: 沿 c.getSuperclass() 逐层 getDeclaredFields
--add-opens java.base/java.lang=ALL-UNNAMED 这类启动参数。
Field f = Point.class.getDeclaredField("x"); f.setAccessible(true); // 突破 private // JDK 9+ 对 java.* 内部成员 → InaccessibleObjectException // 需 --add-opens java.base/java.lang=ALL-UNNAMED
m.invoke(target, 100L); // 多付: 装箱 Object[] + 访问检查 + 无法内联 // 量级: 直接调用 ~1ns / 缓存 Method ~5ns / 裸查找 ~30ns+
static final Method。注意找不到时抛 NoSuchMethodException — 静态块里应转成 ExceptionInInitializerError 快速失败。
private static final Method PAY; static { try { PAY = Payment.class.getDeclaredMethod("pay", long.class); } catch (NoSuchMethodException e) { throw new ExceptionInInitializerError(e); } }
Proxy.newProxyInstance(loader, interfaces, handler) 运行时合成 $Proxy0 实现 given 接口, 每个方法体只是把 (proxy, Method, args) 转发给 InvocationHandler.invoke — 只能代理接口是字节码层面的硬限制。
UserApi proxy = (UserApi) Proxy.newProxyInstance(
loader, new Class<?>[]{UserApi.class},
(p, m, args) -> m.invoke(target, args)); // $Proxy0 只做转发
// 只能代理接口: 生成的是 implements UserApi 的新类Payment p = new ByteBuddy().subclass(Payment.class) .method(named("pay")).intercept(MethodDelegation.to(monitor)) .make().load(loader).getLoaded().getConstructor().newInstance(); // 生成子类: 无接口也能织入; final 类/方法是盲区
// B implements A → 默认 JDK 代理: 注入必须按接口类型 A bean = context.getBean(B.class); // 对: 按接口注入 // B b2 = context.getBean(B.class); ✗ JDK 代理不是 B // Boot 2+ proxyTargetClass=true 一律 CGLIB, 按类也行
@Retention(RetentionPolicy.RUNTIME) // 框架注解必须 RUNTIME @interface Table { String value(); } @Table("t_order") class Order {} Order.class.getAnnotation(Table.class).value(); // → "t_order"
@Inherited @interface Vip {} @Vip class Base {} class Sub extends Base {} Sub.class.isAnnotationPresent(Vip.class); // → true (仅类级+@Inherited) // 接口上的注解对实现类永远不可见, @Inherited 也救不了
e.getCause()。catch (IOException e) 直接套在 invoke 外面是编译不过的经典错误。
try { m.invoke(target, args); } catch (InvocationTargetException e) { Throwable real = e.getCause(); // 关键: 真实异常在 cause 里 } // catch (IOException e) 套在 invoke 外 → 编译不过
MethodHandles.Lookup() 只信任创建它的上下文, 私有访问要用 privateLookupIn。
MethodHandle mh = MethodHandles.lookup()
.findVirtual(String.class, "length", MethodType.methodType(int.class));
int n = (int) mh.invoke("abc"); // → 3, JIT 可内联List<String> list = new ArrayList<>(); Method add = List.class.getMethod("add", Object.class); add.invoke(list, 42); // 反射绕过泛型检查, 编译无感 String s = list.get(0); // → ClassCastException 炸在读取处
报表服务不想为 200 张表引入 MyBatis 全家桶; 注解声明映射, 启动时扫一次缓存, 热路径零查找:
@Table("t_order") public class Order { @Column("id") private Long id; @Column("sku_id") private String skuId; // 驼峰 ↔ 下划线由注解说了算 @Transient private BigDecimal feeCached; // 不落库的字段跳过 } // 启动期解析一次: Field → 列名 缓存住, 运行期不再 getDeclaredFields static Map<Field, String> columns(Class<?> clazz) { return Arrays.stream(clazz.getDeclaredFields()) .filter(f -> f.isAnnotationPresent(Column.class)) .peek(f -> f.setAccessible(true)) // 免掉每次 invoke 的访问检查 .collect(toMap(f -> f, f -> f.getAnnotation(Column.class).value())); }
读写各一行: f.set(o, rs.getObject(col, f.getType())) 与 f.get(o) 拼 SQL, 200 张表零手写映射。
每张导出表手写表头/取值/空值处理, 改一个字段要动三处; 反射 + 注解一次收口:
static String toCsv(List<?> rows, Class<?> type) throws IllegalAccessException { List<Field> cols = new ArrayList<>(); StringBuilder head = new StringBuilder(); for (Field f : type.getDeclaredFields()) { Csv h = f.getAnnotation(Csv.class); if (h == null) continue; // 没标注解 = 不导出, 新增字段默认安全 f.setAccessible(true); cols.add(f); head.append(h.value()).append(','); } StringBuilder body = new StringBuilder(head).append('\n'); for (Object row : rows) { for (Field f : cols) body.append(escape(f.get(row))).append(','); // 转义防 CSV 注入(= + - @ 开头) body.append('\n'); } return body.toString(); // 大数据量换 Stream 逐行写出, 别攒内存 }
给第三方支付接口加限流与审计, 不想改任何实现代码 — 12 行代理搞定:
@SuppressWarnings("unchecked") PayApi limited = (PayApi) Proxy.newProxyInstance( loader, new Class<?>[]{PayApi.class}, (proxy, method, args) -> { Limit lim = method.getAnnotation(Limit.class); // 方法级 QPS 配置 if (lim != null && !acquire(lim, method.getName())) throw new BizException(TOO_BUSY, Map.of("m", method.getName())); long t0 = System.nanoTime(); try { return method.invoke(target, args); } catch (InvocationTargetException e) { throw (e.getCause() instanceof RuntimeException r) ? r : new BizException(PAY_FAILED, Map.of(), e.getCause()); // 拆包再抛 } finally { accessLog(method.getName(), t0); } });
老代码 Payment 类没实现任何接口, JDK 代理无从下手; 子类代理 + 委托拦截:
Class<? extends Payment> enhanced = new ByteBuddy() .subclass(Payment.class) .method(ElementMatchers.isPublic().and(ElementMatchers.not(isSynthetic()))) .intercept(MethodDelegation.to(new TimedInterceptor())) .make().load(loader).getLoaded(); Payment p = enhanced.getDeclaredConstructor().newInstance(); static class TimedInterceptor { @RuntimeType public Object intercept(@SuperCall Callable<?> call) throws Exception { long t0 = System.nanoTime(); try { return call.call(); } finally { metrics.observe(System.nanoTime() - t0); } // P99 直方图埋点 } } // 注意: 生成的是子类实例, 别再 new 原类 — 拦截就白做了
Spring MVC 扫 @RequestMapping 的本质就是这段; 启动期建索引, 运行期 O(1) 派发:
static Map<String, Handler> scan(Class<?>... controllers) { Map<String, Handler> routes = new HashMap<>(); for (Class<?> c : controllers) for (Method m : c.getDeclaredMethods()) { Get g = m.getAnnotation(Get.class); // @Retention 必须 RUNTIME if (g != null) { m.setAccessible(true); routes.put(g.value(), new Handler(c, m)); // "GET /orders/:id" → 方法 } } return routes; // 启动时校验路由冲突, 快速失败优于 404 玄学 } // 派发: routes.get(path).invoke(controllerInstance, boundArgs)
规则引擎每请求反射调用 500 次, profiler 显示 40% CPU 在 getDeclaredMethod; 缓存改造:
// 差: 每次调用都查找 (遍历方法表 + 权限检查 + 参数匹配) Method m = target.getClass().getDeclaredMethod("pay", long.class); m.invoke(target, 100L); // 好: static final 缓存 + 一次性 setAccessible private static final Method PAY; static { try { PAY = Payment.class.getDeclaredMethod("pay", long.class); PAY.setAccessible(true); } catch (NoSuchMethodException e) { throw new ExceptionInInitializerError(e); } } // JMH 量级: 裸查找 ~30ns → 缓存+免检查 ~6ns → MethodHandle ~3ns → 直接调用 ~1ns PAY.invoke(target, 100L); // 压测: 热路径 CPU 从 40% 降到 8%
工单审核要展示"改了哪些字段, 从什么改成什么"; 不想为每个 DTO 手写 equals/diff:
static Map<String, Object[]> diff(Object before, Object after) throws IllegalAccessException { Map<String, Object[]> changes = new LinkedHashMap<>(); for (Field f : before.getClass().getDeclaredFields()) { if (Modifier.isStatic(f.getModifiers()) || f.isAnnotationPresent(Transient.class)) continue; // 静态/瞬态不参与 diff f.setAccessible(true); Object a = f.get(before), b = f.get(after); if (!Objects.equals(a, b)) changes.put(f.getName(), new Object[]{a, b}); } return changes; // 审计日志: {amount=[100, 80], status=[NEW, PAID]} }
支付渠道频繁新增, 不想每次改主工程代码发版; ServiceLoader 按声明发现实现:
// 每个渠道 jar 内: META-INF/services/com.x.PaymentChannel 写实现类全限定名 ServiceLoader<PaymentChannel> channels = ServiceLoader.load(PaymentChannel.class); for (PaymentChannel ch : channels) { // 懒加载: 迭代到才实例化 if (ch.supports(payType)) return ch; // 新渠道 = 提供新 jar, 主工程零改动 } throw new BizException(CHANNEL_NOT_FOUND, Map.of("type", payType)); // 与类加载页衔接: ServiceLoader 走线程上下文类加载器, 打破双亲委派让"父级"能看到"子级"实现 // 隔离要求高时换自定义 ClassLoader 每渠道一个命名空间, 避免依赖版本互相踩
客服后台日志把手机号/身份证打全了, 合规整改; 反向白名单 — 标了才放行:
static Map<String, Object> safeView(Object o) throws IllegalAccessException { Map<String, Object> out = new LinkedHashMap<>(); for (Field f : o.getClass().getDeclaredFields()) { f.setAccessible(true); Object v = f.get(o); if (f.isAnnotationPresent(Sensitive.class)) // 敏感标记 v = mask(v, f.getAnnotation(Sensitive.class).policy()); // 手机/证件/卡号各有掩码策略 out.put(f.getName(), v); } return out; // 出口(日志/工单/客服页)统一走 safeView, 新字段默认全显 — 评审卡 @Sensitive }
遗留代码把 Clock 写死在字段里, 没有构造器入口; 先用反射救测试, 再推动还债:
OrderService svc = new OrderService(repo); // 遗留: clock 字段 private final Field f = OrderService.class.getDeclaredField("clock"); f.setAccessible(true); // JDK 16+ 需 --add-opens 测试 JVM 参数 f.set(svc, Clock.fixed(Instant.parse("2026-01-01T00:00:00Z"), ZoneOffset.UTC)); // 断言"过期判断"不再碰运气: 时间被钉死, 测试可重复 assertTrue(svc.isExpired(order)); // 边界: 这招只准在测试用; 生产代码靠构造器注入. 大量这么写 = 可测性欠债, 回头补构造器参数
static final Method 启动期解析缓存 + 一次 setAccessible(true), 见场景 6。
// 错: m = t.getClass().getDeclaredMethod("pay", long.class); // 每次都查找 // 对: private static final Method PAY = ... 启动期解析缓存一次
--add-opens java.base/java.lang=ALL-UNNAMED 并记录还债。
Field f = String.class.getDeclaredField("value"); f.setAccessible(true); // 错: JDK 9+ → InaccessibleObjectException // 对: java --add-opens java.base/java.lang=ALL-UNNAMED -jar app.jar // 更好: 换公开 API, 别捅 JDK 内部
kotlin-maven-allopen 打开。
public final Result pay() { ... } // 错: 切面静默不拦截, 无报错 // 对: 去掉 final; Kotlin 默认 final → kotlin-maven-allopen 插件打开
// 错: Proxy.newProxyInstance(ld, new Class[0], h) → IllegalArgumentException // 对: new ByteBuddy().subclass(Payment.class)...intercept(...) // 或先抽 interface PaymentApi 再走 JDK 代理
proxy instanceof UserApi; // → true (实现了接口) proxy instanceof UserServiceImpl; // 错: false, JDK 代理另一族 proxy.getClass().getName(); // → com.x.$Proxy0 不是目标类
Field f = Config.class.getDeclaredField("MAX"); f.setAccessible(true); f.set(null, 200); // "成功" readMax(); // 错: 可能还是旧值, JIT 已常量内联 // 对: 可配置项用普通字段 + volatile, 别反射改 final
@Retention(RetentionPolicy.RUNTIME); 排查时先看这个再查别的。
@Retention(RetentionPolicy.CLASS) @interface Table {} // 错 Order.class.getAnnotation(Table.class); // → 永远 null // 对: @Retention(RetentionPolicy.RUNTIME)
// 错: getDeclaredMethod("pya", long.class) → NoSuchMethodException // 参数写 Integer.class 实参是 int → 同样找不到 // 对: 解析放 static 块, 拼错启动即红; 用方法引用常量替代字符串
e.getMessage() 丢真实信息. 正解: Throwable cause = e.getCause() 后再判断类型/记日志。
try { m.invoke(target); } catch (InvocationTargetException e) { log.error("fail", e.getCause()); // 对: 真实异常在 cause } // 错: log.error(e.getMessage()) → 丢真实异常信息
Array.newInstance(elemType, len) 建, Array.get/set 访问; 直接 field.set 到数组元素是不存在的 API。
int[] a = {1, 2, 3}; a.getClass().getComponentType(); // → int.class Object arr = Array.newInstance(int.class, 5); Array.set(arr, 0, 42); // 对: 用 Array 工具读写
// 错: 跨包 / shade relocate 后包名变了 → NoSuchField / IllegalAccess // 对: 反射工具与目标类同包; relocate 时同步改字符串里的类名
class Base { @Vip void run() {} } class Sub extends Base {} Sub.class.getDeclaredMethod("run").isAnnotationPresent(Vip.class); // 错: → false 方法注解不沿继承链 // 对: 自己沿 getSuperclass()/getInterfaces() 遍历查找
(Object) null 消除歧义, 否则 IllegalArgumentException: argument type mismatch. 正解: 精确按形参个数传, 编译不过就强转。
m.invoke(t, null); // 错: null 歧义 → IllegalArgumentException m.invoke(t, (Object) null); // 对: 消除歧义 m.invoke(t, new Object[]{a, b}); // 对: 按形参个数精确传
List.class.getMethod("add", Object.class).invoke(list, 42); String s = list.get(0); // 错: CCE 炸在"不相干"的行 // 对: 外部输入进集合前类型校验; 边界用带类型容器/record
// 错: 每次请求 new Enhancer().setSuperclass(...).create() // → 类不复用不卸载, Metaspace 涨到 OOM // 对: 代理类按目标类缓存复用 (ByteBuddy type cache)
public void a() { this.b(); } // 错: 直连原始对象, 切面失效 @Transactional public void b() { ... } // 对: self.b() 自注入代理 / 拆 Bean / AopContext.currentProxy()
Constructor<Singleton> c = Singleton.class.getDeclaredConstructor(); c.setAccessible(true); c.newInstance(); // 错: 第二实例被造出来 // 对: 构造器已存在检查抛异常; 或 enum 单例 (JVM 拒绝反射实例化)
MethodHandles.privateLookupIn(Target.class, lookup()), 别指望它像 setAccessible 一样万能。
// 错: MethodHandles.lookup() 只信任创建处上下文 // → 访问 Target 私有成员抛 IllegalAccessException // 对: MethodHandles.privateLookupIn(Target.class, MethodHandles.lookup())
record Order(String id) {} Field f = Order.class.getDeclaredField("id"); f.setAccessible(true); f.set(order, "x2"); // 错: final 抛异常 // 对: 换实例 new Order("x2"); 需反射可写就用普通类
// 错: 每请求都 getDeclaredFields() + getAnnotation(...) 新拷贝, GC 双高 // 对: 启动期扫一次存 Map<Method, Config>, 热循环只查缓存