JDK静态代理、动态代理和CGLIB动态代理

参考资料

代理

代理模式(Proxy Pattern)给某一个对象提供一个代理,并由代理对象控制原对象的引用。代理对象在客户端和目标对象之间起到中介作用。

代理模式结构

代理模式优缺点

优点

  1. 代理模式能将代理对象与真实被调用的目标对象分离
  2. 一定程度上降低了系统的耦合度,扩展性好
  3. 可以起到保护目标对象的作用。
  4. 可以对目标对象的功能增强

缺点

  1. 代理模式会造成系统设计中类的数量增加
  2. 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢

代理模式分类

代理模式可以分为静态代理和动态代理两种类型,而动态代理中又分为 JDK 动态代理和 CGLIB 代理两种。

  1. 静态代理:代码编译时就已实现,编译完成后,代理类是一个实际的 class 文件
  2. 动态代理:在运行时动态生成的,编译完成后没有实际的 class 文件,而是在运行时动态生成类字节码,并加载到 JVM 中
    • JDK 代理
    • CGLIB 代理

3种代理方式的对比

代理方式 实现 优点 缺点 特点
JDK 静态代理 代理类与委托类实现同一接口。在代理类中需要硬编码接口 实现简单,容易理解 代理类需要硬编码接口,在实际应用中可能会导致重复编码,浪费存储空间并且效率较低
JDK 动态代理 代理类与委托类实现同一接口。代理类要实现 InvocationHandler 接口并重写 invoke 方法。invoke 方法中可以对委托方法进行增强处理 不需要硬编码接口,代码复用率高 只能够代理实现了接口的委托类 底层使用反射机制进行方法的调用
CGLIB 动态代理 代理类将委托类作为自己的父类并为其中的非 final 委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过 super 调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor 接口的对象,若存在则将调用 intercept 方法对委托方法进行代理 可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口 不能对 final 类以及 final 方法进行代理 底层将方法全部存入一个数组中,通过数组索引直接进行方法调用

静态代理和动态代理的对比

  • 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则。
  • 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
  • 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。

CGLIB 动态代理比 JDK 动态代理速度快?

  • CGLIB 动态代理中,通过字节码技术(使用了字节码处理框架 ASM)为一个类创建子类,并在子类中通过 FastClass 机制,采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
  • JDK 动态代理中,通过反射技术实现对委托方法的调用。
  • 在 JDK 6 之前,CGLIB 动态代理比使用 Java 反射效率要高
  • 在 JDK 6、7、8 版本中,逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率高于 CGLIB 代理效率。
  • 从 JDK 8 版本开始,每一次 JDK 版本升级,JDK 代理效率都得到较大提升,效率已经高于 CGLIB 代理。

Spring AOP 中是使用的 JDK 代理还是 CGLIB 代理?

  • 当 Bean 实现接口时,Spring 就会用 JDK 的动态代理。
  • 当 Bean 没有实现接口时,Spring 使用 CGLIB 实现。
  • 若两种方式均可,则首选 JDK 做动态代理(效率更高)。不过也可以强制使用 CGLIB。

静态代理

结合「代理模式结构」章节的结构图,对静态代理的实现进行说明

  1. 对「服务接口」,创建一个「服务类」实现该接口。
  2. 同时创建一个「代理类」也实现该接口,代理类中持有一个指向「服务对象」的引用成员变量。通常情况下,「代理类」会对其「服务对象」的整个生命周期进行管理。
  3. 「客户端」通过「代理类」来调用「服务对象」实现的接口方法。
//1.「服务接口」
public interface UserDao {    
  void save();     
}

//2.「服务类」
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("正在保存用户...");
    }
}

//3.「代理类」
public class TransactionHandler implements UserDao {
    // 目标代理对象
    // 2.「服务对象」
    private UserDao target;

    //构造代理对象时传入目标对象
    public TransactionHandler(UserDao target) {
        this.target = target;  //也可以延迟初始化 如在调用save方法时才创建target对象
    }
    
    @Override
    public void save() {
        //调用目标方法前的处理
        System.out.println("开启事务控制...");
        //调用目标对象的方法
        target.save();
        //调用目标方法后的处理
        System.out.println("关闭事务控制...");
    }
}

// 4. 「客户端」
public class Main {
    public static void main(String[] args) {
        //新建目标对象
        UserDaoImpl target = new UserDaoImpl();

        //创建代理对象, 并使用接口对其进行引用
        UserDao userDao = new TransactionHandler(target);
        
        //针对接口进行调用
        userDao.save();
    }
}
复制代码

静态代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。

JDK动态代理

原理

在 JDK 动态代理中,每一个动态代理类都必须实现 InvocationHandler 接口,并且每个动态代理类都关联了一个 handler。当我们通过代理对象调用一个方法的时候,这个方法的调用,就会被转发为由 InvocationHandler 接口的 invoke 方法来进行调用。invoke 方法会根据传入的代理对象、方法名称以及参数决定调用代理的哪个方法。详情见下 InvocationHandler 类的源码注释。


/**
 * InvocationHandler is the interface implemented by
 * the invocation handler of a proxy instance.
 *
 * Each proxy instance has an associated invocation handler.
 * When a method is invoked on a proxy instance, the method
 * invocation is encoded and dispatched to the invoke
 * method of its invocation handler.
 *
 * @author      Peter Jones
 * @see         Proxy
 * @since       1.3
 */
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

}
复制代码
使用步骤

使用 JDK 动态代理的 5 大步骤如下

  1. 为动态代理类实现 InvocationHandler 接口并重写 invoke 方法,即为动态代理类关联一个自定义的 handler
  2. 通过 Proxy.getProxyClass 获得动态代理类。
  3. 通过反射机制获得代理类的构造方法,方法签名为 getConstructor(InvocationHandler.class)
  4. 通过构造函数获得代理对象,并将自定义的 InvocationHandler 实例对象作为参数传入。
  5. 通过代理对象调用目标方法。

Java 的 Proxy 类提供了一个 Proxy.newProxyInstance() 方法,封装了上面的第 2~4 步的工作(详情见下 Proxy 类的源码注释),故步骤可精简为 3 步

  1. 为动态代理类实现 InvocationHandler 接口并重写 invoke 方法,即为动态代理类关联一个自定义的 handler
  2. 使用 Proxy.newProxyInstance() 方法获得代理对象
  3. 通过代理对象调用目标方法。
/**
 * Proxy provides static methods for creating dynamic proxy
 * classes and instances, and it is also the superclass of all
 * dynamic proxy classes created by those methods.
 *
 * To create a proxy for some interface Foo:
 * 
 *     InvocationHandler handler = new MyInvocationHandler(...);
 *     Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), 
 *                                                  Foo.class);
 *     Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).
 *                     newInstance(handler);
 * 
 * or more simply:
 * 
 *     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
 *                                          new Class<?>[] { Foo.class },
 *                                          handler);
 * 
 */
public class Proxy implements java.io.Serializable {
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        // ...
    }
}

复制代码
使用示例

下面给出一个 JDK 动态代理的示例。

//1.「服务接口」
public interface IHello {
    void sayHello();
}

//2.「服务类」
public class HelloImpl implements IHello {
    @Override
    public void sayHello() {
        System.out.println("Hello world!");
    }
}
复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
 
// 为动态代理类关联一个自定义的 InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
 
    /** 目标对象 */
    private Object target;
 
    public MyInvocationHandler(Object target){
        this.target = target;
    }
 
    //重写invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("------插入前置通知代码-------------");
        // 执行相应的目标方法
        Object rs = method.invoke(target,args);
        System.out.println("------插入后置处理代码-------------");
        return rs;
    }
}
复制代码
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;

public class MyProxyTest {
    public static void main(String[] args)
            throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        // =========================第一种==========================
        // 1、生成$Proxy0的class文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 2、获取动态代理类
        Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);
        // 3、获得代理类的构造函数,并传入参数类型InvocationHandler.class
        Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
        // 4、通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入
        IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));
        // 5、通过代理对象调用目标方法
        iHello1.sayHello();
 

        // ==========================第二种=============================
        /**
         * Proxy类中还有个将2~4步骤封装好的简便方法来创建动态代理对象,
         *其方法签名为:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h)
         */
        IHello  iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // 加载接口的类加载器
                new Class[]{IHello.class}, // 一组接口
                new MyInvocationHandler(new HelloImpl())); // 自定义的InvocationHandler
        iHello2.sayHello();
    }
}
复制代码
JDK静态代理和JDK动态代理的对比

相同点

  1. JDK 静态代理和 JDK 动态代理,两者在使用时都需要创建代理类,并且代理类都要实现对应的接口。

不同点

  1. JDK 静态代理是通过直接编码创建的,代码编译前,代理类就需要实现和被代理类相同的接口
  2. JDK 动态代理是利用「反射机制」在运行时创建代理类的。
  3. JDK 动态代理中,动态代理类需要实现 InvocationHandler 接口,并重写 invoke 方法。

CGLIB动态代理

原理

CGLIB 动态代理中,通过字节码技术(使用了字节码处理框架 ASM)为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

使用步骤

使用 CGLIB 动态代理的步骤如下

  1. CGLIB动态代理中,要求代理类必须要实现 MethodInterceptor 接口并重写 intercept()
  2. 通过 CGLIB 动态代理,获取代理对象。
  3. 通过代理对象调用目标方法时,对目标方法的调用,将被转发到 MethodInterceptor 接口的 intercept() 方法来进行调用。在 intercept() 方法中,我们除了会调用委托方法外,还会进行一些增强操作,如日志的记录,调用量的监控等。

需要注意的是

  • CGLIB 动态代理中,代理类是继承了被代理类(或服务类),即 enhancer.setSuperclass(Service.class)。所以,被代理类的final 方法或 final 类,不能被代理类继承,也就不能通过代理的方式调用。
  • 代理类将委托类作为自己的父类,并为其中的非 final 委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过 super 调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了 MethodInterceptor 接口的对象,若存在则将调用 intercept 方法对委托方法进行代理。
使用示例
public class HelloService {
 
    public HelloService() {
        System.out.println("HelloService构造");
    }
 
    /**
     * final方法不能被子类覆盖
     * Cglib 是无法代理 final 修饰的方法的
     */
    final public String sayOthers(String name) {
        System.out.println("HelloService:sayOthers>>"+name);
        return null;
    }
 
    public void sayHello() {
        System.out.println("HelloService:sayHello");
    }
}
复制代码
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
import java.lang.reflect.Method;
 
/**
 * 自定义MethodInterceptor
 */
public class MyMethodInterceptor implements MethodInterceptor{
 
    /**
     * sub:cglib生成的代理对象
     * method:被代理对象方法
     * objects:方法入参
     * methodProxy: 代理方法
     */
    @Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}
复制代码
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
 
public class Client {
    public static void main(String[] args) {
        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/lbs/CodeSpace");
        // 通过CGLIB动态代理获取代理对象的过程
        Enhancer enhancer = new Enhancer();
        // 设置enhancer对象的父类 
        // 代理类是继承了服务类 !!!
        enhancer.setSuperclass(HelloService.class);
        // 设置enhancer的回调对象
        enhancer.setCallback(new MyMethodInterceptor());
        // 创建代理对象
        HelloService proxy= (HelloService)enhancer.create();
        // 通过代理对象调用目标方法
        proxy.sayHello();
    }
}

复制代码
FastClass机制

CGLIB 动态代理中,方法的调用并不是通过反射来完成的,而是通过 FastClass 机制来实现对被拦截方法的调用。

FastClass 机制中,不使用反射类(ConstructorMethod)来调用委托类的方法,而是动态生成一个新的类(继承 FastClass),并为委托类的方法调用语句建立索引,使用者根据方法签名(方法名 + 参数类型)得到索引值,再通过索引值调用相应的方法。

下面给出一个分别使用反射技术和 FastClass 机制调用委托方法的示例。

  1. 委托类
public class DelegateClass {

    public DelegateClass() {
    }

    public DelegateClass(String string) {
    }

    public boolean add(String string, int i) {
        System.out.println("This is add method: " + string + ", " + i);
        return true;
    }

    public void update() {
        System.out.println("This is update method");
    }
}
复制代码
  1. 通过反射技术调用委托方法
// Java Reflect
public static void reflectTest() throws Exception {
 
    Class delegateClass = DelegateClass.class;

    // 反射构造类
    Constructor delegateConstructor = delegateClass.getConstructor(String.class);
    // 创建委托类实例
    DelegateClass delegateInstance = (DelegateClass) delegateConstructor.newInstance("Tom");

    // 反射方法类
    Method addMethod = delegateClass.getMethod("add", String.class, int.class);
    // 调用方法
    addMethod.invoke(delegateInstance, "Tom", 30);

    Method updateMethod = delegateClass.getMethod("update");
    updateMethod.invoke(delegateInstance);
}
复制代码
  1. 通过 FastClass 机制调用委托方法
// FastClass
public static void fastClassTest() throws Exception {
    //设置 CGLIB 生成的代理类文件存入本地磁盘,方便我们反编译查看源码
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/lbs/CodeSpace");
    
    // FastClass动态子类实例
    FastClass fastClass = FastClass.create(DelegateClass.class);

    // 创建委托类实例
    DelegateClass fastInstance = (DelegateClass) fastClass.newInstance(
        new Class[] {String.class}, new Object[]{"Jack"});

    // 调用委托类方法
    fastClass.invoke("add", new Class[]{ String.class, int.class}, fastInstance, 
        new Object[]{ "Jack", 25});

    fastClass.invoke("update", new Class[]{}, fastInstance, new Object[]{});
}
复制代码
  1. 下面看一下 FastClass 的源码
public abstract class FastClass{

    // 委托类
    private Class type;
    
    // 子类访问构造方法
    protected FastClass() {}
    protected FastClass(Class type) {
        this.type = type;
    }
    
    // 创建动态FastClass子类
    public static FastClass create(Class type) {
        // Generator:子类生成器,继承AbstractClassGenerator
        Generator gen = new Generator();
        gen.setType(type);
        gen.setClassLoader(type.getClassLoader());
        return gen.create();
    }
    
    /**
     * 调用委托类方法
     *
     * @param name 方法名
     * @param parameterTypes 方法参数类型
     * @param obj 委托类实例
     * @param args 方法参数对象
     */
    public Object invoke(String name, Class[] parameterTypes, Object obj, Object[] args) {
        return invoke(getIndex(name, parameterTypes), obj, args);
    }
    
    /**
     * 根据方法描述符找到方法索引
     *
     * @param name 方法名
     * @param parameterTypes 方法参数类型
     */
    public abstract int getIndex(String name, Class[] parameterTypes);
    
    
    /**
     * 根据方法索引调用委托类方法
     *
     * @param index 方法索引
     * @param obj 委托类实例
     * @param args 方法参数对象
     */
    public abstract Object invoke(int index, Object obj, Object[] args);
    
    /**
     * 调用委托类构造方法
     * 
     * @param parameterTypes 构造方法参数类型
     * @param args 构造方法参数对象
     */
    public Object newInstance(Class[] parameterTypes, Object[] args) throws {
        return newInstance(getIndex(parameterTypes), args);
    }
    
    /**
     * 根据构造方法描述符(参数类型)找到构造方法索引
     *
     * @param parameterTypes 构造方法参数类型
     */
    public abstract int getIndex(Class[] parameterTypes);
    
    /**
     * 根据构造方法索引调用委托类构造方法
     *
     * @param index 构造方法索引
     * @param args 构造方法参数对象
     */
    public abstract Object newInstance(int index, Object[] args);
}
复制代码
  1. 找到 CGLIB 生成的代理类文件 DelegateClass$$FastClassByCGLIB$$4af5b667,反编译查看源码
public class DelegateClass$$FastClassByCGLIB$$4af5b667 extends FastClass {
    
    /**
     * 动态子类构造方法
     */
    public DelegateClass$$FastClassByCGLIB$$4af5b667(Class delegateClass) {
        super(delegateClass);
    }

    /**
     * 根据方法签名得到方法索引
     *
     * @param name 方法名
     * @param parameterTypes 方法参数类型
     */
    public int getIndex(String methodName, Class[] parameterTypes) {
        switch(methodName.hashCode()) {
            
            // 委托类方法add索引:0
            case 96417:
                if (methodName.equals("add")) {
                    switch(parameterTypes.length) {
                        case 2:
                            if (parameterTypes[0].getName().equals("java.lang.String") && 
                                parameterTypes[1].getName().equals("int")) {
                                return 0;
                            }
                    }
                }
                break;
            
            // 委托类方法update索引:1
            case -838846263:
                if (methodName.equals("update")) {
                    switch(parameterTypes.length) {
                        case 0:
                            return 1;
                    }
                }
                break;
                
            // Object方法equals索引:2
            case -1295482945:
                if (methodName.equals("equals")) {
                    switch(parameterTypes.length) {
                        case 1:
                            if (parameterTypes[0].getName().equals("java.lang.Object")) {
                                return 2;
                            }
                    }
                }
                break;
            
            // Object方法toString索引:3
            case -1776922004:
                if (methodName.equals("toString")) {
                    switch(parameterTypes.length) {
                        case 0: return 3;
                    }
                }
                break;
            
            // Object方法hashCode索引:4
            case 147696667:
                if (methodName.equals("hashCode")) {
                    switch(parameterTypes.length) {
                        case 0:
                            return 4;
                    }
                }
        }

        return -1;
    }
    
    /**
     * 根据方法索引调用委托类方法
     *
     * @param methodIndex 方法索引
     * @param delegateInstance 委托类实例
     * @param parameterValues 方法参数对象
     */
    public Object invoke(int methodIndex, Object delegateInstance, Object[] parameterValues) {
        DelegateClass instance = (DelegateClass) delegateInstance;
        int index = methodIndex;
        try {
            switch(index) {
                case 0:
                    // 委托类实例直接调用方法语句
                    return new Boolean(instance.add((String)parameterValues[0], 
                            ((Number)parameterValues[1]).intValue()));
                case 1:
                    instance.update();
                    return null;
                case 2:
                    return new Boolean(instance.equals(parameterValues[0]));
                case 3:
                    return instance.toString();
                case 4:
                    return new Integer(instance.hashCode());
            }
        } catch (Throwable t) {
            throw new InvocationTargetException(t);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

    /**
     * 根据构造方法描述符(参数类型)找到构造方法索引
     *
     * @param parameterTypes 构造方法参数类型
     */
    public int getIndex(Class[] parameterTypes) {
        switch(parameterTypes.length) {
            // 无参构造方法索引:0
            case 0:
                return 0;
            
            // 有参构造方法索引:1
            case 1:
                if (parameterTypes[0].getName().equals("java.lang.String")) {
                    return 1;
                }
            default:
                return -1;
        }
    }
    
    /**
     * 根据构造方法索引调用委托类构造方法
     *
     * @param methodIndex 构造方法索引
     * @param parameterValues 构造方法参数对象
     */
    public Object newInstance(int methodIndex, Object[] parameterValues) {
        // 创建委托类实例
        DelegateClass newInstance = new DelegateClass;
        DelegateClass newObject = newInstance;
        int index = methodIndex;
        try {
            switch(index) {
                // 调用构造方法(<init>)
                case 0:
                    newObject.<init>();
                    return newInstance;
                case 1:
                    newObject.<init>((String)parameterValues[0]);
                    return newInstance;
            }
        } catch (Throwable t) {
            throw new InvocationTargetException(t);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

    public int getMaxIndex() {
        return 4;
    }
}
复制代码
JDK动态代理和CGLIB动态代理的对比
  1. JDK 动态代理时,需要委托类(被代理类)实现了一个接口。CGLIB 动态代理时,则无此要求,因为代理类是继承的委托类。
  2. JDK 动态代理中,代理类通过 Java 反射技术调用委托方法。CGLIB 动态代理中,代理类通过 FastClass 机制,采用方法拦截的技术对委托方法进行调用。

猜你喜欢

转载自juejin.im/post/7098269942310502437