口语化答案
好的,面试官。jdk 的动态代理主要是依赖Proxy类 和InvocationHandler 接口。jdk 动态代理要求类必须有接口。在进行实现的时候,首先要定义接口,比如MyService,这个接口就是我们的正常功能的实现。但是希望在不更改MyService 的情况下增加额外功能,那么我们需要定义一个实现InvocationHandler 接口的实现类,同时在方法实现上面增加额外的逻辑。最后通过 Proxy 的 newProxyInstance 将二者结合到一起。就实现了动态代理。
题目解析
大家不要觉得动态代理很难理解,按照这个步骤其实你发现很简单。记忆的过程和 cglib 对比着看,就很轻松,面试也是属于常考一点的题目。
面试得分点
InvocationHandler 增强,Proxy 创建代理
题目详细答案
JDK 动态代理主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。
实现步骤
定义接口:定义需要代理的接口。
实现接口:创建接口的实现类。
创建调用处理器:实现InvocationHandler接口,并在invoke方法中定义代理逻辑。
创建代理对象:通过Proxy.newProxyInstance方法创建代理对象。
代码 Demo
假设我们有一个简单的服务接口MyService和它的实现类MyServiceImpl,我们将通过 JDK 动态代理为MyService创建一个代理对象,并在方法调用前后添加日志。
1. 定义接口
public interface MyService {void performTask();
}
2. 实现接口
public class MyServiceImpl implements MyService {@Overridepublic void performTask() {System.out.println("Performing task");}
}
3. 创建调用处理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class LoggingInvocationHandler implements InvocationHandler {private final Object target;public LoggingInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Logging before method execution: " + method.getName());Object result = method.invoke(target, args);System.out.println("Logging after method execution: " + method.getName());return result;}
}
4. 创建代理对象并使用
import java.lang.reflect.Proxy;public class MainApp {public static void main(String[] args) {// 创建目标对象MyService myService = new MyServiceImpl();// 创建调用处理器LoggingInvocationHandler handler = new LoggingInvocationHandler(myService);// 创建代理对象MyService proxyInstance = (MyService) Proxy.newProxyInstance(myService.getClass().getClassLoader(),myService.getClass().getInterfaces(),handler);// 调用代理对象的方法proxyInstance.performTask();}
}
详细解释
1、 接口定义和实现:
MyService是一个简单的接口,定义了一个方法performTask。
MyServiceImpl是MyService的实现类,实现了performTask方法。
2、 调用处理器:
LoggingInvocationHandler实现了InvocationHandler接口。它的invoke方法在代理对象的方法调用时被调用。invoke方法接收三个参数:proxy:代理对象。method:被调用的方法。args:方法参数。在invoke方法中,我们在方法调用前后添加了日志打印。
3、 创建代理对象:
使用Proxy.newProxyInstance方法创建代理对象。
newProxyInstance方法接收三个参数:类加载器:通常使用目标对象的类加载器。接口数组:目标对象实现的所有接口。调用处理器:实现了InvocationHandler接口的实例。
4、 使用代理对象:
通过代理对象调用方法时,实际调用的是LoggingInvocationHandler的invoke方法。
在invoke方法中,首先打印日志,然后通过反射调用目标对象的方法,最后再打印日志。
JDK动态代理通俗详解
面试官您好,关于JDK动态代理,我用一个生活中的例子来帮助理解:
快递代收点类比
想象你网购了一件商品:
- 商家(MyServiceImpl):实际发货的人
- 快递代收点(Proxy):中间代理点
- 代收点规则(InvocationHandler):代收点提供的额外服务(比如验货、暂存)
实现步骤详解
1. 定义服务接口(购物清单)
// 就像网购时商家承诺的服务标准
public interface ShoppingService {void deliverItem(); // 送货服务String checkQuality(); // 验货服务
}
2. 实际商家实现(真实发货)
public class Amazon implements ShoppingService {@Overridepublic void deliverItem() {System.out.println("亚马逊发货中...");}@Overridepublic String checkQuality() {return "正品保障";}
}
3. 创建代收点规则(增值服务)
public class ProxyService implements InvocationHandler {private Object target; // 真实的商家对象public ProxyService(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 前置增强:代收点验货if(method.getName().equals("deliverItem")) {System.out.println("【代收点】快递消毒中...");}// 执行原方法(让商家正常发货)Object result = method.invoke(target, args);// 后置增强:签收服务if(method.getName().equals("deliverItem")) {System.out.println("【代收点】已签收,短信通知客户");}return result;}
}
4. 创建代收点(生成代理)
public class Client {public static void main(String[] args) {// 真实商家ShoppingService amazon = new Amazon();// 创建代理规则InvocationHandler handler = new ProxyService(amazon);// 建立代收点(生成代理实例)ShoppingService proxy = (ShoppingService) Proxy.newProxyInstance(amazon.getClass().getClassLoader(),amazon.getClass().getInterfaces(),handler);// 客户通过代收点购物proxy.deliverItem();System.out.println("验货结果:" + proxy.checkQuality());}
}
输出结果
【代收点】快递消毒中...
亚马逊发货中...
【代收点】已签收,短信通知客户
验货结果:正品保障
关键点说明
- 必须要有接口:就像必须通过电商平台下单,不能直接找路边摊
- InvocationHandler是核心:所有增强逻辑都在这里实现
- Proxy.newProxyInstance三要素:
- 类加载器:用原来的就行
- 接口数组:说明要代理哪些服务
- 处理规则:怎么增强这些服务
实际项目应用
在我们电商系统中:
- 支付服务接口用JDK代理添加日志
- 订单服务接口用JDK代理添加事务
- 商品服务接口用JDK代理做缓存
与CGLIB对比记忆
特性 | JDK动态代理 | CGLIB |
依赖 | 必须实现接口 | 不需要接口 |
原理 | 实现相同接口 | 继承目标类 |
性能 | 反射调用稍慢 | 直接调用更快 |
适用场景 | Spring默认对接口的代理 | 代理普通类 |
这样设计既保持了规范性(面向接口编程),又能灵活添加通用功能。