学习笔记-Java动态代理的简单使用

打印 上一主题 下一主题

主题 864|帖子 864|积分 2592

代理模式


  • 一种设计模式
  • 简单地说,在代理模式中存在三个角色

    • 用户
    • 代理
    • 被代理的对象

  • 用户调用代理,代理去调用被代理的对象
  • 以此来实现功能的增强
  • 动态代理在java中有两种实现方法

    • JDK中的Proxy类
    • CGLIB

JDK中的Proxy类

步骤


  • 实现InvocationHandler接口,创建自己的调用处理器
  • 通过为Proxy类指定ClassLoader和一组Interface来创建动态代理类

    • 被代理对象的ClassLoader和Interface

  • 通过反射机制获取动态代理类的构造函数

    • 其需要的唯一参数类型是InvocationHandler

  • 通过构造函数创建动态代理实例

    • 构造时将之前实现的InvocationHandler对象作为参数传入

这四步之后,我们就可以用使用被代理对象的方式,来使用动态代理实例了
另外

  • 后三步可以自己手动调用Proxy类的方法来分别实现
  • 也可以直接调用Proxy封装好的方法来一步实现

    • Proxy.newProxyInstance(ClassLoader, Interface[], InvocationHandler)

Demo
  1. package cn.andl;
  2. import cn.andl.util.Computer;
  3. import cn.andl.util.impl.ComputerImpl;
  4. import java.lang.reflect.InvocationHandler;
  5. import java.lang.reflect.Method;
  6. import java.lang.reflect.Proxy;
  7. /**
  8. * 测试JDK动态代理方式
  9. * @author Andl
  10. * @since 2023/5/29 11:17
  11. */
  12. public class TestProxy {
  13.     public static void main(String[] args) {
  14.         // 创建被代理对象实例
  15.         ComputerImpl computer = new ComputerImpl();
  16.         // 实现一个调用处理器
  17.         InvocationHandler invocationHandler = new InvocationHandler() {
  18.             /**
  19.              * 在之后的代理类调用方法时,会实际调用这个方法
  20.              *
  21.              * @param proxy 代理
  22.              *
  23.              * @param method 要被代理的方法
  24.              *
  25.              * @param args 方法里的参数列表
  26.              *
  27.              * @return 方法的返回值
  28.              * @throws Throwable
  29.              */
  30.             @Override
  31.             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  32.                 System.out.format("[%s] 执行 %s 方法, 参数1:%d, 参数2:%d\n",
  33.                         this.getClass().getName(), method.getName(), (Integer)args[0], (Integer)args[1]);
  34.                 // 调用被代理对象的方法,并获取返回值
  35.                 Object result = method.invoke(computer, args);
  36.                 System.out.format("[%s] 执行 %s 方法完毕, 结果:%d\n",
  37.                         this.getClass().getName(), method.getName(), (Integer)result);
  38.                 return result;
  39.             }
  40.         };
  41.         //获取代理对象
  42.         Computer computerProxy = (Computer) Proxy.newProxyInstance(
  43.                 // 被代理对象的类加载器
  44.                 computer.getClass().getClassLoader(),
  45.                 // 被代理对象实现的接口
  46.                 computer.getClass().getInterfaces(),
  47.                 // 调用处理器
  48.                 invocationHandler);
  49.         // 执行方法
  50.         computerProxy.add(1, 2);
  51.     }
  52. }
复制代码
Computer接口
  1. package cn.andl.util;
  2. /**
  3. * 计算接口
  4. * @author Andl
  5. * @create 2023/5/29 11:18
  6. */
  7. public interface Computer {
  8.     /**
  9.      * 计算a和b的和
  10.      * @param a 加数1
  11.      * @param b 加数2
  12.      * @return 和
  13.      */
  14.     int add(int a, int b);
  15. }
复制代码
ComputerImpl类
  1. package cn.andl.util.impl;
  2. import cn.andl.util.Computer;
  3. /**
  4. * 计算接口实现类
  5. * @author Andl
  6. * @since 2023/5/29 11:23
  7. */
  8. public class ComputerImpl implements Computer {
  9.     @Override
  10.     public int add(int a, int b) {
  11.         System.out.format("[%s] 方法执行中\n", this.getClass().getName());
  12.         return a + b;
  13.     }
  14. }
复制代码
输出结果
  1. [cn.andl.TestProxy$1] 执行 add 方法, 参数1:1, 参数2:2
  2. [cn.andl.util.impl.ComputerImpl] 方法执行中
  3. [cn.andl.TestProxy$1] 执行 add 方法完毕, 结果:3:3
复制代码
Demo2

简单封装一下
  1. package cn.andl;
  2. import cn.andl.util.Computer;
  3. import cn.andl.util.impl.ComputerImpl;
  4. import java.lang.reflect.InvocationHandler;
  5. import java.lang.reflect.Method;
  6. import java.lang.reflect.Proxy;
  7. /**
  8. * 测试JDK动态代理2
  9. * @author Andl
  10. * @since 2023/5/29 13:19
  11. */
  12. public class TestProxy2 {
  13.     static class InvocationHandlerImpl implements InvocationHandler {
  14.         Object originalObject;
  15.         public Object bind(Object originalObject) {
  16.             this.originalObject = originalObject;
  17.             return Proxy.newProxyInstance(
  18.                     originalObject.getClass().getClassLoader(),
  19.                     originalObject.getClass().getInterfaces(),
  20.                     this);
  21.         }
  22.         @Override
  23.         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  24.             System.out.format("[%s] 执行 %s 方法, 参数1:%d, 参数2:%d\n",
  25.                     this.getClass().getName(), method.getName(), (Integer)args[0], (Integer)args[1]);
  26.             Object result = method.invoke(originalObject, args);
  27.             System.out.format("[%s] 执行 %s 方法完毕, 结果:%d\n",
  28.                     this.getClass().getName(), method.getName(), (Integer)result);
  29.             return result;
  30.         }
  31.     }
  32.     public static void main(String[] args) {
  33.         // 获取代理
  34.         Computer computer = (Computer) new InvocationHandlerImpl().bind(new ComputerImpl());
  35.         // 执行方法
  36.         computer.add(1, 2);
  37.     }
  38. }
复制代码
输出结果
  1. [cn.andl.TestProxy2$InvocationHandlerImpl] 执行 add 方法, 参数1:1, 参数2:2
  2. [cn.andl.util.impl.ComputerImpl] 方法执行中
  3. [cn.andl.TestProxy2$InvocationHandlerImpl] 执行 add 方法完毕, 结果:3
复制代码
原理简述

通过在main方法中最开始时加入一句代码,我们可以保留动态代理对象的字节码文件

  • System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
  • 可以在和src同级的com文件夹下的sun/proxy/中找到
类名

public final class $Proxy0 extends Proxy implements Computer {
观察类名可以发现,动态代理类继承了Proxy方法,实现了Computer接口
静态代码块
  1.     static {
  2.         try {
  3.             m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
  4.             m2 = Class.forName("java.lang.Object").getMethod("toString");
  5.             m3 = Class.forName("cn.andl.util.Computer").getMethod("add", Integer.TYPE, Integer.TYPE);
  6.             m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  7.         } catch (NoSuchMethodException var2) {
  8.             throw new NoSuchMethodError(var2.getMessage());
  9.         } catch (ClassNotFoundException var3) {
  10.             throw new NoClassDefFoundError(var3.getMessage());
  11.         }
  12.     }
复制代码
观察静态代码块可以发现,被代理对象的方法被赋值到了变量中
add方法
  1.     public final int add(int var1, int var2) throws  {
  2.         try {
  3.             return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
  4.         } catch (RuntimeException | Error var4) {
  5.             throw var4;
  6.         } catch (Throwable var5) {
  7.             throw new UndeclaredThrowableException(var5);
  8.         }
  9.     }
复制代码
观察动态代理类中的add方法可以发现

  • 是通过调用父类中变量h的invoke方法来实现功能的
  • 而这个h,就是我们在之前创建动态代理类时,向构造器传入的InvocationHandler
CGLIB


  • cglib是一个功能强大、高性能、高质量的字节码操作库
  • 主要用于在运行时拓展Java类或者根据接口生成对象
  • 本身的实现基于asm库
  • 要使用cglib主要会用到Enhancer和回调类
Enhancer


  • Enhancer是cglib中使用最多的类
  • Enhancer可以生成被代理类的子类,并且会拦截所有方法的调用

    • 称之为增强

  • Enhancer可以基于接口来生成动态代理类,也可以直接基于类生成动态代理类
  • Enhancer不能增强构造函数,也不能增强被final修饰的类,或者被static和final修饰的方法

    • 因为Enhancer是通过继承被代理的目标类来是实现增强的

  • Enhancer的使用分成两步

    • 传入目标类型
    • 设置回调

MethodInterceptor

cglib中回调类型有很多,这里主要介绍方法拦截器MethodInterceptor

  • 方法拦截器会对被代理的目标类中所有可以增强的方法进行增强

    • 不包括构造方法、final方法和static方法

  • 方法拦截器的核心方法public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

    • o

      • 被代理的目标对象

    • method

      • 被代理的目标方法

    • objects

      • 参数列表

    • methodProxy

      • 代理类的方法引用


Demo
  1. public class TestCGLIB {
  2.     public static void main(String[] args) {
  3.         // 初始化enhancer对象
  4.         Enhancer enhancer = new Enhancer();
  5.         // 传入目标类型
  6.         enhancer.setSuperclass(ComputerImpl.class);
  7.         // 也可以传入接口
  8. //        enhancer.setInterfaces(ComputerImpl.class.getInterfaces());
  9.         // 设置回调类型
  10.         enhancer.setCallback(new MethodInterceptor() {
  11.             /**
  12.              * 拦截方法
  13.              * @param o 目标对象
  14.              * @param method 目标方法
  15.              * @param objects 参数列表
  16.              * @param methodProxy 代理方法
  17.              */
  18.             @Override
  19.             public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  20.                 System.out.format("[%s] 执行方法 [%s] 参数:[%d][%d]\n",
  21.                         this.getClass().getName(), method.getName(), (Integer)objects[0], (Integer)objects[1]);
  22.                 Object result = methodProxy.invokeSuper(o, objects);
  23.                 System.out.format("[%s] 执行方法 [%s] 结果:[%d]\n",
  24.                         this.getClass().getName(), method.getName(), (Integer)result);
  25.                 return result;
  26.             }
  27.         });
  28.         // 创建代理对象
  29.         ComputerImpl computer = (ComputerImpl)enhancer.create();
  30.         // 执行方法
  31.         computer.add(1, 2);
  32.     }
  33. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

雁过留声

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表