静态代理与动态代理
静态代理
Java中的静态代理是一种设计模式,它允许我们通过一个代理类来控制对另一个类的访问。这个代理类(代理对象)和目标类(被代理对象)实现相同的接口,代理类内部持有目标类的一个实例,并可以在调用目标类的方法前后执行一些附加操作,比如权限检查、日志记录、事务处理等。
静态代理的"静态"二字意味着代理类是手动创建或固定的,它在编译时已经确定,与之相对的是动态代理,后者在运行时动态创建代理类和对象。
静态代理的组成部分
- 接口(Interface) :定义了代理类和目标类需要实现的方法。
- 目标类(Target Class) :实现接口的被代理类,包含业务逻辑。
- 代理类(Proxy Class) :也实现了接口,并持有目标类的引用,在调用目标类的方法前后可以执行额外的操作。
静态代理的实现步骤
下面是一个简单的静态代理实现的例子:
假设有一个简单的接口Subject和一个实现了这个接口的实际类RealSubject,我们想通过一个代理类ProxySubject来访问RealSubject的方法。
定义接口(Massage) :
public interface Massage {
void request();
}
实现接口的目标类(Agent)
public class Agent implements Massage {
@Override
public void request() {
System.out.println("Executing request in Agent");
}
}
代理类(ProxySubject)
public class ProxySubject implements Massage {
private Agent realSubject; // 持有对真实主题的引用
public ProxySubject(Subject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
preRequest();
realSubject.request(); // 调用真实主题的方法
postRequest();
}
private void preRequest() {
System.out.println("Pre Request in ProxySubject");
}
private void postRequest() {
System.out.println("Post Request in ProxySubject");
}
}
使用代理
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
ProxySubject proxy = new ProxySubject(realSubject);
proxy.request(); // 使用代理访问
}
}
静态代理的优缺点
优点:
- 简单易懂,易于实现。
- 在不修改目标对象的前提下扩展目标对象的功能,因为我们可以直接通过修改Proxy类来实现功能的拓展。
缺点:
- 每个代理类只能为一个接口服务,功能一致但接口不同的类需要额外的代理类。
- 代理和实际对象之间的增加了耦合度。
- 系统中类的数量可能会大量增加,增加了系统的复杂度。
静态代理虽然简单,但在实际使用中由于其灵活性不足,动态代理(如Java的动态代理、CGLIB等)通常是更受欢迎的选择。
动态代理
Java动态代理主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。Proxy类用于在运行时动态创建代理对象,而InvocationHandler则用于定义代理对象的调用处理程序。
当通过Proxy创建代理对象并对其进行方法调用时,实际上是调用了InvocationHandler的invoke方法。在这个方法内,可以定义在调用实际方法前后要执行的逻辑,比如日志记录、权限检查、事务处理等。
对比静态代理的区别, 静态代理中使用对象对被代理方法进行调用, 动态代理统一由 InvocationHandler 进行方法反射调用
定义一个接口
public interface Subject {
void doAction();
}
实现接口的目标类
public class RealSubject implements Subject {
@Override
public void doAction() {
System.out.println("Executing action in RealSubject");
}
}
创建调用处理程序
public class DynamicProxyHandler implements InvocationHandler {
private Object subject; // 实际对象的引用
public DynamicProxyHandler(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(subject, args); // 调用实际对象的方法
System.out.println("After method: " + method.getName());
return result;
}
}
public Object invoke(Object o, Method method, Object[] objects){}
这个方法中。
在Java动态代理机制中,InvocationHandler接口的invoke方法是核心方法,它用于处理代理实例上的方法调用。invoke方法有三个参数,分别是proxy、method和args,它们的作用如下:
1. Object proxy
proxy参数代表的是代理对象本身,即调用方法的代理实例。通过这个对象,可以执行代理对象上的其他方法(尽管直接这么做可能会导致无限递归调用,特别是在调用像toString、hashCode、equals这样的方法时)。然而,在大多数情况下,不需要通过proxy对象直接操作,除非有特定的需求,比如计算代理对象的哈希码或比较代理对象。
- 使用注意:直接在
invoke方法内部调用proxy的方法很容易导致无限递归,因为这些调用会再次被路由到invoke方法,最终可能导致StackOverflowError。
2. Method method
- 类型:
java.lang.reflect.Method,method参数表示代理对象上被调用的方法。这个Method对象提供了关于方法本身的所有信息,包括方法名、返回类型、参数类型等。可以使用这个对象来决定如何处理当前的方法调用,比如只对特定的方法执行某些操作。 - 使用方式:通过检查
method的名称或其他属性来实现条件逻辑,或者直接使用method.invoke(...)来对特定目标对象调用原始方法。
3. Object[] args
args参数是一个对象数组,包含了代理方法调用时传递的所有实际参数。这些是调用者传给方法的参数,我们可以对这些参数进行操作,比如修改参数值,或者基于这些参数值进行特定的逻辑处理。
- 使用方式:
args允许读取、修改或者分析调用方法时使用的参数。例如,可以记录方法调用的参数值,或者在调用原始方法之前修改这些参数。
使用Proxy类动态创建代理对象并使用代理
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
Subject proxySubject = (Subject) Proxy.newProxyInstance(
RealSubject.class.getClassLoader(),
new Class[]{Subject.class},
new DynamicProxyHandler(realSubject));
proxySubject.doAction(); // 使用代理执行方法
}
}
Proxy.newProxyInstance方法用于在运行时创建一个实现了Subject接口的代理对象。这个代理对象内部使用DynamicProxyHandler来处理所有的方法调用。
动态代理的优点
- 灵活性:动态代理允许我们在运行时创建代理对象,提高了代码的灵活性和复用性。
- 简化开发:通过动态代理可以轻松实现横切关注点(如日志、安全等)的统一处理,简化了开发。
- 减少重复代码:避免了为每个需要代理的类手动编写代理类的需求,减少了重复代码。
如果有多个接口,例如还有Subject2, Subject3等,我们在使用这个代码的时候,还可以这样写:
Subject proxySubject = (Subject) Proxy.newProxyInstance(
RealSubject.class.getClassLoader(),
new Class[]{Subject.class, Subject2.class, Subject3.class},
new DynamicProxyHandler(realSubject));
之后需要调整DynamicProxyHandler:
public class DynamicProxyHandler implements InvocationHandler {
private Object subject1;
private Object subject2;
private Object subject3;
public DynamicProxyHandler(Object subject1, Object subject2, Object subject3) {
this.subject1 = subject1;
this.subject2 = subject2;
this.subject3 = subject3;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Method called: " + method.getName());
// 根据method所属的接口类型,将调用分派到不同的实际对象
if (Arrays.asList(subject1.getClass().getInterfaces()).contains(method.getDeclaringClass())) {
return method.invoke(subject1, args);
} else if (Arrays.asList(subject2.getClass().getInterfaces()).contains(method.getDeclaringClass())) {
return method.invoke(subject2, args);
} else if (Arrays.asList(subject3.getClass().getInterfaces()).contains(method.getDeclaringClass())) {
return method.invoke(subject3, args);
}
throw new RuntimeException("Method not supported: " + method.getName());
}
}
上边的方式中:
subject1.getClass().getInterfaces(): 获取subject1对象的类实现的所有接口。这部分返回的是一个Class<?>[]数组,每个元素代表一个接口的Class对象。Arrays.asList(...): 将上一步获取的接口数组转换成一个List<Class<?>>。这样做是为了利用List提供的方法,如contains,来检查列表中是否包含特定元素。method.getDeclaringClass(): 获取声明了当前正在被调用的method的类(或接口)。对于接口中定义的方法,这将返回该接口的Class对象。contains(...): 检查从subject1的类实现的接口列表中是否包含method所声明的接口。如果包含,说明method是subject1实现的接口之一的方法。
如果不想调整,也可以通过RealSubject同时实现subject2和subject3来实现同样的效果。
public class RealSubject implements Subject,Subject2,Subject3 {
@Override
public void doAction() {
System.out.println("Executing action in RealSubject");
}
@Override
public void doAction2() {
}
@Override
public void doAction3() {
}
}
自带实现类的整体源码分析
// 1. 定义业务接口 Subject
public interface Subject {
// 客户端想要调用的业务方法
void doAction();
}
// 2. 真实的实现类 RealSubject
public class RealSubject implements Subject {
@Override
public void doAction() {
// 真实业务逻辑在这里执行
System.out.println("Executing action in RealSubject");
}
}
// 3. 动态代理的处理器:拦截所有方法调用并委托给真实对象
public class DynamicProxyHandler implements InvocationHandler {
// 持有真实对象引用,用于反射调用
private Object subject;
public DynamicProxyHandler(Object subject) {
// 在创建代理时传入真实对象
this.subject = subject;
}
/**
* 任何通过代理对象调用的接口方法,都会路由到这里
*
* @param proxy 代理实例本身(通常不用直接调用它)
* @param method 被调用的方法的反射对象
* @param args 方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置处理:打印方法名
System.out.println("Before method: " + method.getName());
// 核心委托:反射调用真实对象的方法,执行实际业务
Object result = method.invoke(subject, args);
// 后置处理:打印方法名
System.out.println("After method: " + method.getName());
return result;
}
}
// 4. 客户端使用动态代理
public class Client {
public static void main(String[] args) {
// 4.1 创建真实对象
RealSubject realSubject = new RealSubject();
// 4.2 生成代理实例
// - 使用 RealSubject 的 ClassLoader
// - 指定代理需要实现的接口 Subject
// - 传入上面定义的 DynamicProxyHandler
Subject proxySubject = (Subject) Proxy.newProxyInstance(
RealSubject.class.getClassLoader(),
new Class[]{Subject.class},
new DynamicProxyHandler(realSubject)
);
// 4.3 调用代理,相当于:
// $Proxy0.doAction() → handler.invoke(...) → realSubject.doAction()
proxySubject.doAction();
}
}
// 5. 下面是 JVM 运行时生成的代理类 $Proxy0(简化自 FernFlower 反编译)
// 它 extends java.lang.reflect.Proxy 并 implements Subject
package jdk.proxy1;
public final class $Proxy0 extends Proxy implements Subject {
// 缓存 Method 对象,指向 Object.hashCode / equals / toString / Subject.doAction
private static final Method m0; // Object.hashCode
private static final Method m1; // Object.equals(Object)
private static final Method m2; // Object.toString
private static final Method m3; // Subject.doAction()
// 构造函数:把用户传入的 InvocationHandler 交给父类存储
public $Proxy0(InvocationHandler h) {
super(h);
}
// ---------- 以下每个方法都把调用转给 InvocationHandler ----------
public final int hashCode() {
try {
// super.h 就是 DynamicProxyHandler 实例
return (Integer) super.h.invoke(this, m0, null);
} catch (Throwable t) {
// 把受检异常包装成 RuntimeException
throw new UndeclaredThrowableException(t);
}
}
public final boolean equals(Object obj) {
try {
return (Boolean) super.h.invoke(this, m1, new Object[]{obj});
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
public final String toString() {
try {
return (String) super.h.invoke(this, m2, null);
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
// 这是代理了 Subject.doAction()
public final void doAction() {
try {
// 调用 InvocationHandler.invoke(proxyInstance, Method m3, null)
super.h.invoke(this, m3, null);
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
// 静态初始化:反射获取各方法到 Method 对象
static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
// 注意这里包名要和接口 Subject 的包保持一致
m3 = Class.forName("Subject 所在的完整包名.Subject").getMethod("doAction");
} catch (Exception e) {
throw new RuntimeException("初始化代理 Method 对象失败", e);
}
}
}
-
第一段 代码,展示了如何定义接口、实现类,以及如何通过
Proxy.newProxyInstance与InvocationHandler组合成动态代理。 -
第二段 是反编译后的
$Proxy0,它是真正实现了Subject接口的“代理类”,在每个方法体里只是调用super.h.invoke(...)将调用交给DynamicProxyHandler.invoke(...)。
另外,补充一个类似于Retrofit的动态代理
在 JDK 动态代理里,真正的“实现”都写在那段 invoke(...) 里——代理类($Proxy0)本身只会做两件事:
- 调用
InvocationHandler.invoke(this, method, args) - 返回
invoke的结果
/**
* 1. 接口定义:只声明方法签名,无需任何实现
*/
public interface HelloService {
/**
* sayHello 方法
* @param var1 客户端传入的名字
* @return 代理生成的问候语
*/
String sayHello(String var1);
}
/**
* 2. 主类:演示“无真实实现类”的纯动态代理
*/
public class DynamicProxyNoTargetDemo {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
/**
* 3. 使用 Proxy.newProxyInstance 创建代理实例
* - ClassLoader:用来加载生成的代理类
* - interfaces:代理类要实现的接口列表
* - InvocationHandler:处理所有方法调用的逻辑
*/
HelloService proxy = (HelloService) Proxy.newProxyInstance(
HelloService.class.getClassLoader(), // ① 指定 ClassLoader
new Class<?>[]{ HelloService.class }, // ② 指定要实现的接口
new InvocationHandler() { // ③ 定义 InvocationHandler
/**
* 所有通过 proxy 调用的方法,都会路由到这里
*
* @param proxy 当前代理对象
* @param method 正在调用的接口方法(反射 Method 对象)
* @param args 方法参数数组
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 3.1 如果是 Object 自带的方法(toString、hashCode、equals),
// 直接执行当前 handler 对象自身的方法,以保持基础行为正常
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
// 3.2 针对 sayHello 方法做自定义处理,
// 这一段代码就相当于你手写的 `HelloServiceImpl.sayHello(...)`
if ("sayHello".equals(method.getName())
&& args != null && args.length == 1) {
String name = (String) args[0]; // 拿到传入的参数
// 返回自定义结果,而不需要真实实现类
return "[Proxy 自动生成] Hello, " + name + "!";
}
// 3.3 其他方法一律不支持,抛出异常
throw new UnsupportedOperationException(
"Unsupported method: " + method.getName());
}
}
);
// 4. 通过代理对象调用接口方法,实际会执行 InvocationHandler.invoke(...)
String result = proxy.sayHello("李四");
System.out.println(result);
// 预期输出: [Proxy 自动生成] Hello, 李四!
// 5. 打印运行时生成的代理类全限定名,通常形如 jdk.proxy1.$Proxy0
System.out.println("Generated proxy class: " + proxy.getClass().getName());
}
}
在linux或者git bash中执行命令,生成动态代理的文件
java \
-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true \
-cp . \
DynamicProxyNoTargetDemo
动态代理生成的真实的文件,这也是运行时真正调用的文件:
/**
* 自动生成的动态代理类,继承自 java.lang.reflect.Proxy 并实现用户接口 HelloService
* 由 JVM 在运行时通过 ProxyGenerator 生成,用于将方法调用委托给 InvocationHandler
*/
public final class $Proxy0 extends Proxy implements HelloService {
// 缓存原生 Object.hashCode 方法的 Method 对象,用于在代理中转发
private static final Method m0;
// 缓存原生 Object.equals 方法的 Method 对象,用于在代理中转发
private static final Method m1;
// 缓存原生 Object.toString 方法的 Method 对象,用于在代理中转发
private static final Method m2;
// 缓存接口 HelloService.sayHello(String) 方法的 Method 对象,用于在代理中转发
private static final Method m3;
/**
* 构造函数,由 Proxy 架构调用,将用户传入的 InvocationHandler 保存在父类字段 h 中
* @param h 用户实现的 InvocationHandler 实例
*/
public $Proxy0(InvocationHandler h) {
super(h);
}
/**
* 代理 hashCode() 方法的实现,将调用转发给 InvocationHandler
*/
public final int hashCode() {
try {
// 调用 handler.invoke(proxyInstance, Method, args)
return (Integer) super.h.invoke(this, m0, (Object[]) null);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
// 将受检异常包装成 UndeclaredThrowableException
throw new UndeclaredThrowableException(t);
}
}
/**
* 代理 equals(Object) 方法的实现,将调用转发给 InvocationHandler
*/
public final boolean equals(Object obj) {
try {
return (Boolean) super.h.invoke(this, m1, new Object[]{obj});
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
/**
* 代理 toString() 方法的实现,将调用转发给 InvocationHandler
*/
public final String toString() {
try {
return (String) super.h.invoke(this, m2, (Object[]) null);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
/**
* 代理接口方法 sayHello(String),将调用转发给 InvocationHandler
*/
public final String sayHello(String name) {
try {
return (String) super.h.invoke(this, m3, new Object[]{name});
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
// 在类加载时初始化所有 Method 对象,以便在 invoke 时快速查找
static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("HelloService").getMethod("sayHello", Class.forName("java.lang.String"));
} catch (NoSuchMethodException e) {
// 如果方法签名不匹配,抛出 NoSuchMethodError
throw new NoSuchMethodError(e.getMessage());
} catch (ClassNotFoundException e) {
// 如果找不到类,抛出 NoClassDefFoundError
throw new NoClassDefFoundError(e.getMessage());
}
}
/**
* 支持 MethodHandles.Lookup API,确保代理类能够访问目标方法的句柄
*/
private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup lookup) throws IllegalAccessException {
// 仅在 lookupClass 为 Proxy 且拥有完全权限时,返回真实的 Lookup
if (lookup.lookupClass() == Proxy.class && lookup.hasFullPrivilegeAccess()) {
return MethodHandles.lookup();
} else {
throw new IllegalAccessException(lookup.toString());
}
}
}
JDK 动态代理在运行时按接口列表(和 ClassLoader)生成代理类
-
同一套接口,只生成一次
- 如果多次用同一个接口数组、同一个 ClassLoader 调用
Proxy.newProxyInstance,JVM 只会生成一次对应的$ProxyN类,后续都会复用它。 - 这样可以避免重复生成字节码、节省内存开销。
- 如果多次用同一个接口数组、同一个 ClassLoader 调用
-
不同接口集合,各自生成
-
当传入新的接口列表(哪怕只有一个新接口),JVM 会为这组接口再生成一个代理类。
-
例如:
// 生成第一个代理类:只实现 HelloService HelloService p1 = (HelloService) Proxy.newProxyInstance( cl, new Class[]{HelloService.class}, handler); System.out.println(p1.getClass().getName()); // jdk.proxy1.$Proxy0 // 生成第二个代理类:只实现 ServiceB ServiceB p2 = (ServiceB) Proxy.newProxyInstance( cl, new Class[]{ServiceB.class}, handler); System.out.println(p2.getClass().getName()); // jdk.proxy1.$Proxy1 // 生成第三个代理类:同时实现 HelloService + ServiceB Object p3 = Proxy.newProxyInstance( cl, new Class[]{HelloService.class, ServiceB.class}, handler); System.out.println(p3.getClass().getName()); // jdk.proxy1.$Proxy2
-
-
因此,它可以一次“制造”任意多个代理类
- 并不是只能对一个类生成,而是对每种接口组合生成或复用一个代理类。
- 只要你给它不同的接口列表,它就会在内存(或落盘,如果你开启了
-D…ProxyGenerator.saveGeneratedFiles=true)生成相应的$ProxyX。
-
一个代理类也可以实现多接口
- 如上例的
p3,在一次调用里就生成了一个代理类,同时实现HelloService和ServiceB。 - 这个机制让动态代理能很灵活地“拼接”接口,而不是像静态代理那样,一个代理类只能对应一个接口。
- 如上例的
归根结底,JDK 动态代理就两种玩法
-
委托给真实实现
-
先写一个或一批接口实现类(
RealSubject、HelloServiceImpl……) -
在
InvocationHandler.invoke(...)里用Object result = method.invoke(subject, args);完成真正的业务调用,前后再插入日志、权限、事务等切面逻辑。
-
-
纯动态“写死”逻辑
-
根本不依赖任何实现类
-
在
invoke(...)里自己写所有业务:if ("sayHello".equals(method.getName())) { return "[Proxy] Hello, "+args[0]; }
-
JDK Proxy 本身不会帮助你生成或查找任何真实对象,它唯一做的事情是:
- 在运行时生成一个实现了接口的“壳”类(
$ProxyN), - 把每个接口方法的调用一律转给你提供的
InvocationHandler.invoke(...)。
所以,你要么
- 先有实现,再在
invoke里method.invoke(subject, …)委托给它;
要么 - 直接在
invoke里实现(或拼装)你想要的行为,省去真实类。
参考文献
学后检测
一、单项选择题(每题 4 分)
-
在静态代理模式中,代理类与被代理类必须具备的共同特征是?
A. 继承同一父类
B. 实现同一接口
C. 拥有完全相同的字段
D. 处于同一包答案:B
解析: 静态代理要求代理对象与目标对象对调用者表现一致,因此二者必须实现同一个接口以保持行为契约;并非强制继承同一父类或位于同一包。 -
JDK 动态代理运行时生成的
$Proxy0实际继承自哪一个类?
A.java.lang.Object
B. 目标对象的类
C.java.lang.reflect.Proxy
D.java.lang.ClassLoader答案:C
解析: 所有由ProxyGenerator生成的代理类都会继承java.lang.reflect.Proxy;Proxy再继承自Object。 -
在
InvocationHandler.invoke方法里直接调用method.invoke(proxy, args)最可能导致什么结果?
A. 方法正常执行
B.IllegalAccessException
C. 无限递归直至StackOverflowError
D. 返回null答案:C
解析:proxy本身还是代理对象,再次调用其方法会再次触发invoke,形成递归。 -
下列哪一项是 JDK 动态代理能够正常工作的前提?
A. 目标类必须是final
B. 目标类实现了至少一个接口
C. 目标类必须无参构造
D. 目标类必须位于默认包答案:B
解析: JDK 动态代理基于接口列表生成代理类;若目标类无接口,只能用 CGLIB 等字节码增强方案。 -
使用动态代理大幅降低为每个业务类重复编写日志、权限、事务代码的目的体现了哪种设计思想?
A. 单例模式
B. 观察者模式
C. 面向切面编程(AOP)/关注点分离
D. 责任链模式答案:C
解析: 动态代理让横切关注点(日志、事务等)与核心业务解耦,正是 AOP 思想的典型落地。
二、多项选择题(每题 4 分,多选或少选均不得分)
-
静态代理常见缺点包括哪些?
A. 运行时生成代理类开销大
B. 一个代理类只能服务一个接口
C. 需要手写大量重复代理代码
D. 系统类数量膨胀,增加维护成本答案:B C D
解析: 静态代理在编译期就确定,无法复用或拼接接口,且每个接口都要编写对应代理类,导致代码臃肿。运行时开销反而较小。 -
以下哪些场景特别适合用 JDK 动态代理实现?
A. 记录方法调用日志
B. 对服务方法统一添加事务边界
C. 基于注解进行权限校验
D. 序列化/反序列化对象答案:A B C
解析: 日志、事务、权限都属于横切关注点,可在invoke中统一处理;序列化不是代理的直接应用场景。
三、判断题(每题 2 分,正确打 √,错误打 ×)
- 静态代理一定比动态代理性能更高。 ×
解析: 动态代理在 HotSpot JIT 优化后调用开销可忽略不计;性能差异取决于具体场景,不存在“一定”更高。 - 多次以相同 ClassLoader 和完全相同接口数组调用
Proxy.newProxyInstance,JVM 只生成一个代理类并复用。 √
解析:Proxy内部使用缓存(key 为 ClassLoader + 接口列表)避免重复生成字节码。
四、简答题(共 10 分)
-
写出
Proxy.newProxyInstance的三个核心参数及各自作用。ClassLoader loader:指定生成的代理类应由哪个类加载器加载。Class<?>[] interfaces:新代理类需要实现的接口列表,决定了代理对象能暴露哪些方法。InvocationHandler h:方法调用的统一分发入口;所有接口方法都会回调到h.invoke(...)由开发者自定义处理逻辑。
答案要点: 三个参数名称与职责必须对应说明;阐述它们如何协同决定代理类的生成与行为。
五、实践题(共 20 分)
-
动手编码
-
定义接口
CalcService(含int add(int a,int b))。 -
编写实现类
CalcServiceImpl。 -
使用 JDK 动态代理创建一个代理对象,在每次调用
add前后打印参数与返回值。 -
在
main方法中调用add(5, 7),输出示例:>> entering add: a=5, b=7 << exiting add: result=12
-
1. 接口 CalcService
public interface CalcService {
int add(int a, int b);
}
2. 实现类 CalcServiceImpl
public class CalcServiceImpl implements CalcService {
@Override
public int add(int a, int b) {
return a + b;
}
}
3. 动态代理处理器 LogInvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LogInvocationHandler implements InvocationHandler {
/** 真正执行业务的对象 */
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
/** 统一分发所有方法调用 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置日志
if ("add".equals(method.getName()) && args != null && args.length == 2) {
System.out.printf(">> entering add: a=%s, b=%s%n", args[0], args[1]);
}
// 委托给真实对象
Object result = method.invoke(target, args);
// 后置日志
if ("add".equals(method.getName())) {
System.out.printf("<< exiting add: result=%s%n", result);
}
return result;
}
}
4. 测试入口 Main
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
// 1. 创建真实对象
CalcService real = new CalcServiceImpl();
// 2. 创建代理对象
CalcService proxy = (CalcService) Proxy.newProxyInstance(
CalcService.class.getClassLoader(),
new Class<?>[]{CalcService.class},
new LogInvocationHandler(real)
);
// 3. 调用业务方法,触发日志
int sum = proxy.add(5, 7);
System.out.println("sum = " + sum); // 这里额外打印一次,便于确认
}
}