• JDK动态代理和CGLIB动态代理


    转载自http://www.itzhai.com/java-dong-tai-dai-li-zhi-jdk-dong-tai-dai-li-he-cglib-dong-tai-dai-li-mian-xiang-qie-mian-bian-cheng-aop-yuan-li.html

    静态代理

    静态代理相对来说比较简单,无非就是聚合+多态:

    参考:设计模式笔记 – Proxy 代理模式 (Design Pattern)

    动态代理

    我们知道,通过使用代理,可以在被代理的类的方法的前后添加一些处理方法,这样就达到了类似AOP的效果。而JDK中提供的动态代理,就是实现AOP的绝好底层技术。

    JDK动态代理

    JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。

    Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

    例子:Java笔记 – 反射 动态代理

    CGLib动态代理

    还有一个叫CGLib的动态代理,CGLib全称为Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLib封装了asm,可以再运行期动态生成新的class。和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib创建动态代理。

    例子:

    首先是两个测试类:

    interfaceAnimal{void sound();}classDogimplementsAnimal{publicvoid sound(){System.out.println("wong~");}}

    接下来演示CGLib的使用:

    publicclassCglibProxyimplementsMethodInterceptor{privateEnhancer enhancer =newEnhancer();publicObject getProxy(Class clazz){// 设置需要创建子类的类
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);// 通过字节码技术动态创建子类实例return enhancer.create();}// 拦截父类所有方法的调用@OverridepublicObject intercept(Object obj,Method method,Object[] args,MethodProxy proxy)throwsThrowable{System.out.println("begin...");// 通过代理类调用父类中的方法Object result = proxy.invokeSuper(obj, args);System.out.println("end...");returnnull;}publicstaticvoid main(String[] args){CglibProxy proxy =newCglibProxy();Animal dog =(Animal)proxy.getProxy(Dog.class);
            dog.sound();}}

    CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。

    JDK动态代理和CGLib的比较

    CGLib所创建的动态代理对象的性能比JDK所创建的代理对象性能高不少,大概10倍,但CGLib在创建代理对象时所花费的时间却比JDK动态代理多大概8倍,所以对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建新的实例,所以比较适合CGLib动态代理技术,反之则适用于JDK动态代理技术。另外,由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final,private等方法进行处理。所以,大家需要根据实际的情况选择使用什么样的代理了。

    同样的,Spring的AOP编程中相关的ProxyFactory代理工厂内部就是使用JDK动态代理或CGLib动态代理的,通过动态代理,将增强(advice)应用到目标类中。

    Spring中的动态代理的使用

    Spring定义了org.springframework.aop.framework.AopProxy接口,并提供了如下两种final类型的实现类:

    20130401-Java-01

    关于Spring中选择代理类型的判断

    如果通过ProxyFactory的setInterfaces(Class[] interfaces)指定针对接口进行代理,ProxyFactory就使用JdkDynamicAopProxy,如果是针对类的代理,则使用Cglib2AopProxy。另外,如果使用ProxyFactory的setOptimize(true)方法,则启动了优化代理方式,这样针对接口的代理也会使用Cglib2AopProxy。

    在引介增强中就需要强制指定为Cglib2AopProxy,因为引介增强是一种比较特殊的增强类型,不是在目标周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点事类级别的。而非方法级别的。

    JDK动态代理原理浅析

    下面来探析下JDK动态代理的原理,看看为什么JDK动态代理的性能会比较低。

    动态代理的基本原理为反射 + 多态 + 聚合

    首先创建一个接口,和一个需要被代理的类:

    interfaceAnimal{void sound();}classDogimplementsAnimal{publicvoid sound(){System.out.println("wong~");}}

    接下来是创建一个MyInvocationHandler,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起:

    publicinterfaceMyInvocationHandler{publicvoid invoke(Method m,Object... args)throwsException;}

    接下来是创建一个MyInvocationHandler的实现类:

    package com.itzhai.javanote.proxy;import java.lang.reflect.Method;publicclassAnimalHandlerimplementsMyInvocationHandler{privateObject animal;publicAnimalHandler(Object animal){this.animal = animal;}publicvoid invoke(Method m,Object[] args)throwsException{System.out.println("before");  
            m.invoke(animal, args);System.out.println("after");}}

    接下里是创建代理类Proxy,里面包含一个newProxy方法,该方法生成需要代理的接口相关的所有方法,通过MyInvocationHandler的invoke方法,调用实际的对象,通过反射机制执行实际的方法,并且需要嵌入的代码也就在invoke方法里面被执行到了:

    下面是使用我们创建的动态代理的代码:

    package com.itzhai.javanote.proxy;import java.io.File;import java.io.FileWriter;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.net.URL;import java.net.URLClassLoader;import java.util.Arrays;import javax.tools.JavaCompiler;import javax.tools.JavaCompiler.CompilationTask;import javax.tools.JavaFileObject;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;publicclassMyProxy{publicstaticObject newProxyInstance(Class interfaces,MyInvocationHandler handler)throwsException{String r ="
    ";Method[] methods = interfaces.getMethods();StringBuffer sb =newStringBuffer("");// 生成需要代理的接口中的所有方法 的代码,通过调用MyInvocationHandler的invoke方法实现真实对象方法的调用for(int i =0;i<methods.length;i++){
                sb.append(" public void "+methods[i].getName()+"() {"+ r +"       try{ "+ r +"       Method md = "+interfaces.getName()+".class.getMethod(""+methods[i].getName()+"");"+ r +"       handler.invoke( md,new Object[]{});"+ r +"       }catch(Exception e){e.printStackTrace();}"+ r +"   }"+r + r
                );}// 生成完整的类代码String src ="package com.itzhai.javanote.proxy;"+ r + r +"import java.lang.reflect.*;"+ r + r +"public class Proxy$1 implements "+interfaces.getName()+"{"+ r +"   private com.itzhai.javanote.proxy.MyInvocationHandler handler;"+ r +"   public Proxy$1("+handler.getClass().getName()+" handler){"+ r +"       this.handler = handler;"+ r +"   }"+ r + r +  
    
                sb.toString()+"}"+r;// 输出Java文件String dir =System.getProperty("user.dir")+"/src/com/itzhai/javanote/proxy/";FileWriter writer =newFileWriter(newFile(dir+"Proxy$1.java"));  
            writer.write(src);  
            writer.flush();  
            writer.close();// 编译动态代理类JavaCompiler compiler =ToolProvider.getSystemJavaCompiler();StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null,null,null);Iterable<?extendsJavaFileObject> units = fileMgr.getJavaFileObjects(dir+"Proxy$1.java");Iterable<String> options =Arrays.asList("-d",System.getProperty("user.dir")+"/bin/");CompilationTask t = compiler.getTask(null, fileMgr,null, options,null, units);  
            t.call();  
            fileMgr.close();// 加载动态代理类,并返回动态代理类的实例
            URL[] urls =new URL[]{new URL("file:/"+dir)};URLClassLoader loader =newURLClassLoader(urls);Class c = loader.loadClass("com.itzhai.javanote.proxy.Proxy$1");Constructor ctr = c.getConstructor(handler.getClass());return ctr.newInstance(handler);}}

    分析可以得出,当我们通过MyProxy的newProxyInstance()方法返回代理对象时,实际上是通过传入的接口和MyInvocationHandler生成了一个代理类的字节码,并而代理类中的真实对象的方法是通过反射机制进行调用的(我们在实现invoke方法时都是通过反射的APIMethod的invoke调用具体的方法可知),这也是JDK动态代理在创建代理对象快而运行时却比较慢的原因。

    感兴趣的朋友可以去看一下JDK的动态代理实现的源代码(JDK中通过Proxy中的getProxyClass(loader, interfaces)方法生成代理代理对象)。

    如果要代理具体的业务类,需要使用CGLib动态代理生成子类了。

    静态代理

    静态代理相对来说比较简单,无非就是聚合+多态:

    参考:设计模式笔记 – Proxy 代理模式 (Design Pattern)

    动态代理

    我们知道,通过使用代理,可以在被代理的类的方法的前后添加一些处理方法,这样就达到了类似AOP的效果。而JDK中提供的动态代理,就是实现AOP的绝好底层技术。

    JDK动态代理

    JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。

    Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

    例子:Java笔记 – 反射 动态代理

    CGLib动态代理

    还有一个叫CGLib的动态代理,CGLib全称为Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLib封装了asm,可以再运行期动态生成新的class。和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib创建动态代理。

    例子:

    首先是两个测试类:

    interfaceAnimal{void sound();}classDogimplementsAnimal{publicvoid sound(){System.out.println("wong~");}}

    接下来演示CGLib的使用:

    publicclassCglibProxyimplementsMethodInterceptor{privateEnhancer enhancer =newEnhancer();publicObject getProxy(Class clazz){// 设置需要创建子类的类
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);// 通过字节码技术动态创建子类实例return enhancer.create();}// 拦截父类所有方法的调用@OverridepublicObject intercept(Object obj,Method method,Object[] args,MethodProxy proxy)throwsThrowable{System.out.println("begin...");// 通过代理类调用父类中的方法Object result = proxy.invokeSuper(obj, args);System.out.println("end...");returnnull;}publicstaticvoid main(String[] args){CglibProxy proxy =newCglibProxy();Animal dog =(Animal)proxy.getProxy(Dog.class);
            dog.sound();}}

    CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。

    JDK动态代理和CGLib的比较

    CGLib所创建的动态代理对象的性能比JDK所创建的代理对象性能高不少,大概10倍,但CGLib在创建代理对象时所花费的时间却比JDK动态代理多大概8倍,所以对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建新的实例,所以比较适合CGLib动态代理技术,反之则适用于JDK动态代理技术。另外,由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final,private等方法进行处理。所以,大家需要根据实际的情况选择使用什么样的代理了。

    同样的,Spring的AOP编程中相关的ProxyFactory代理工厂内部就是使用JDK动态代理或CGLib动态代理的,通过动态代理,将增强(advice)应用到目标类中。

    Spring中的动态代理的使用

    Spring定义了org.springframework.aop.framework.AopProxy接口,并提供了如下两种final类型的实现类:

    20130401-Java-01

    关于Spring中选择代理类型的判断

    如果通过ProxyFactory的setInterfaces(Class[] interfaces)指定针对接口进行代理,ProxyFactory就使用JdkDynamicAopProxy,如果是针对类的代理,则使用Cglib2AopProxy。另外,如果使用ProxyFactory的setOptimize(true)方法,则启动了优化代理方式,这样针对接口的代理也会使用Cglib2AopProxy。

    在引介增强中就需要强制指定为Cglib2AopProxy,因为引介增强是一种比较特殊的增强类型,不是在目标周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点事类级别的。而非方法级别的。

    JDK动态代理原理浅析

    下面来探析下JDK动态代理的原理,看看为什么JDK动态代理的性能会比较低。

    动态代理的基本原理为反射 + 多态 + 聚合

    首先创建一个接口,和一个需要被代理的类:

    interfaceAnimal{void sound();}classDogimplementsAnimal{publicvoid sound(){System.out.println("wong~");}}

    接下来是创建一个MyInvocationHandler,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起:

    publicinterfaceMyInvocationHandler{publicvoid invoke(Method m,Object... args)throwsException;}

    接下来是创建一个MyInvocationHandler的实现类:

    package com.itzhai.javanote.proxy;import java.lang.reflect.Method;publicclassAnimalHandlerimplementsMyInvocationHandler{privateObject animal;publicAnimalHandler(Object animal){this.animal = animal;}publicvoid invoke(Method m,Object[] args)throwsException{System.out.println("before");  
            m.invoke(animal, args);System.out.println("after");}}

    接下里是创建代理类Proxy,里面包含一个newProxy方法,该方法生成需要代理的接口相关的所有方法,通过MyInvocationHandler的invoke方法,调用实际的对象,通过反射机制执行实际的方法,并且需要嵌入的代码也就在invoke方法里面被执行到了:

    下面是使用我们创建的动态代理的代码:

    package com.itzhai.javanote.proxy;import java.io.File;import java.io.FileWriter;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.net.URL;import java.net.URLClassLoader;import java.util.Arrays;import javax.tools.JavaCompiler;import javax.tools.JavaCompiler.CompilationTask;import javax.tools.JavaFileObject;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;publicclassMyProxy{publicstaticObject newProxyInstance(Class interfaces,MyInvocationHandler handler)throwsException{String r ="
    ";Method[] methods = interfaces.getMethods();StringBuffer sb =newStringBuffer("");// 生成需要代理的接口中的所有方法 的代码,通过调用MyInvocationHandler的invoke方法实现真实对象方法的调用for(int i =0;i<methods.length;i++){
                sb.append(" public void "+methods[i].getName()+"() {"+ r +"       try{ "+ r +"       Method md = "+interfaces.getName()+".class.getMethod(""+methods[i].getName()+"");"+ r +"       handler.invoke( md,new Object[]{});"+ r +"       }catch(Exception e){e.printStackTrace();}"+ r +"   }"+r + r
                );}// 生成完整的类代码String src ="package com.itzhai.javanote.proxy;"+ r + r +"import java.lang.reflect.*;"+ r + r +"public class Proxy$1 implements "+interfaces.getName()+"{"+ r +"   private com.itzhai.javanote.proxy.MyInvocationHandler handler;"+ r +"   public Proxy$1("+handler.getClass().getName()+" handler){"+ r +"       this.handler = handler;"+ r +"   }"+ r + r +  
    
                sb.toString()+"}"+r;// 输出Java文件String dir =System.getProperty("user.dir")+"/src/com/itzhai/javanote/proxy/";FileWriter writer =newFileWriter(newFile(dir+"Proxy$1.java"));  
            writer.write(src);  
            writer.flush();  
            writer.close();// 编译动态代理类JavaCompiler compiler =ToolProvider.getSystemJavaCompiler();StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null,null,null);Iterable<?extendsJavaFileObject> units = fileMgr.getJavaFileObjects(dir+"Proxy$1.java");Iterable<String> options =Arrays.asList("-d",System.getProperty("user.dir")+"/bin/");CompilationTask t = compiler.getTask(null, fileMgr,null, options,null, units);  
            t.call();  
            fileMgr.close();// 加载动态代理类,并返回动态代理类的实例
            URL[] urls =new URL[]{new URL("file:/"+dir)};URLClassLoader loader =newURLClassLoader(urls);Class c = loader.loadClass("com.itzhai.javanote.proxy.Proxy$1");Constructor ctr = c.getConstructor(handler.getClass());return ctr.newInstance(handler);}}

    分析可以得出,当我们通过MyProxy的newProxyInstance()方法返回代理对象时,实际上是通过传入的接口和MyInvocationHandler生成了一个代理类的字节码,并而代理类中的真实对象的方法是通过反射机制进行调用的(我们在实现invoke方法时都是通过反射的APIMethod的invoke调用具体的方法可知),这也是JDK动态代理在创建代理对象快而运行时却比较慢的原因。

    感兴趣的朋友可以去看一下JDK的动态代理实现的源代码(JDK中通过Proxy中的getProxyClass(loader, interfaces)方法生成代理代理对象)。

    如果要代理具体的业务类,需要使用CGLib动态代理生成子类了。

  • 相关阅读:
    c# 泛型总结
    透过字节码分析java基本类型数组的内存分配方式。
    c#索引器
    redis在asp.net 中的应用
    Unity3D shaderLab
    Unity3d Asset Store 打不开
    C# 类型转换的开销
    [转]权重算法
    Coroutine的原理以及实现
    在Unity3D里使用WinForm
  • 原文地址:https://www.cnblogs.com/heyanan/p/6071052.html
Copyright © 2020-2023  润新知