Java动态代理之JDK动态代理和CGLib动态代理 面向切面编程AOP原理
本文由发表于4年前 | J2EE | 评论数 1 |  被围观 10,760 views+
静态代理动态代理JDK动态代理CGLib动态代理JDK动态代理和CGLib的比较Spring中的动态代理的使用关于Spring中选择代理类型的判断JDK动态代理原理浅析
静态代理

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

参考:设计模式笔记 – 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创建动态代理。

例子:

首先是两个测试类:

interface Animal{
    void sound();
}

class Dog implements Animal{
    public void sound(){
        System.out.println("wong~");
    }
}

接下来演示CGLib的使用:

public class CglibProxy implements MethodInterceptor{

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz){
        // 设置需要创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        // 通过字节码技术动态创建子类实例
        return enhancer.create();
    }

    // 拦截父类所有方法的调用
    @Override
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        System.out.println("begin...");
        // 通过代理类调用父类中的方法
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("end...");
        return null;
    }

    public static void main(String[] args){
        CglibProxy proxy = new CglibProxy();
        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动态代理的性能会比较低。

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

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

interface Animal{
    void sound();
}

class Dog implements Animal{
    public void sound(){
        System.out.println("wong~");
    }
}

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

public interface MyInvocationHandler { 
    public void invoke(Method m,Object... args) throws Exception; 
}

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

package com.itzhai.javanote.proxy;

import java.lang.reflect.Method;

public class AnimalHandler implements MyInvocationHandler {  

    private Object animal;  

    public AnimalHandler (Object animal){  
        this.animal = animal;  
    }

    public void invoke(Method m,Object[] args) throws Exception{  
        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;

public class MyProxy {  

    public static Object newProxyInstance(Class interfaces,MyInvocationHandler handler) throws Exception{  
        String r = "\n";

        Method[] methods = interfaces.getMethods();  

        StringBuffer sb = new StringBuffer("");  
        // 生成需要代理的接口中的所有方法 的代码,通过调用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 = new FileWriter(new File(dir+"Proxy$1.java"));  
        writer.write(src);  
        writer.flush();  
        writer.close();  
        // 编译动态代理类
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);  
        Iterable<? extends JavaFileObject> 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 = new URLClassLoader(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动态代理生成子类了。

除了文章中有特别说明,均为IT宅原创文章,转载请以链接形式注明出处。
本文链接: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
arthinking Java技术交流群:280755654,入门群:428693174 more
分享到:
 
2014 4/1
如果您有更好的原创技术博文或者观点,欢迎投稿:admin@itzhai.com,或者关注订阅左侧浮动面板的微信号订阅IT宅itread)发送消息。
文章评论
    一条评论
  1. 邱吉胜 2016年07月03日20:25:52  #-49楼 回复 回复

    无意见看到

给我留言

有人回复时邮件通知我
J2EE的相关文章
随机文章 本月热门 热评
1 JVM笔记 – 虚拟机执行子系统(虚拟机类加载机制) 2014/12/8
2 Java递归删除目录中的子目录和文件的方法 2011/4/12
3 Java笔记 – 泛型 泛型方法 泛型接口 擦除 边界 通配符(2) 2014/3/16
4 Javascript笔记汇总 | IT宅文章归档 AD 2012/05/23 2012/5/22
5 Hibernate继承映射策略之每棵类继承树一张表 2011/5/25
6 ExtJS中表格控件的使用,属性设置和数据的获取 2011/7/24
友情推荐 更多
破博客 文官洗碗安天下,武将打怪定乾坤。多么美好的年代,思之令人泪落。
Mr.5's Life 白天是一名程序员,晚上就是个有抱负的探索者
行知-追寻技术之美 关注大数据,分布式系统
我爱编程 编程成长轨迹
Cynthia's Blog 学习笔记 知识总结 思考感悟
 
猜您喜欢
欢迎关注我的公众号 IT宅
关于IT宅 文章归档

IT宅中的文章除了标题注明转载或有特别说明的文章,均为IT宅的技术知识总结,学习笔记或随笔。如果喜欢,请使用文章下面提供的分享组件。转载请注明出处并加入文章的原链接。 感谢大家的支持。

联系我们:admin@itzhai.com

Theme by arthinking. Copyright © 2011-2015 IT宅.com 保留所有权利.