Java动态代理之JDK动态代理和CGLib动态代理 面向切面编程AOP原理

静态代理

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

参考:设计模式笔记 – 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动态代理生成子类了。

arthinking wechat
欢迎关注itzhai公众号