动态代理与反射边界
学习目标
- 能为接口创建 JDK 动态代理,并在 InvocationHandler 中转发调用。
- 能正确处理参数、返回值、Object 方法和目标异常。
- 能判断 JDK Proxy、普通实现类和字节码代理框架的使用边界。
核心知识点
专业术语
- Proxy:运行时生成实现一个或多个接口的代理对象。
- InvocationHandler:代理每次方法调用都会进入的处理器。
- 代理对象:调用方看到的接口实例,不一定有可直接使用的业务实现类。
- default method:接口默认方法,代理可以选择调用默认实现或自行拦截。
白话解释与边界
JDK 动态代理只能代理接口,适合日志、鉴权、事务、远程调用等横切边界。处理器要区分业务方法和 toString/equals/hashCode,并原样处理参数、返回值与异常。需要代理具体类或复杂字节码增强时才考虑其他工具;代理不是把任意对象“变成接口”的万能转换器。
常用用法
Proxy.newProxyInstance:创建接口代理
代理接口必须对代理类加载器可见;返回值要符合方法返回类型,否则调用处才会收到 ClassCastException 或 WrongMethodTypeException。
// 说明:Proxy.newProxyInstance:创建接口代理。
import java.lang.reflect.Proxy;
interface Greeter { String greet(String name); }
public class ProxyCreateDemo {
public static void main(String[] args) {
Greeter greeter = (Greeter) Proxy.newProxyInstance(
Greeter.class.getClassLoader(),
new Class<?>[]{Greeter.class},
(proxy, method, arguments) -> "Hi, " + arguments[0]);
// 初始状态:greeter = (Greeter) Proxy.newProxyInstance(。
// 作用:Greeter greeter = (Greeter) Proxy.newProxyInstance(;创建接口代理。
System.out.println(greeter.greet("Java"));
// 输出:Hi, Java
}
}InvocationHandler:集中处理调用
处理器是每个代理方法的统一入口;复杂逻辑应拆成可测试的策略,不要把所有业务实现都塞进一个 lambda。
// 说明:InvocationHandler:集中处理调用。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
interface Calculator { int add(int left, int right); }
public class InvocationHandlerDemo {
public static void main(String[] args) {
InvocationHandler handler = (proxy, method, arguments) -> {
// 初始状态:handler = (proxy, method, arguments) ->。
// 作用:InvocationHandler handler = (proxy, method, arguments) -> {;集中处理调用。
System.out.println(method.getName());
// 输出:add
return (int) arguments[0] + (int) arguments[1];
};
var calculator = (Calculator) Proxy.newProxyInstance(
Calculator.class.getClassLoader(), new Class<?>[]{Calculator.class}, handler);
// 初始状态:calculator = (Calculator) Proxy.newProxyInstance(。
// 作用:var calculator = (Calculator) Proxy.newProxyInstance(;集中处理调用。
System.out.println(calculator.add(2, 3));
// 输出:5
}
}Method.invoke:转发到真实对象
真实代理通常缓存目标 Method,避免每次按名称查找;反射调用抛出的异常需要解包,否则业务根因会被包装。
// 说明:Method.invoke:转发到真实对象。
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Service { String run(String value); }
class RealService implements Service {
public String run(String value) { return value.toUpperCase(); }
// 作用:public String run(String value) { return value.toUpperCase(); };转发到真实对象。
}
public class ProxyDelegateDemo {
public static void main(String[] args) {
Service target = new RealService();
// 作用:Service target = new RealService();;转发到真实对象。
// 初始状态:target = new RealService()。
Service proxy = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(), new Class<?>[]{Service.class},
(object, method, arguments) -> {
// 作用:Service proxy = (Service) Proxy.newProxyInstance(;转发到真实对象。
// 作用:Service.class.getClassLoader(), new Class<?>[]{Service.class},;转发到真实对象。
// 初始状态:proxy = (Service) Proxy.newProxyInstance(。
Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
// 作用:Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());;转发到真实对象。
// 初始状态:targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes())。
return targetMethod.invoke(target, arguments);
// 初始状态:return targetMethod.invoke(target, arguments)。
// 作用:return targetMethod.invoke(target, arguments);;转发到真实对象。
});
System.out.println(proxy.run("java"));
// 输出:JAVA
}
}代理前后增加日志或计时
横切逻辑应保证 finally 中的记录不会覆盖原异常;日志还要脱敏参数和限制耗时标签的 cardinality。
// 说明:代理前后增加日志或计时。
import java.lang.reflect.Proxy;
interface Repository { String find(); }
public class ProxyTimingDemo {
public static void main(String[] args) {
Repository repository = (Repository) Proxy.newProxyInstance(
Repository.class.getClassLoader(), new Class<?>[]{Repository.class},
(proxy, method, args1) -> {
// 初始状态:repository = (Repository) Proxy.newProxyInstance(。
// 作用:Repository repository = (Repository) Proxy.newProxyInstance(;创建实现 Repository 的代理并赋给 repository。
// 作用:Repository.class.getClassLoader(), new Class<?>[]{Repository.class},;代理使用 Repository.class 的类加载器和接口数组。
long start = System.nanoTime();
// 初始状态:start = System.nanoTime()。
// 作用:long start = System.nanoTime();;System.nanoTime() 返回的纳秒值写入 start。
try {
return "result";
} finally {
System.out.println(method.getName() + ":" + (System.nanoTime() >= start));
// 输出:find:true
}
});
System.out.println(repository.find());
// 输出:result
}
}检查动态代理:确认代理类并取得处理器
这些 API 适合框架诊断和边界判断;不要把处理器暴露给不可信调用方,否则可能绕过代理层的安全策略。
// 说明:检查动态代理:确认代理类并取得处理器。
import java.lang.reflect.Proxy;
interface Clock { int hour(); }
public class ProxyInspectDemo {
public static void main(String[] args) {
Clock clock = (Clock) Proxy.newProxyInstance(
Clock.class.getClassLoader(), new Class<?>[]{Clock.class}, (proxy, method, args1) -> 8);
// 初始状态:clock = (Clock) Proxy.newProxyInstance(。
// 作用:Clock clock = (Clock) Proxy.newProxyInstance(;确认代理类并取得处理器。
// 作用:Clock.class.getClassLoader(), new Class<?>[]{Clock.class}, (proxy, method, args1) -> 8);;确认代理类并取得处理器。
System.out.println(Proxy.isProxyClass(clock.getClass()));
// 输出:true
System.out.println(Proxy.getInvocationHandler(clock) != null);
// 输出:true(第2次输出)
}
}多接口代理
多个接口的方法签名冲突时要设计统一语义;代理类也必须满足接口方法返回类型的兼容要求。
// 说明:多接口代理。
import java.lang.reflect.Proxy;
interface Named { String name(); }
interface Enabled { boolean enabled(); }
public class MultiInterfaceProxyDemo {
public static void main(String[] args) {
Object proxy = Proxy.newProxyInstance(
Named.class.getClassLoader(), new Class<?>[]{Named.class, Enabled.class},
(object, method, args1) -> method.getName().equals("name") ? "api" : true);
// 初始状态:proxy = Proxy.newProxyInstance(。
// 作用:Object proxy = Proxy.newProxyInstance(;创建同时实现 Named、Enabled 的代理并赋给 proxy。
// 作用:Named.class.getClassLoader(), new Class<?>[]{Named.class, Enabled.class},;使用 Named.class 的类加载器与 Named、Enabled 接口。
// 作用:(object, method, args1) -> method.getName().equals("name") ? "api" : true);;name() 返回 "api",其他接口方法返回 true。
System.out.println(((Named) proxy).name() + "," + ((Enabled) proxy).enabled());
// 输出:api,true
}
}不常用但需要知道
处理代理对象基础契约:统一对象方法语义
Object 方法也会进入 InvocationHandler;若不处理,日志、集合键和断言可能出现令人困惑的默认行为。示例使用 switch 表达式是 JDK 14+ 稳定语法,不是预览特性。
// getProxyClass 用 LegacyApi 的类加载器生成只实现该接口的代理 Class,此处尚未创建 InvocationHandler 或实例。
import java.lang.reflect.Proxy;
interface Marker { }
public class ProxyObjectMethodDemo {
public static void main(String[] args) {
Marker marker = (Marker) Proxy.newProxyInstance(
Marker.class.getClassLoader(), new Class<?>[]{Marker.class},
(proxy, method, args1) -> switch (method.getName()) {
// 初始状态:marker = (Marker) Proxy.newProxyInstance(。
// 作用:Marker marker = (Marker) Proxy.newProxyInstance(;统一对象方法语义。
// 作用:Marker.class.getClassLoader(), new Class<?>[]{Marker.class},;统一对象方法语义。
case "toString" -> "MarkerProxy";
case "hashCode" -> 42;
case "equals" -> proxy == args1[0];
default -> null;
});
System.out.println(marker + "," + marker.hashCode() + "," + marker.equals(marker));
// 输出:MarkerProxy,42,true
}
}InvocationHandler.invokeDefault:调用接口默认方法
默认方法调用受接口可见性和模块边界影响;代理若要统一改写默认方法,不应悄悄混用目标实现和默认实现。
// 作用:通过 InvocationHandler.invokeDefault 调用接口默认方法。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
interface Greeting {
default String hello() { return "default"; }
}
public class ProxyDefaultMethodDemo {
public static void main(String[] args) {
InvocationHandler handler = (proxy, method, args1) ->
method.isDefault() ? InvocationHandler.invokeDefault(proxy, method, args1) : "custom";
// 初始状态:handler = (proxy, method, args1) ->。
// 作用:method.isDefault() ? InvocationHandler.invokeDefault(proxy, method, args1) : "custom";;调用接口默认方法,返回读取结果。
Greeting greeting = (Greeting) Proxy.newProxyInstance(
Greeting.class.getClassLoader(), new Class<?>[]{Greeting.class}, handler);
// 作用:Greeting greeting = (Greeting) Proxy.newProxyInstance(;创建实现 Greeting 的代理并赋给 greeting。
// 作用:Greeting.class.getClassLoader(), new Class<?>[]{Greeting.class}, handler);;调用接口默认方法,返回读取结果。
// 初始状态:greeting = (Greeting) Proxy.newProxyInstance(。
System.out.println(greeting.hello());
// 输出:default
}
}UndeclaredThrowableException:检查异常包装
处理器抛出的检查异常若不在接口声明中,会被包装;代理层要决定异常翻译契约,不能让调用方随机面对包装层次。
// 作用:通过 UndeclaredThrowableException 检查异常包装。
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
interface SafeAction { void run(); }
public class ProxyThrowableDemo {
public static void main(String[] args) {
SafeAction action = (SafeAction) Proxy.newProxyInstance(
SafeAction.class.getClassLoader(), new Class<?>[]{SafeAction.class},
(proxy, method, args1) -> { throw new Exception("checked"); });
// 初始状态:(proxy, method, args1) -> { throw new Exception("checked"); })。
// 初始状态:action = (SafeAction) Proxy.newProxyInstance(。
// 作用:SafeAction action = (SafeAction) Proxy.newProxyInstance(;检查异常包装。
try {
action.run();
// 作用:action.run();;检查异常包装。
} catch (UndeclaredThrowableException e) {
System.out.println(e.getUndeclaredThrowable().getMessage());
// 输出:checked
}
}
}Proxy.getProxyClass 的旧式动态类获取
新代码优先使用 newProxyInstance,它同时完成类生成和实例创建;直接获取代理类容易把实现细节泄漏到业务代码。
// 说明:Proxy.getProxyClass 使用 LegacyApi 的类加载器生成代理 Class;类名包含 "$Proxy",但此时尚未创建代理实例。
import java.lang.reflect.Proxy;
interface LegacyApi { }
public class ProxyClassDemo {
public static void main(String[] args) {
Class<?> proxyClass = Proxy.getProxyClass(
LegacyApi.class.getClassLoader(), LegacyApi.class);
// 初始状态:proxyClass = Proxy.getProxyClass(。
// 作用:Class<?> proxyClass = Proxy.getProxyClass(;按 LegacyApi.class 获取代理 Class,结果写入 proxyClass。
// 作用:LegacyApi.class.getClassLoader(), LegacyApi.class);;使用 LegacyApi 的类加载器和接口类型。
System.out.println(proxyClass.getName().contains("$Proxy"));
// 输出:true
}
}代理具体类的边界
JDK Proxy 的接口前提不能代理普通具体类;需要类级别拦截时应重新设计接口,或选择明确的字节码工具并评估模块、调试和维护成本。
import java.lang.reflect.Proxy;
class ConcreteService { public String run() { return "ok"; } }
public class ConcreteProxyBoundaryDemo {
public static void main(String[] args) {
System.out.println(ConcreteService.class.isInterface());
// 输出:false
System.out.println(Proxy.isProxyClass(ConcreteService.class));
// 输出:false(第2次输出)
}
}代理与模块可见性
接口必须对代理类加载器可见;命名模块中还要检查导出、开放和包可访问性,不能只在 classpath 上测试后直接迁移。
import java.lang.reflect.Proxy;
interface PublicService { String call(); }
public class ProxyModuleBoundaryDemo {
public static void main(String[] args) {
Object proxy = Proxy.newProxyInstance(
PublicService.class.getClassLoader(), new Class<?>[]{PublicService.class},
(object, method, args1) -> "ok");
// 初始状态:proxy = Proxy.newProxyInstance(。
// 作用:Object proxy = Proxy.newProxyInstance(;创建实现 PublicService 的代理并赋给 proxy。
// 作用:PublicService.class.getClassLoader(), new Class<?>[]{PublicService.class},;使用 PublicService.class 的类加载器与接口类型。
System.out.println(proxy.getClass().getInterfaces()[0].getSimpleName());
// 输出:PublicService
}
}简单案例
import java.lang.reflect.Proxy;
interface UserService { String find(String id); }
public class DynamicProxyDemo {
public static void main(String[] args) {
UserService service = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(), new Class<?>[]{UserService.class},
(proxy, method, args1) -> {
// 初始状态:service = (UserService) Proxy.newProxyInstance(。
// 作用:UserService service = (UserService) Proxy.newProxyInstance(;创建实现 UserService 的代理并赋给 service。
// 作用:UserService.class.getClassLoader(), new Class<?>[]{UserService.class},;使用 UserService.class 的类加载器与接口类型。
System.out.println("call " + method.getName());
// 输出:call find
return "user-" + args1[0];
});
System.out.println(service.find("42"));
// 输出:user-42
}
}这个代理把日志和返回值放在接口边界,真实项目还要处理鉴权、异常、超时、重试和 Object 方法,不要把代理当成业务容器。
易混点
- JDK Proxy 代理接口,不能直接代理具体类;类代理需要不同工具或重新抽象接口。
- InvocationHandler 的异常会按接口声明传播或包装为 UndeclaredThrowableException。
- Object 方法同样进入处理器,equals/hashCode 的一致性会影响集合行为。
sendAsync、反射 Method.invoke 和动态代理都提供动态性,但职责分别是异步 I/O、成员调用和接口拦截。
课后小问
为什么一个具体类没有实现接口时不能直接用 JDK Proxy 拦截? 答案:JDK Proxy 生成的类只能实现指定接口,不能继承并重写任意具体类。 解析:优先抽出稳定接口;必须拦截类方法时再评估字节码代理方案及其模块影响。
为什么代理处理器必须处理
equals和hashCode? 答案:集合、缓存和断言会直接调用 Object 方法,它们也会进入 InvocationHandler。 解析:如果返回值不稳定,代理作为 Map key 或 Set 元素时会出现难以追踪的行为。
本节小结
- Proxy 创建接口代理,InvocationHandler 集中处理调用。
- 代理可以做横切逻辑,但必须维护参数、返回值、异常和 Object 契约。
- 默认方法、检查异常、类代理限制和模块可见性是低频但关键边界。
- 代理解决的是调用拦截,不替代接口设计、业务错误处理和性能测量。
快速回顾
newProxyInstance需要类加载器、接口数组和处理器。method.invoke可转发到真实对象,但异常需要解包。- Object 方法、默认方法和检查异常都要单独考虑。
- JDK Proxy 只面向接口,类代理是另一类工具问题。
阿源的知识库