Loading

Java 静态代理、JDK动态代理、CGLIB代理的区别及原理

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

代理(Proxy):是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。

好处:可以在目标对象实现的基础上,增强额外的功能操作,即目标对象的扩展功能。

举个例子:假如产品经理提了一个新的需求,需要在某个方法调用之前以及调用之后打印日志。那么我们就可以通过代理的方式,不修改方法内部的代码而实现。

1. Java静态代理

静态代理: 代理对象和目标对象实现相同的接口或者继承相同的父类,然后通过调用代理对象的方法来调用目标对象的方法实现代理。

// 委托接口
public interface HelloService {

    void sayHello();
}

// 接口实现类
public class HelloServiceImpl implements HelloService {

    @Override
    public void sayHello() {
        System.out.println("hello 我是接口实现类");
    }
}

// 代理类
public class HelloServiceProxy implements HelloService {

    private HelloService helloService;

    public HelloServiceProxy(HelloService helloService) {
        this.helloService = helloService;
    }

    @Override
    public void sayHello() {
        System.out.println("目标对象方法调用前打印日志 ...");
        this.helloService.sayHello();
        System.out.println("目标对象方法调用后打印日志 ...");
    }
}

// 测试静态代理类
public class Main {

    public static void main(String[] args) {
        HelloService target = new HelloServiceImpl();
        HelloService proxy = new HelloServiceProxy(target);
        proxy.sayHello();
    }
}

运行结果

image-20220114191943424

我们将接口实现类注入到代理类中,然后通过代理类调用真正的实现类实现的方法。那么我们就可以在调用实现的方法的前后做一些处理。

优点:

可以做到不修改目标对象功能的前提下,对目标对象进行扩展。

缺点:

因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,同时接口一旦增加新的方法,代理对象也需要实现相应的方法。

2. JDK动态代理

为解决静态代理为不同接口实现不同的代理类而带来的缺点,我们可以利用动态代理的方式,代理类无需实现目标类的接口,就可以代理目标类的方法,但是目标类必须要实现接口

// 方法调用前后打印日志的代理类
public class LogProxy implements InvocationHandler {

    Object target;

    public LogProxy(Object target) {
        this.target = target;
    }


    /**
     * 动态生成代理类对象,Proxy.newProxyInstance
     * @return 返回代理类的实例
     */
    public Object newProxyInstance() {
        return Proxy.newProxyInstance(
            	//指定代理对象的类加载器
                target.getClass().getClassLoader(),
            	//代理对象需要实现的接口,可以同时指定多个接口
                target.getClass().getInterfaces(),
            	//方法调用的实际处理者,代理对象的方法调用都会转发到这里
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(target + " 的 " + method.getName() + " 方法即将调用");
        Object ret = method.invoke(this.target, args);
        System.out.println(target + " 的 " + method.getName() + " 方法调用完毕");
        return ret;
    }
}

// 测试代理类
public class Main {

    public static void main(String[] args) {
        HelloService target = new HelloServiceImpl();
        LogProxy logProxy = new LogProxy(target);
        HelloService proxyInstance = (HelloService) logProxy.newProxyInstance();
        proxyInstance.sayHello();

        /* 同一个代理类,可以代理实现同接口的目标类 */
        CharSequence target2 = new String("hello");
        LogProxy logProxy2 = new LogProxy(target2);
        CharSequence proxyInstance2 = (CharSequence) logProxy2.newProxyInstance();
        System.out.println(proxyInstance2.length());
    }
}

运行结果:

image-20220114200928897

代理类通过实现 InvocationHandler 接口的 invoke() 方法,并通过 Proxy 类的静态方法newProxyInstance()方法动态生成代理类实例, 即可对注入的目标类进行代理,而且同一个代理类可以代理实现不同接口的目标类,相比较静态代理大大提高了开发效率。

那么动态代理是如何实现的呢?

在了解动态代理之前,我们先来回顾一下,在JVM中是如何将类加载到内存中的。

加载

  1. 通过全类名获取定义此类的二进制字节流
  2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
  3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口

在步骤1中,二进制字节流可以是从文件、ZIP包、网络流、运行时计算生成等方式获取。其中的运行时计算生成就是动态代理技术的实现方式。

那么我们就可以根据不同的目标类动态的生成不同的代理类实例的字节码,即可在内存中得到不同的代理类实例。

分析 Proxy 源码,我们可以看到其内部就是通过动态生成字节码的方式来得的到代理类实例。

image-20220114220413484

动态生成的代理类又是如何实现代理的呢?

在下面这段程序中,proxyInstance 调用的 sayHello() 方法,实际上调用的是 logProxyinvoke()方法,在invoke() 内部我们又调用了目标类真正的方法,从而实现代理的功能。

HelloService target = new HelloServiceImpl();
LogProxy logProxy = new LogProxy(target);
HelloService proxyInstance = (HelloService) logProxy.newProxyInstance();
proxyInstance.sayHello();

也就是说,在 proxyInstancesayHello()方法,实际上是调用 proxyInstace.invoke(),即

// proxyInstance 代理类实例的方法
public void sayHello() {
    // ...
    proxyInstance.invoke(this, method, null); // method 为目标类的真正方法
    // ...
}

invoke() 方法中,method 为目标类的真正方法,那么我们如何获取到关于 method 的相关信息呢?

答案就是:通过反射

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println(target + " 的 " + method.getName() + " 方法即将调用");
    Object ret = method.invoke(this.target, args);
    System.out.println(target + " 的 " + method.getName() + " 方法调用完毕");
    return ret;
}

在 Java 中通过反射,我们可以在运行时获得类的成员和成员的信息。

接下来就来验证我们的猜想。

通过 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true")我们可以在项目根目录下得到动态生成的字节码文件。

通过反编译查看 HelloService 代理类实例

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import com.chen.dynamicproxy.HelloService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements HelloService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    // 传入 InvocationHandler 的实现类
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sayHello() throws  {
        try {
            // 通过 InvocationHandler 的实现类代理目标方法
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    // 通过反射的方式获取方法
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.chen.dynamicproxy.HelloService").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

通过反编译 JDK 动态生成的字节码,我们可以看到代理类实例,是通过反射的方式实现代理。

优点:

解决了静态代理中冗余的代理实现类问题。

缺点:

JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。

3. CGLIB 代理

JDK 动态代理依赖接口实现,而我们只有类没有接口的时候就需要另一种动态代理技术,CGLIB动态代理。

CGLIB动态代理是第三方框架实现的,需要在 maven 中引入相关依赖。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2</version>
</dependency>

CGLIB动态代理是针对类来实现代理的,原理是对指定的目标类生成一个子类,并重写其方法来实现代理。

CGLIB动态代理创建动态代理的模式是:

  1. 查找目标类上所有的非 finalpublic 类型的方法(final 方法不能被重写)
  2. 将这些方法的定义转成字节码
  3. 将组成的字节码转换成相应的代理的 Class 对象然后通过反射获得代理类的实例对象
  4. 实现 MethodInterceptor 接口, 用来处理对代理类上所有方法的请求

例子:

// 没有实现接口的目标类
public class HelloClass {

    public void sayHello() {
        System.out.println("hello 我是目标类");
    }
}

// 实现 MethodInterceptor 的类
public class CglibProxy implements MethodInterceptor {

    public static Object newProxyInstance(Class<?> clazz) {
        //Enhancer 是 CGLIB 的字节码增强器,代理类对象是由 Enhancer 类创建的
        Enhancer enhancer = new Enhancer();
        // 设置产生代理对象的父类型
        enhancer.setSuperclass(clazz);
        // 定义逻辑对象,要求实现 MethodInterceptor 接口
        enhancer.setCallback(new CglibProxy());
        // 使用默认无参数的构造函数创建目标对象,这是一个前提,被代理的类要提供无参构造方法
        return enhancer.create();
    }

    // 实现 MethodInterceptor 接口的 intercept 方法
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("方法调用之前");
        // 注意调用的是 invokeSuper,调用 invoke 则会导致死循环
        Object ret = proxy.invokeSuper(obj, args);
        System.out.println("方法调用之后");
        return ret;
    }
}

// 测试

public class Main {

    public static void main(String[] args) {
        HelloClass proxyInstance = (HelloClass) CglibProxy.newProxyInstance(HelloClass.class);
        proxyInstance.sayHello();

        Object proxyInstance2 = CglibProxy.newProxyInstance(Object.class);
        proxyInstance2.hashCode();
    }
}

运行结果:

image-20220115212123037

那么 CGLIB 动态代理是如何实现的呢?

我们在主函数开头加入System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "generate"), 即可项目根目录下看到生成的字节码文件。

反编译代理类得:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.chen.cglibproxy;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

// 继承自 HelloClass
public class HelloClass$$EnhancerByCGLIB$$8df0bd4 extends HelloClass implements Factory {
    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$sayHello$0$Method;
    private static final MethodProxy CGLIB$sayHello$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$finalize$1$Method;
    private static final MethodProxy CGLIB$finalize$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy;

    // CGLIB 通过 MethodProxy.create() 的方式定位方法,而 JDK 动态代理则是通过反射
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.chen.cglibproxy.HelloClass$$EnhancerByCGLIB$$8df0bd4");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$finalize$1$Method = var10000[0];
        CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
        CGLIB$equals$2$Method = var10000[1];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[2];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[3];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[4];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        CGLIB$sayHello$0$Method = ReflectUtils.findMethods(new String[]{"sayHello", "()V"}, (var1 = Class.forName("com.chen.cglibproxy.HelloClass")).getDeclaredMethods())[0];
        CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$0");
    }

    // ...
}

通过查看代理类的源码我们可以看到,代理类会获得在父类继承的所有方法。

我们来看代理类的方法是如何重写的:

// proxy.invokeSuper() 调用此方法
final void CGLIB$sayHello$0() {
    super.sayHello();
}

// proxy.invoke() 调用此方法,这就是为什么在 intercept 方法中调用 invoke 会发生死循环
public final void sayHello() {
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }

    if (var10000 != null) {
        // 调用 intecept 方法
        var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
    } else {
        super.sayHello();
    }
}

在 CGLIB动态代理中,重写方法的方式与 JDK 动态代理类似,都是在方法中调用实现相应接口的方法,在CGLIB中调用的是MethodInterceptorintercept() 方法,在 JDK 动态代理调用的是InvocationHandler invoke() 方法,大同小异。

虽然 CGLIB 动态代理重写方法的方式与 JDK动态代理相似。

但是 CGLIB 采用了FastClass 机制,它的原理简单来说就是:为代理类和被代理类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int类型)。

这个index当做一个入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比JDK动态代理通过反射调用高。

在代理类初始化时,会创建 MethodProxy 其内部含有要定位方法的信息

CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$0");

MethodProxy.create() 方法其内部实现如下:

public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
    MethodProxy proxy = new MethodProxy();
    proxy.sig1 = new Signature(name1, desc); //被代理方法签名
    proxy.sig2 = new Signature(name2, desc); //代理方法签名
    proxy.createInfo = new CreateInfo(c1, c2);
    return proxy;
}

invokeSuper() 调用时, init() 会获取 index 索引

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        init(); 
        FastClassInfo fci = fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    }
}

// FastClass并不是跟代理类一块生成的,而是在第一次执行MethodProxy invoke/invokeSuper时生成的并放在了缓存中
private void init()
{
    if (fastClassInfo == null)
    {
        synchronized (initLock)
        {
            if (fastClassInfo == null)
            {
                CreateInfo ci = createInfo;

                FastClassInfo fci = new FastClassInfo();
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(sig1);
                fci.i2 = fci.f2.getIndex(sig2); // 获取 index
                fastClassInfo = fci;
            }
        }
    }
}

反编译 FastClass 的类的字节码

// 根据方法签名获取index
public int getIndex(Signature var1) {
    String var10000 = var1.toString();
    switch(var10000.hashCode()) {
        case -1725733088:
            if (var10000.equals("getClass()Ljava/lang/Class;")) {
                return 7;
            }
            break;
        case -1026001249:
            if (var10000.equals("wait(JI)V")) {
                return 1;
            }
            break;
        case 243996900:
            if (var10000.equals("wait(J)V")) {
                return 2;
            }
            break;
        case 946854621:
            if (var10000.equals("notifyAll()V")) {
                return 9;
            }
            break;
        case 1116248544:
            if (var10000.equals("wait()V")) {
                return 3;
            }
            break;
        case 1535311470:
            if (var10000.equals("sayHello()V")) {
                return 0;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return 4;
            }
            break;
        case 1902039948:
            if (var10000.equals("notify()V")) {
                return 8;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return 5;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return 6;
            }
    }

    return -1;
}

// 根据index直接定位执行方法
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
    HelloClass var10000 = (HelloClass)var2;
    int var10001 = var1;

    try {
        switch(var10001) {
            case 0:
                var10000.sayHello();
                return null;
            case 1:
                var10000.wait(((Number)var3[0]).longValue(), ((Number)var3[1]).intValue());
                return null;
            case 2:
                var10000.wait(((Number)var3[0]).longValue());
                return null;
            case 3:
                var10000.wait();
                return null;
            case 4:
                return new Boolean(var10000.equals(var3[0]));
            case 5:
                return var10000.toString();
            case 6:
                return new Integer(var10000.hashCode());
            case 7:
                return var10000.getClass();
            case 8:
                var10000.notify();
                return null;
            case 9:
                var10000.notifyAll();
                return null;
        }
    } catch (Throwable var4) {
        throw new InvocationTargetException(var4);
    }

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

总结

  1. JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。
  2. 2.JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
  3. 3.JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。
posted @ 2022-01-15 22:36  Code-CHAN  阅读(259)  评论(0编辑  收藏  举报