静态代理、动态代理、cglib代理

383 阅读5分钟

「这是我参与2022首次更文挑战的第38天,活动详情查看:2022首次更文挑战

代理

代理顾名思义就是代替执行任务,代理的本质是多态+组合,同一类型(继承或者实现了同一个类或者接口)的对象使用父类引用,实际过程中使用代理类替换原来的实现类进行执行,内部使用组合执行被代理类的方法之外额外执行一些非核心的操作。

说明:环境为jdk1.8。

使用场景

如一些消息、日志等主功能之外的操作。能够将主功能和额外操作就行解耦,方便代码的维护。

静态代理

接口

public interface Person {
    /**
     * 上交班费
     */
    void giveMoney();
}

被代理类

public class Student implements Person {
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void giveMoney() {
        System.out.println(name + "交了50元班费");
    }
}

代理类

public class StudentMonitor implements Person{
    private Student student;

    public StudentMonitor(Student student){
        this.student=student;
    }

    @Override
    public void giveMoney() {
        System.out.println("我是班长负责统一收缴班费");
        student.giveMoney();
        System.out.println("该同学近期表现良好");
    }
}

本质

本质就是多态+组合,最原始的代理。

优点

最原始的代理,简单明了可读性比较强。

缺点/限制

如上的例子,使用起来比较麻烦,需要管理接口、代理类、被代理类。

动态代理

代理类使用如下代替:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class StudentInvocationHandler<T> implements InvocationHandler {
    private T target;

    public StudentInvocationHandler(T target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理执行" + method.getName() + "方法");
        Object result = method.invoke(target, args);
        return result;
    }
}

测试:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyTest {
    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        //创建一个实例对象,这个对象是被代理的对象
        Person zhangsan = new Student("张三");
        InvocationHandler stuHandler = new StudentInvocationHandler(zhangsan);
        Person studyProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, stuHandler);
        studyProxy.giveMoney();
    }
}

可以发现测试中,创建代理前加了 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");这个是用来把动态代理类输出来的设置,具体输出来的内容如下:

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

package com.sun.proxy;

import com.study.proxy.Person;
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 Person {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    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 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 void giveMoney() throws  {
        try {
            super.h.invoke(this, m3, (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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.study.proxy.Person").getMethod("giveMoney");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以发现$Proxy0继承了 Proxy 实现了Person接口,所有的Stuent方法都被重写了(包括Object的public非fianl方法),最终调用的是StudentInvocationHandler的invoke方法,invoke方法内部使用反射进行调用Stuent的方法,从而实现代理。

本质

本质就是多态+组合+动态生成字节码+反射。核心为 Proxy 类和InvocationHandler接口。

优点

只需要实现InvocationHandler接口,在invoke方法内进行代理操作即可。不需要把代理类给写的很死,它是一个通用的代理类,可以不固定接口。但是代理操作是一样的,也就是说需要为不同代理操作创建不同的InvocationHandler

缺点/限制

动态代理是运行时在内存中“临时”生成的,也就是说每运行一次至少要生成一次,会消耗一定的资源,这点不如静态代理。同时和静态代理的本质却别时反射,但是反射有一定的开销,所以从性能上来看是不如静态代理的。

反射的缺点和JVM的优化

反射肯定没有方法的直接调用来的快,因为Method#invoke的时候会进行权限检查、参数会生成一个长度为传入参数数量的Object数组、如果是基本类型的话还要进行自动装箱,JDK6之前会调用本地方法,需要从Java到C++再到Java的切换。

JDK6的时候进行了优化对反射的委派实现提供了一种动态生成字节码的实现,直接使用invoke指令来调用目标方法,伪代码如下:

// 动态实现的伪代码,这里只列举了关键的调用逻辑,其实它还包括调用者检测、参数检测的字节码。
package jdk.internal.reflect;
 
public class GeneratedMethodAccessor1 extends ... {
  @Overrides    
  public Object invoke(Object obj, Object[] args) throws ... {
    Test.target((int) args[0]);
    return null;
  }
}

这样能避免从Java到C++再到Java的切换。此外JVM的即时编译器会根据是否有逃逸对象进行方法内联,从而进一步加快了反射的速度。

  • 多少次调用动态生成字节码阈值设置: -Dsun.reflect.inflationThreashold= 默认为15。
  • -Dsun.reflect.noInflation=true:可以在反射时一开始就直接生成动态实现,如果调用次数必定大于15可以考虑设置。

使用场景

一个框架、通用的组件等如Spring AOP,不知道你会实现那个接口,但是又需要使用代理,那么就会考虑动态代理,使用反射完成代理。

cglib代理

被代理类(去掉了Person接口):

public class Student{
    private String name;

    public Student(String name) {
        this.name = name;
    }

    public void giveMoney() {
        System.out.println(name + "交了50元班费");
    }
}

代理类替换如下:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class StudentCglibProxy implements MethodInterceptor {
    private Class targetClass;

    public StudentCglibProxy(Class targetClass) {
        this.targetClass = targetClass;
    }

    //为目标对象生成代理对象
    public Object getProxyInstance( String name) {
        //工具类
        Enhancer en = new Enhancer();
        //设置父类
        en.setSuperclass(targetClass);
        //设置回调函数
        en.setCallback(this);
        //创建子类对象代理
        return en.create(new Class[]{String.class}, new Object[]{name});
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理执行" + method.getName() + "方法");

        // 执行目标对象的方法
        Object returnValue = methodProxy.invokeSuper(obj, args);

        return returnValue;
    }

}

测试类:

import org.springframework.cglib.core.DebuggingClassWriter;

public class CglibProxyTest {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");
        Student proxyInstance = (Student) new StudentCglibProxy(Student.class).getProxyInstance("张三");
        proxyInstance.giveMoney();
    }
}

cglib生成的代理类:

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

package com.study.proxy;

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

public class Student$$EnhancerByCGLIB$$b56eea6e extends Student implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$giveMoney$0$Method;
    private static final MethodProxy CGLIB$giveMoney$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.study.proxy.Student$$EnhancerByCGLIB$$b56eea6e");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
        CGLIB$giveMoney$0$Method = ReflectUtils.findMethods(new String[]{"giveMoney", "()V"}, (var1 = Class.forName("com.study.proxy.Student")).getDeclaredMethods())[0];
        CGLIB$giveMoney$0$Proxy = MethodProxy.create(var1, var0, "()V", "giveMoney", "CGLIB$giveMoney$0");
    }

    final void CGLIB$giveMoney$0() {
        super.giveMoney();
    }

    public final void giveMoney() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$giveMoney$0$Method, CGLIB$emptyArgs, CGLIB$giveMoney$0$Proxy);
        } else {
            super.giveMoney();
        }
    }

    final boolean CGLIB$equals$1(Object var1) {
        return super.equals(var1);
    }

    public final boolean equals(Object var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);
            return var2 == null ? false : (Boolean)var2;
        } else {
            return super.equals(var1);
        }
    }

    final String CGLIB$toString$2() {
        return super.toString();
    }

    public final String toString() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy) : super.toString();
    }

    final int CGLIB$hashCode$3() {
        return super.hashCode();
    }

    public final int hashCode() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
            return var1 == null ? 0 : ((Number)var1).intValue();
        } else {
            return super.hashCode();
        }
    }

    final Object CGLIB$clone$4() throws CloneNotSupportedException {
        return super.clone();
    }

    protected final Object clone() throws CloneNotSupportedException {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        return var10000 != null ? var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy) : super.clone();
    }

    public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
        String var10000 = var0.toString();
        switch(var10000.hashCode()) {
        case -508378822:
            if (var10000.equals("clone()Ljava/lang/Object;")) {
                return CGLIB$clone$4$Proxy;
            }
            break;
        case 858452198:
            if (var10000.equals("giveMoney()V")) {
                return CGLIB$giveMoney$0$Proxy;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return CGLIB$equals$1$Proxy;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return CGLIB$toString$2$Proxy;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return CGLIB$hashCode$3$Proxy;
            }
        }

        return null;
    }

    public Student$$EnhancerByCGLIB$$b56eea6e(String var1) {
        super(var1);
        CGLIB$BIND_CALLBACKS(this);
    }

    public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
        CGLIB$THREAD_CALLBACKS.set(var0);
    }

    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
        CGLIB$STATIC_CALLBACKS = var0;
    }

    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        Student$$EnhancerByCGLIB$$b56eea6e var1 = (Student$$EnhancerByCGLIB$$b56eea6e)var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }

            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }

    }

    public Object newInstance(Callback[] var1) {
        CGLIB$SET_THREAD_CALLBACKS(var1);
        Student$$EnhancerByCGLIB$$b56eea6e var10000 = new Student$$EnhancerByCGLIB$$b56eea6e();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Callback var1) {
        CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
        Student$$EnhancerByCGLIB$$b56eea6e var10000 = new Student$$EnhancerByCGLIB$$b56eea6e();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
        CGLIB$SET_THREAD_CALLBACKS(var3);
        Student$$EnhancerByCGLIB$$b56eea6e var10000 = new Student$$EnhancerByCGLIB$$b56eea6e;
        switch(var1.length) {
        case 1:
            if (var1[0].getName().equals("java.lang.String")) {
                var10000.<init>((String)var2[0]);
                CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
                return var10000;
            }
        default:
            throw new IllegalArgumentException("Constructor not found");
        }
    }

    public Callback getCallback(int var1) {
        CGLIB$BIND_CALLBACKS(this);
        MethodInterceptor var10000;
        switch(var1) {
        case 0:
            var10000 = this.CGLIB$CALLBACK_0;
            break;
        default:
            var10000 = null;
        }

        return var10000;
    }

    public void setCallback(int var1, Callback var2) {
        switch(var1) {
        case 0:
            this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
        default:
        }
    }

    public Callback[] getCallbacks() {
        CGLIB$BIND_CALLBACKS(this);
        return new Callback[]{this.CGLIB$CALLBACK_0};
    }

    public void setCallbacks(Callback[] var1) {
        this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
    }

    static {
        CGLIB$STATICHOOK1();
    }
}

可以发现继承了Student。

本质

本质就是多态+组合+动态生成字节码。运行时动态生成字节码,继承被代理类。使用字节码处理框架ASM。

优点

不需要实现接口,比较方便。

缺点/限制

因为是继承,所以只能代理能继承的方法。同时动态生成字节码比较耗性能。

使用场景

适合框架和通用组件使用,不约束被代理类非要实现接口。

jdk动态代理和cglib性能对比

下面我们进行测试一下。内联的阈值为-XX:CompileThreshold=默认为10000。

jdk动态代理不生成动态字节码的情况

因为-Dsun.reflect.inflationThreashold= 默认为15,所以我们先测下,不动态生成字节码的情况。

cglib

import org.springframework.cglib.core.DebuggingClassWriter;

public class CglibProxyTest {
    public static void main(String[] args) {
//        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");
        Student proxyInstance = (Student) new StudentCglibProxy(Student.class).getProxyInstance("张三");


        long current = System.currentTimeMillis();
        for (int i = 1; i <= 1_5; i++) {
            if (i % 5 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current + "ms");
                current = temp;
            }

            proxyInstance.giveMoney();
        }
    }
}

输出(以下都省略无关打印):

65ms
0ms
0ms

jdk

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyTest {
    public static void main(String[] args) {
//        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        //创建一个实例对象,这个对象是被代理的对象
        Person zhangsan = new Student("张三");
        InvocationHandler stuHandler = new StudentInvocationHandler(zhangsan);
        Person studyProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, stuHandler);

        long current = System.currentTimeMillis();
        for (int i = 1; i <= 1_5; i++) {
            if (i % 5 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current + "ms");
                current = temp;
            }

            studyProxy.giveMoney();
        }
    }
}

输出:

1ms
0ms
0ms

小结

可以发现cglib在第一次调用时,比较耗时,后面差不多。

jdk动态代理生成动态字节码的情况

改成i <= 300000即可,把多余的打印去掉。

cglib

import org.springframework.cglib.core.DebuggingClassWriter;

public class CglibProxyTest {
    public static void main(String[] args) {
//        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");
        Student proxyInstance = (Student) new StudentCglibProxy(Student.class).getProxyInstance("张三");


        long start = System.currentTimeMillis();
        long current = start;
        for (int i = 1; i <= 300000; i++) {
            if (i % 10000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current + "ms");
                current = temp;
            }

            if (i == 300000) {
                System.out.println("end:" + (System.currentTimeMillis() - start) + "ms");
            }

            proxyInstance.giveMoney();
        }
    }
}

输出:

43ms
4ms
0ms
0ms
1ms
0ms
1ms
0ms
1ms
3ms
1ms
1ms
0ms
2ms
0ms
0ms
1ms
0ms
0ms
0ms
0ms
0ms
0ms
0ms
1ms
0ms
0ms
1ms
0ms
0ms
end:60ms

jdk

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyTest {
    public static void main(String[] args) {
//        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        //创建一个实例对象,这个对象是被代理的对象
        Person zhangsan = new Student("张三");
        InvocationHandler stuHandler = new StudentInvocationHandler(zhangsan);
        Person studyProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, stuHandler);

        long start = System.currentTimeMillis();
        long current = start;
        for (int i = 1; i <= 300000; i++) {
            if (i % 10000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current + "ms");
                current = temp;
            }
            if (i == 300000) {
                System.out.println("end:" + (System.currentTimeMillis() - start) + "ms");
            }
            studyProxy.giveMoney();
        }
    }
}

输出:

10ms
5ms
1ms
1ms
4ms
1ms
0ms
1ms
1ms
0ms
0ms
1ms
0ms
1ms
1ms
1ms
1ms
0ms
0ms
0ms
0ms
0ms
1ms
0ms
0ms
0ms
0ms
1ms
0ms
0ms
end:32ms

小结

可以发现cglib在第一次调用时,比较耗时,后面差不多。

小结

可以发现不管jdk用没有上动态生成字节码,jdk动态代理比cglib代理要快(jdk动态代理的反射影响拉不开差距,何况后期还有动态生成字节码),因该是cglib比jdk动态代理生成的字节码要复杂,生成字节码的时候比较慢,但是后期差不多。

参考

java的静态代理和动态代理