伙伴云客服论坛»论坛 S区 S软件开发 查看内容

0 评论

0 收藏

分享

Java动态代理四种实现方式详解

代理形式也是一种非常常见的设计形式。理解Spring框架的都晓得,Spring AOP 使用的就是动态代理形式。今天就来系统的重温一遍代理形式。
在现实生活中代理是随处可见的,当事人因某些隐私不方便出面,或者当事人不具备某些相关的专业技能,而需要一个职业人员来完成一些专业的操作, 也可能由于当事人没有时间处置事务,而聘用代理人出面。而在软件设计中,使用代理形式的地方也很多,由于安全原因,屏蔽客户端直接访问真实对象, 或者为了提升系统性能,使用代理形式实现延迟加载,还有就是AOP,对委托类的功能停止加强等。
一、代理形式的构造

代理形式的主要参与者有4个,如下图所示:
Java动态代理四种实现方式详解-1.png

角色作用
Subject主题接口,定义了代理类和委托类的公共对外方法,也是代理类代理委托类的方法
RealSubject委托类,真实主题,真正实现业务逻辑的类
Proxy代理类,代理和封装委托类
Client客户端,使用代理类和主题接口完成业务逻辑

loading="lazy" alt="" />角色作用Subject主题接口,定义了代理类和委托类的公共对外方法,也是代理类代理委托类的方法RealSubject委托类,真实主题,真正实现业务逻辑的类Proxy代理类,代理和封装委托类Client客户端,使用代理类和主题接口完成业务逻辑

二、代理形式的实现

代理形式一般分为静态代理和动态代理两种:
    静态代理,顾名思义,就是提早创建好代理类文件并在程序运行前已经编译成字节码。动态代理,是指在运行时动态生成代理类,即代理类的字节码将在运行时生成并载入到ClassLoader中。
理解了两种代理形式大约区别后,接下来就以一个短信发送功能加强的示例来详细论述两种代理的实现方式。
1、静态代理实现

第一步,定义主题接口,该接口只要一个send方法:
  1. public interface ISender {
  2.     public boolean send();
  3. }
复制代码
第二步,定义主题真正实现类:
  1. public class SmsSender implements ISender {
  2.     public boolean send() {
  3.         System.out.println("sending msg");
  4.         return true;
  5.     }
  6. }
复制代码
第三步,创建代理类,封装实现类:
  1. public class ProxySender implements ISender {
  2.     private ISender sender;
  3.     public ProxySender(ISender sender){
  4.         this.sender = sender;
  5.     }
  6.     public boolean send() {
  7.         System.out.println("处置前");
  8.         boolean result = sender.send();
  9.         System.out.println("处置后");
  10.         return result;
  11.     }
  12. }
复制代码
第四步,客户端调用:
  1. @Test
  2. public void testStaticProxy(){
  3.     ISender sender = new ProxySender(new SmsSender());
  4.     boolean result = sender.send();
  5.     System.out.println("输出结果:" + result);
  6. }
复制代码
以上就实现了一个简单的静态代理,很明显,静态代理需要为每个真实主题定义一个形式上完全一样的封装类,
假设真实主题方法有所修改,那代理类也需要跟着修改,不利于系统的维护。
2、动态代理实现

与静态代理相比,动态代理有更多优势,动态代理不只不需要定义代理类,甚至可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵敏性。
目前动态代理类的生成方法有很多,有JDK自带的动态代理、CGLIB、Javassist和ASM库等。
    JDK动态代理:内置在JDK中,不需要引入第三方jar,使用简单,但功能比较弱。CGLIB/Javassist:这两个都是高级的字节码生成库,总体性能比JDK动态代理好,且功能强大。ASM:低级字节码生成工具,近乎使用bytecode编码,对开发人员要求最高。当然性能也是最好(相比前几种也不是很大的提升,这里不做详细介绍)。
以下实例仍然以SmsSender和ISender作为被代理对象和接口停止试验。
1) JDK动态代理

JDK的动态代理需要实现一个处置方法调用的Handler,用于实现代理方法的内部逻辑,实现InvocationHandler接口。
  1. public class JdkProxyHandler implements InvocationHandler {
  2.     private Object target;
  3.     public JdkProxyHandler(Object target){
  4.         this.target = target;
  5.     }
  6.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  7.         System.out.println("处置前");
  8.         Object result = method.invoke(target,args);
  9.         System.out.println("处置后");
  10.         return result;
  11.     }
  12. }
复制代码
客户端调用:
  1. @Test
  2. public void testJdkProxy(){
  3.     ISender sender = (ISender) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
  4.             new Class[]{ISender.class},
  5.             new JdkProxyHandler(new SmsSender()));
  6.     boolean result = sender.send();
  7.     System.out.println("代理对象:" + sender.getClass().getName());
  8.     System.out.println("输出结果:" + result);
  9. }
复制代码
输出结果:
处置前
sending msg
处置后
代理对象:com.sun.proxy.$Proxy4
输出结果:true
这样实现一个简单的AOP就完成了,我们看到代理类的类型是com.sun.proxy.$Proxy4。那JDK是如何创建代理类?
首先从Proxy.newProxyInstance动手,来研究JDK是如何生成代理类:
  1. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
复制代码
该方法有3个参数:
    loader:用哪个类加载器去加载代理对象,生成目的对象的代理需要确保其类加载器相同,所以需要将目的对象的类加载器作为参数传送。interfaces:代理类需实现的接口列表,JDK动态代理技术需要代理类和目的对象都继承自同一接口,所以需要将目的对象的接口作为参数传送。h:调用处置器,调用实现了InvocationHandler类的一个回调方法,对目的对象的加强逻辑在这个实现类中。
详细代码如下:
  1. public static Object newProxyInstance(ClassLoader loader,
  2.                                           Class<?>[] interfaces,
  3.                                           InvocationHandler h) throws IllegalArgumentException {
  4.     //1.检查
  5.     Objects.requireNonNull(h);
  6.     final Class<?>[] intfs = interfaces.clone();
  7.     final SecurityManager sm = System.getSecurityManager();
  8.     if (sm != null) {
  9.         checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
  10.     }
  11.     /*
  12.      * Look up or generate the designated proxy class.
  13.      */
  14.     //获取代理类类型
  15.     Class<?> cl = getProxyClass0(loader, intfs);
  16.     /*
  17.      * Invoke its constructor with the designated invocation handler.
  18.      */
  19.     try {
  20.         if (sm != null) {
  21.             checkNewProxyPermission(Reflection.getCallerClass(), cl);
  22.         }
  23.         //通过反射创建代理对象
  24.         final Constructor<?> cons = cl.getConstructor(constructorParams);
  25.         final InvocationHandler ih = h;
  26.         if (!Modifier.isPublic(cl.getModifiers())) {
  27.             AccessController.doPrivileged(new PrivilegedAction<Void>() {
  28.                 public Void run() {
  29.                     cons.setAccessible(true);
  30.                     return null;
  31.                 }
  32.             });
  33.         }
  34.         return cons.newInstance(new Object[]{h});
  35.     } catch (IllegalAccessException|InstantiationException e) {
  36.         throw new InternalError(e.toString(), e);
  37.     } catch (InvocationTargetException e) {
  38.         Throwable t = e.getCause();
  39.         if (t instanceof RuntimeException) {
  40.             throw (RuntimeException) t;
  41.         } else {
  42.             throw new InternalError(t.toString(), t);
  43.         }
  44.     } catch (NoSuchMethodException e) {
  45.         throw new InternalError(e.toString(), e);
  46.     }
  47. }
复制代码
总结:详细代码细节就不在这里深究,但可以明显的看出,JDK的动态代理底层是通过Java反射机制实现的,并且需要目的对象继承自一个接口才干生成它的代理类。
2) CGLIB(Code Generation Library)动态代理

使用CGLIB动态代理前需要引入依赖:
  1. <dependency>
  2.     <groupId>cglib</groupId>
  3.     <artifactId>cglib</artifactId>
  4.     <version>3.3.0</version>
  5. </dependency>
复制代码
和JDK代理不同,CGLib动态代理技术不需要目的对象实现自一个接口,只需要实现一个处置代理逻辑的切入类,并实现MethodInterceptor接口。
定义真实主题实现类:
  1. public class BdSender {
  2.     public boolean send() {
  3.         System.out.println("sending msg");
  4.         return true;
  5.     }
  6. }
复制代码
代理类逻辑处置类:
  1. public class CglibProxyInterceptor implements MethodInterceptor {
  2.     private Enhancer enhancer = new Enhancer();
  3.     /**
  4.      * 获取代理类
  5.      * @param clazz
  6.      * @return
  7.      */
  8.     public Object getProxy(Class clazz) {
  9.         enhancer.setSuperclass(clazz);
  10.         enhancer.setCallback(this);
  11.         return enhancer.create();
  12.     }
  13.     public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  14.         System.out.println("处置前");
  15.         Object result = methodProxy.invokeSuper(object,args);
  16.         System.out.println("处置后");
  17.         return result;
  18.     }
  19. }
复制代码
客户端调用:
  1. @Test
  2. public void testCglibProxy(){
  3.     BdSender sender = (BdSender) new CglibProxyInterceptor().getProxy(BdSender.class);
  4.     boolean result = sender.send();
  5.     System.out.println("代理对象:" + sender.getClass().getName());
  6.     System.out.println("输出结果:" + result);
  7. }
复制代码
输出结果:
处置前
sending msg
处置后
代理对象:org.yd.proxy.BdSender$$EnhancerByCGLIB$$d65f9e34
输出结果:true

总结CgLib的特点:
    使用CGLib实现动态代理,完全不受代理类必需实现接口的限制CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高CGLib不能对声明为final的方法停止代理,因为CGLib原理是动态生成被代理类的子类
3)Javassist动态代理

Javassist是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。
相对于bcel, asm等这些工具,开发者不需要理解虚拟机指令,就能动态改变类的构造,或者动态生成类。
使用avassist动态代理前需要引入依赖:
  1. <dependency>
  2.     <groupId>org.javassist</groupId>
  3.     <artifactId>javassist</artifactId>
  4.     <version>3.27.0-GA</version>
  5. </dependency>
复制代码
使用Javassist生成动态代理可以有以下两种方式:
    代理工厂创建:需要实现MethodHandler用于代理逻辑处置,实现与CGLib非常类似动态代码创建:可通过Java代码生成字节码,这种方式创建的动态代理非常灵敏,甚至可以在运行时生成业务逻辑
代理工厂创建 — 代理逻辑处置类
  1. public class JavassistProxyHandler implements MethodHandler {
  2.     private ProxyFactory proxyFactory = new ProxyFactory();
  3.     /**
  4.      * 获取代理对象
  5.      * @param clazz 被代理类
  6.      * @return
  7.      * @throws Exception
  8.      */
  9.     public Object getProxy(Class clazz) throws Exception {
  10.         proxyFactory.setSuperclass(clazz);
  11.         Class<?> factoryClass = proxyFactory.createClass();
  12.         Object proxy = factoryClass.newInstance();
  13.         ((ProxyObject)proxy).setHandler(this);
  14.         return proxy;
  15.     }
  16.     public Object invoke(Object object, Method method, Method method1, Object[] args) throws Throwable {
  17.         System.out.println("处置前");
  18.         Object result = method1.invoke(object,args);
  19.         System.out.println("处置后");
  20.         return result;
  21.     }
  22. }
复制代码
客户端调用:
  1. @Test
  2. public void testJavassistProxy() throws Exception {
  3.     BdSender sender = (BdSender) new JavassistProxyHandler().getProxy(BdSender.class);
  4.     boolean result = sender.send();
  5.     System.out.println("代理对象:" + sender.getClass().getName());
  6.     System.out.println("输出结果:" + result);
  7. }
复制代码
输出结果
处置前
sending msg
处置后
代理对象:org.yd.proxy.BdSender_$$_jvstbce_0
输出结果:true
动态代码创建 — 代理逻辑处置类:
  1. public static Object getProxy(Class clazz) throws Exception {
  2.     ClassPool mPool = ClassPool.getDefault();
  3.     CtClass c0 = mPool.get(clazz.getName());
  4.     //定义代理类名称
  5.     CtClass mCtc = mPool.makeClass(clazz.getName() + "$$BytecodeProxy");
  6.     //添加父类继承
  7.     mCtc.setSuperclass(c0);
  8.     //添加类的字段信息
  9.     CtField field = new CtField(c0, "real", mCtc);
  10.     field.setModifiers(AccessFlag.PRIVATE);
  11.     mCtc.addField(field);
  12.     //添加构造函数
  13.     CtConstructor constructor = new CtConstructor(new CtClass[]{c0},mCtc);
  14.     constructor.setBody("{$0.real = $1;}"); // $0代表this, $1代表构造函数的第1个参数
  15.     mCtc.addConstructor(constructor);
  16.     //添加方法
  17.     CtMethod ctMethod = mCtc.getSuperclass().getDeclaredMethod("send");
  18.     CtMethod newMethod = new CtMethod(ctMethod.getReturnType(), ctMethod.getName(),ctMethod.getParameterTypes(), mCtc);
  19.     newMethod.setBody("{" +
  20.             "System.out.println("处置前");" +
  21.             "boolean result = $0.real.send();" +
  22.             "System.out.println("处置后");" +
  23.             "return result;}");
  24.     mCtc.addMethod(newMethod);
  25.     //生成动态类
  26.     return mCtc.toClass().getConstructor(clazz).newInstance(clazz.newInstance());
  27. }
复制代码
客户端调用:
  1. @Test
  2. public void testJavassisBytecodetProxy() throws Exception {
  3.     BdSender sender = (BdSender) JavassistDynamicCodeProxy.getProxy(BdSender.class);
  4.     boolean result = sender.send();
  5.     System.out.println("代理对象:" + sender.getClass().getName());
  6.     System.out.println("输出结果:" + result);
  7. }
复制代码
输出结果:
处置前
sending msg
处置后
代理对象:org.yd.proxy.BdSender$$BytecodeProxy
输出结果:true
Javassist被用于struts2和hibernate中,都用来做动态字节码修改使用。一般开发中不会用到,但在封装框架时比较有用。
以上介绍了静态代理和动态代理创建的几种方法与优缺点介绍,希望可以帮到大家。
到此这篇关于Java四种动态代理实现方式的文章就介绍到这了,更多相关Java动态代理内容请搜索网站以前的文章或继续阅读下面的相关文章希望大家以后多多支持网站!

回复

举报 使用道具

全部回复
暂无回帖,快来参与回复吧
本版积分规则 高级模式
B Color Image Link Quote Code Smilies

靓靓
注册会员
主题 18
回复 20
粉丝 0
|网站地图
快速回复 返回顶部 返回列表