版权声明:转载请注明出处! https://blog.csdn.net/zongf0504/article/details/86629034
java 在java.lang.reflect 包下提供了一个Proxy 和 InvocationHandler 接口来支持生产Jdk 动态代理类(不常用)或动态代理对象. 在普通的企业开发中, 通常是不会涉及到动态代理开发的, 但是若是开发框架或做底层封装时就需要涉及到动态代理了, 如spring 的AOP 就是使用动态代理来实现的. 若想使用Jdk 动态代理, 则被代理类必须实现了接口, 未实现任何接口的类不能使用jdk 动态代理方式生产动态代理.
1. 创建动态代理
jdk 动态代理的核心是InvocationHandler, 因为调用被动态代理对象的方法实则调用的是InvocationHandler 的invoke 方法. Proxy类 提供了两个静态方法分别用于生成动态代理类和动态代理对象:
方法签名 | 方法描述 |
---|---|
tatic Class<?> getProxyClass(ClassLoader loader, Class<?>…interfaces) | 创建一个动态代理类所对应的Class 对象, 该代理类将实现interfaces 指定的所有接口. 在创建实例时需要传入InvocationHandler 对象, 所以这种方式并不常用 |
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) | 创建一个动态代理对象, 该代理对象实现了interfaces 指定的所有接口, 当执行代理对象的每一个方法时, 都会被替换成 invocationHandler 的invoke 方法 |
2. jdk 动态代理开发步骤
- 自定义动态代理处理器InvokeHandler: 当调用被代理对象的任何方法时, 都会替换成调用此类的invoke 方法. 在invoke 方法中可以控制被调用方法前后的操作
- 创建动态代理工厂: 通常会为动态代理定义一个代理工厂, 方法生成动态代理
- 转换代理对象为动态代理对象接口类型: 将生成的动态代理对象类型强转为被代理对象接口的类型, 不能转换为实现类的类型, 这样才能调用被代理对象的方法.
3. 动态代理测试
笔者创建一计算器类, 并提供两个方法, 目标不侵入计算器类代码, 实现执行每个计算方法前后都输出日志.
3.1 定义被代理对象, 需要实现接口
普通业务组件类, 只需要实现接口即可.
public interface ICalculator <T> {
public T plus(T t1, T t2);
public T minus(T t1, T t2);
}
public class IntCalculator implements ICalculator<Integer>{
private void sleep() {
int seconds = new Random().nextInt(10) * 1000;
try {
Thread.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public Integer plus(Integer t1, Integer t2) {
// 模拟方法执行耗时
sleep();
int result = t1 + t2;
System.out.printf("%d + %d = %d\n", t1, t2, result);
return result;
}
@Override
public Integer minus(Integer t1, Integer t2) {
// 模拟方法执行耗时
sleep();
int result = t1 - t2;
System.out.printf("%d - %d = %d\n", t1, t2, result);
return result;
}
}
3.2 定义日志类
目标方法执行前后输出日志.
public class CalculatorLogUtil {
// 调用方法前打印日志
public static void before(String method, Object... args){
System.out.printf("\n[%s] method:%s, params:%s --start\n", LocalDateTime.now(), method, Arrays.asList(args));
}
// 调用方法后打印日志
public static void after(String method, Object... args){
System.out.printf("[%s] method:%s, params:%s --ended\n", LocalDateTime.now(), method, Arrays.asList(args));
}
}
3.3 自定义动态代理处理器
动态代理的核心, 可以在invoke 中对调用目标方法前后做处理工作
public class CalculatorHandler implements InvocationHandler {
// 被代理对象
private Object target;
public CalculatorHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用方法前处理
CalculatorLogUtil.before(method.getName(), args);
// 调用方法
Object result = method.invoke(target, args);
// 调用方法后处理
CalculatorLogUtil.after(method.getName(), args);
return result;
}
}
3.4 自定义动态代理工厂
动态代理工厂用于生产动态代理对象
public class CalculatorProxyFactory {
/**
* 获取目标对象代理
* @param target 目标对象
* @return 代理对象
*/
public static Object getProxy(Object target) {
// 创建代理处理器
CalculatorHandler calculatorProxy = new CalculatorHandler(target);
// 创建jdk 代理对象
Object proxyObject = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), calculatorProxy);
return proxyObject;
}
}
3.5 测试
需要注意的是, 必须将动态代理对象转换为被代理对象的接口类型, 不能转换为实现类类型IntCalculator, 否则会抛出类型转换异常.
@Test
public void test_JdkProxy(){
// 创建被代理目标对象
ICalculator<Integer> calculator = new IntCalculator();
// 通过动态代理工厂获取动态代理
Object proxy = CalculatorProxyFactory.getProxy(calculator);
// 将动态代理强制转换为被代理目标对象接口类型, 否则Object 类型对象没有目标对象的相关方法
ICalculator<Integer> calculatorProxy = (ICalculator) proxy;
// 通过动态代理对象执行方法, 其实执行的是CalculatorHandler的invoke方法
calculatorProxy.plus(5, 3);
calculatorProxy.minus(5, 3);
}
3.6 测试输出
[2019-01-24T15:59:11.342] method:plus, params:[5, 3] --start
5 + 3 = 8
[2019-01-24T15:59:18.344] method:plus, params:[5, 3] --ended
[2019-01-24T15:59:18.345] method:minus, params:[5, 3] --start
5 - 3 = 2
[2019-01-24T15:59:24.348] method:minus, params:[5, 3] --ended
总结:
- 动态代理的核心就是InvocationHandler中的invoke方法, 所以当看到动态代理时, 直接找其对应的InvocationHandler即可
- Jdk 动态代理的方法必须实现了接口, 且是重写接口的方法才行
- AOP(切面编程)得以实现就是依赖于动态代理
- Spring 的AOP 会根据目标类是否实现了接口自动选择使用Jdk 动态代理还是Cglib动态代理