Java 动态代理-Jdk动态代理

╰半橙微兮° 2022-04-23 06:28 543阅读 0赞

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 动态代理开发步骤

  1. 自定义动态代理处理器InvokeHandler: 当调用被代理对象的任何方法时, 都会替换成调用此类的invoke 方法. 在invoke 方法中可以控制被调用方法前后的操作
  2. 创建动态代理工厂: 通常会为动态代理定义一个代理工厂, 方法生成动态代理
  3. 转换代理对象为动态代理对象接口类型: 将生成的动态代理对象类型强转为被代理对象接口的类型, 不能转换为实现类的类型, 这样才能调用被代理对象的方法.

3. 动态代理测试

笔者创建一计算器类, 并提供两个方法, 目标不侵入计算器类代码, 实现执行每个计算方法前后都输出日志.

3.1 定义被代理对象, 需要实现接口

普通业务组件类, 只需要实现接口即可.

  1. public interface ICalculator <T> {
  2. public T plus(T t1, T t2);
  3. public T minus(T t1, T t2);
  4. }
  5. public class IntCalculator implements ICalculator<Integer>{
  6. private void sleep() {
  7. int seconds = new Random().nextInt(10) * 1000;
  8. try {
  9. Thread.sleep(seconds);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. @Override
  15. public Integer plus(Integer t1, Integer t2) {
  16. // 模拟方法执行耗时
  17. sleep();
  18. int result = t1 + t2;
  19. System.out.printf("%d + %d = %d\n", t1, t2, result);
  20. return result;
  21. }
  22. @Override
  23. public Integer minus(Integer t1, Integer t2) {
  24. // 模拟方法执行耗时
  25. sleep();
  26. int result = t1 - t2;
  27. System.out.printf("%d - %d = %d\n", t1, t2, result);
  28. return result;
  29. }
  30. }

3.2 定义日志类

目标方法执行前后输出日志.

  1. public class CalculatorLogUtil {
  2. // 调用方法前打印日志
  3. public static void before(String method, Object... args){
  4. System.out.printf("\n[%s] method:%s, params:%s --start\n", LocalDateTime.now(), method, Arrays.asList(args));
  5. }
  6. // 调用方法后打印日志
  7. public static void after(String method, Object... args){
  8. System.out.printf("[%s] method:%s, params:%s --ended\n", LocalDateTime.now(), method, Arrays.asList(args));
  9. }
  10. }

3.3 自定义动态代理处理器

动态代理的核心, 可以在invoke 中对调用目标方法前后做处理工作

  1. public class CalculatorHandler implements InvocationHandler {
  2. // 被代理对象
  3. private Object target;
  4. public CalculatorHandler(Object target) {
  5. this.target = target;
  6. }
  7. @Override
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  9. // 调用方法前处理
  10. CalculatorLogUtil.before(method.getName(), args);
  11. // 调用方法
  12. Object result = method.invoke(target, args);
  13. // 调用方法后处理
  14. CalculatorLogUtil.after(method.getName(), args);
  15. return result;
  16. }
  17. }

3.4 自定义动态代理工厂

动态代理工厂用于生产动态代理对象

  1. public class CalculatorProxyFactory {
  2. /** * 获取目标对象代理 * @param target 目标对象 * @return 代理对象 */
  3. public static Object getProxy(Object target) {
  4. // 创建代理处理器
  5. CalculatorHandler calculatorProxy = new CalculatorHandler(target);
  6. // 创建jdk 代理对象
  7. Object proxyObject = Proxy.newProxyInstance(target.getClass().getClassLoader(),
  8. target.getClass().getInterfaces(), calculatorProxy);
  9. return proxyObject;
  10. }
  11. }

3.5 测试

需要注意的是, 必须将动态代理对象转换为被代理对象的接口类型, 不能转换为实现类类型IntCalculator, 否则会抛出类型转换异常.

  1. @Test
  2. public void test_JdkProxy(){
  3. // 创建被代理目标对象
  4. ICalculator<Integer> calculator = new IntCalculator();
  5. // 通过动态代理工厂获取动态代理
  6. Object proxy = CalculatorProxyFactory.getProxy(calculator);
  7. // 将动态代理强制转换为被代理目标对象接口类型, 否则Object 类型对象没有目标对象的相关方法
  8. ICalculator<Integer> calculatorProxy = (ICalculator) proxy;
  9. // 通过动态代理对象执行方法, 其实执行的是CalculatorHandler的invoke方法
  10. calculatorProxy.plus(5, 3);
  11. calculatorProxy.minus(5, 3);
  12. }

3.6 测试输出

  1. [2019-01-24T15:59:11.342] method:plus, params:[5, 3] --start
  2. 5 + 3 = 8
  3. [2019-01-24T15:59:18.344] method:plus, params:[5, 3] --ended
  4. [2019-01-24T15:59:18.345] method:minus, params:[5, 3] --start
  5. 5 - 3 = 2
  6. [2019-01-24T15:59:24.348] method:minus, params:[5, 3] --ended

总结:

  • 动态代理的核心就是InvocationHandler中的invoke方法, 所以当看到动态代理时, 直接找其对应的InvocationHandler即可
  • Jdk 动态代理的方法必须实现了接口, 且是重写接口的方法才行
  • AOP(切面编程)得以实现就是依赖于动态代理
  • Spring 的AOP 会根据目标类是否实现了接口自动选择使用Jdk 动态代理还是Cglib动态代理

发表评论

表情:
评论列表 (有 0 条评论,543人围观)

还没有评论,来说两句吧...

相关阅读

    相关 代理-jdk动态代理

    1、基于接口的实现,要jdk动态代理的类必须要实现一个接口; 2、中介类:实现了InvocationHandler,并重写这个接口的 方法(public Object inv