Java动态代理

2022-12-01 21:36:29 浏览数 (1)

Java动态代理

Java动态代理是一种在运行时对目标类进行拓展的技术。目前,Java动态代理有两种实现方式:JDK和CGLIB(Code Generation Library),下面分别从两个章节对它们进行介绍。

1 JDK

JDK动态代理是官方原生方案,Java 1.3引入的特性。Proxy.newProxyInstance()即用来在运行时生成代理类;newProxyInstance()方法第一个参数用于指定类加载器;第二个参数用于指定代理类所需要实现的接口列表,如果这里指定的不是接口,那么会抛出IllegalArgumentException异常;第三个参数用于指定InvocationHandler接口的实现类实例,InvocationHandler接口中只有一个invoke()方法,事实上,Proxy.newProxyInstance()所生成的代理类就是通过委托invoke()方法来进行拓展处理的,换句话说,invoke()方法才是真正的拓展逻辑所在。

代码语言:javascript复制
public class Proxy implements java.io.Serializable {
    protected InvocationHandler h;

    private Proxy() {
    }
    protected Proxy(InvocationHandler h) {
        this.h = h;
    }
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
    }
}

1.1 代理目标

CustomInterface接口中定义了一个doPrint()方法,用于打印给定内容。

代码语言:javascript复制
public interface CustomInterface {
    public boolean doPrint(String content);
}
代码语言:javascript复制
public class CustomInterfaceImpl implements CustomInterface {
    @Override
    public boolean doPrint(String content) {
        System.out.println(content);
        return true;
    }
}

1.2 拓展逻辑

运行时doPrint()方法进行拓展:执行前后分别打印一条日志

1.3 实现InvocationHandler接口,封装拓展逻辑

既然InvocationHandler接口中的invoke()方法承载了拓展逻辑,那就直接定义一个实现类CustomInvocationHandler,将打印日志的拓展逻辑封装在invoke()方法中。另外,该实现类CustomInvocationHandler还需要持有代理目标CustomInterface实例,这样才可以执行CustomInterface中的doPrint()方法。

代码语言:javascript复制
public class CustomInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, @NotNull Method method, Object[] args) throws Throwable {
        System.out.println("*** start log ***");
        Object result = method.invoke(this.target, args);
        System.out.println("*** end log ***");

        return result;
    }
}

1.4 生成运行时代理类

代码语言:javascript复制
public class DynamicProxyApp {
    public static void main(String[] args) {
        CustomInvocationHandler customInvocationHandler = new CustomInvocationHandler(new CustomInterfaceImpl());
        CustomInterface customInterfaceProxy = (CustomInterface) Proxy.newProxyInstance(
                CustomInterface.class.getClassLoader(),
                new Class<?>[]{CustomInterface.class},
                customInvocationHandler
        );
        customInterfaceProxy.doPrint("jdk dynamic proxy");
    }
}

运行结果:
*** start log ***
jdk dynamic proxy
*** end log ***

1.5 时序图

1.6 源码解读

JDK所生成的动态代理类,默认贮存在内存中,如果想将代理类持久化到磁盘中,并形成.class文件(直接使用IDEA打开即可),那么可以采用以下方法来实现。

代码语言:javascript复制
// JDK 8
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// JDK 11
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

Proxy0即JDK所生成的代理类,从下面源码可以发现它不仅实现了CustomInterface接口,还继承了Proxy类;同时,Proxy0代理类由final关键字修饰,这意味着它是不可继承的;doPrint()方法中的逻辑很简单,就是直接委托InvocationHandler接口中invoke()方法来处理。

代码语言:javascript复制
public final class $Proxy0 extends Proxy implements CustomInterface {
    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 boolean doPrint(String var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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.example.optimus_prime.proxy.CustomInterface").getMethod("doPrint", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

2 CGLIB

CGLIB是一款高性能的代码生成库,它被广泛应用于基于代理的AOP框架中。作为JDK动态代理的互补,它为那些没有实现接口的目标类提供了代理方案(CGLIB同样支持为已实现接口的目标类进行拓展)。本质上,CGLIB通过生成子类、覆盖代理目标中的方法来实现拓展。

2.1 代理目标

CustomInterfaceImpl类并没有实现CustomInterface接口,其同样定义了一个doPrint()方法。

代码语言:javascript复制
public class CustomInterfaceImpl {
    public boolean doPrint(String content) {
        System.out.println(content);
        return true;
    }
}

2.2 拓展逻辑

运行时doPrint()方法进行拓展:执行前后分别打印一条日志

2.3 实现MethodInterceptor接口,封装拓展逻辑

代码语言:javascript复制
public class CustomMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(
            Object proxy, 
            Method method, 
            Object[] args, 
            MethodProxy methodProxy) throws Throwable {
        System.out.println("*** start log ***");
        Object result=  methodProxy.invokeSuper(proxy, args);
        System.out.println("*** end log ***");

        return result;
    }
}

2.4 生成运行时代理类

代码语言:javascript复制
public class CglibProxyApp {
    public static void main(String[] args) throws IOException {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(CustomInterfaceImpl.class);
        enhancer.setCallback(new CustomMethodInterceptor());
        CustomInterfaceImpl customInterfaceProxy = (CustomInterfaceImpl) enhancer.create();
        customInterfaceProxy.doPrint("cglib dynamic proxy");
    }
}

运行结果:
*** start log ***
cglib dynamic proxy
*** end log ***

2.5 时序图

2.6 源码解读

CGLIB所生成的动态代理类,默认贮存在内存中,如果想将代理类持久化到磁盘中,并形成.class文件(直接使用IDEA打开即可),那么可以采用以下方法来实现。

代码语言:javascript复制
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\optimus_prime");

CustomInterfaceImpl

EnhancerByCGLIB

777d4723即CGLIB所生成的代理类,从下面源码可以发现它继承CustomInterfaceImpl类并覆盖了父类中doPrint()方法,doPrint()方法中的逻辑很直观,如果提供了MethodInterceptor回调接口,则执行MethodInterceptor回调接口中intercept()方法,否则直接执行目标类(父类)中的doPrint()方法;另外,该代理类还暴露了CGLIB

代码语言:javascript复制
public class CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723 
        extends CustomInterfaceImpl 
        implements Factory {

    private static final Method CGLIB$doPrint$0$Method;
    private static final MethodProxy CGLIB$doPrint$0$Proxy;
    
    private MethodInterceptor CGLIB$CALLBACK_0;

    final boolean CGLIB$doPrint$0(String var1) {
        return super.doPrint(var1);
    }

    @Override
    public final boolean doPrint(String var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 != null) {
            Object var2 = var10000.intercept(
                    this,
                    CGLIB$doPrint$0$Method,
                    new Object[]{var1},
                    CGLIB$doPrint$0$Proxy);
            return var2 == null ? false : (Boolean)var2;
        } else {
            return super.doPrint(var1);
        }
    }
}

既然intercept()方法封装了日志打印的拓展逻辑,那么intercept()方法中methodProxy.invokeSuper(proxy, args)的作用肯定就是执行CustomInterfaceImpl中的doPrint()方法了。为了更高效地执行目标类中的doPrint()方法,CGLIB还是花了一定心思的,CGLIB引入了FastClass机制,FastClass机制就是为方法构建索引,调用方法时根据方法签名来计算索引,通过索引来直接调用对应的方法。

代码语言:javascript复制
abstract public class FastClass {
    /**
     * type主要指定所调用方法的归属类,比如本文所涉及的:
     * (1) CustomInterfaceImpl
     * (2) CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723
     */
    private Class type;

    protected FastClass(Class type) {
        this.type = type;
    }

    /**
     * @param sig 方法签名:包含方法名称、方法返回类型和参数类型
     * @return 方法索引
     */
    abstract public int getIndex(Signature sig);

    /**
     * 根据方法索引调用对应的方法
     * @param index the method index
     * @param obj the object the underlying method is invoked from
     * @param args the arguments used for the method call
     */
    abstract public Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException;
}

public class MethodProxy {
    // CustomInterfaceImpl类中doPrint()方法签名信息:方法名称、方法返回类型和参数类型
    private Signature sig1;

    // CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723代理类中CGLIB$doPrint$0()方法签名信息:方法名称、方法返回类型和参数类型
    private Signature sig2;

    private CreateInfo createInfo;

    private volatile FastClassInfo fastClassInfo;

    private final Object initLock = new Object();

    private void init() {
        if (fastClassInfo == null) {
            synchronized (initLock) {
                if (fastClassInfo == null) {
                    CreateInfo ci = createInfo;
                    FastClassInfo fci = new MethodProxy.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);
                    fastClassInfo = fci;
                    createInfo = null;
                }
            }
        }
    }

    private static class FastClassInfo {
        // CustomInterfaceImpl$$FastClassByCGLIB$$e8bf017e
        FastClass f1;
        
        // CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723$$FastClassByCGLIB$$2f30e4f9
        FastClass f2;
        
        // CustomInterfaceImpl类中doPrint()方法的索引
        int i1;

        // CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723代理类中CGLIB$doPrint$0()方法的索引
        int i2;
    }

    /**
     * 执行CustomInterfaceImpl$$FastClassByCGLIB$$e8bf017e类invoke()方法
     * @param obj 该参数不可以是所生成的代理类的实例,否则将无限递归执行
     * @param args args 参数,即"cglib dynamic proxy"
     */
    public Object invoke(Object obj, Object[] args) throws Throwable {
        try {
            init();
            FastClassInfo fci = fastClassInfo;
            return fci.f1.invoke(fci.i1, obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        } catch (IllegalArgumentException e) {
            if (fastClassInfo.i1 < 0) {
                throw new IllegalArgumentException("Protected method: "   sig1);
            }
            throw e;
        }
    }

    /**
     * 执行CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723$$FastClassByCGLIB$$2f30e4f9类invoke()方法
     * @param obj 即所生成的代理类的实例
     * @param args 参数,即"cglib dynamic proxy"
     */
    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();
        }
    }
}

从上面MethodProxy源码中,我们已经知道了invokeSuper内部逻辑:首先,init()方法根据方法签名计算出方法索引fci.i2;然后根据方法索引调用fci.f2中的invoke()方法。而fci.f2成员变量是FastClass类型的且FastClass是一个抽象类,那我们可以猜测CGLIB肯定还生成了FastClass的子类。没错,如下所示:

代码语言:javascript复制
public class CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723$$FastClassByCGLIB$$2f30e4f9
        extends FastClass {
    public CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723$$FastClassByCGLIB$$2f30e4f9(Class var1) {
        super(var1);
    }

    @Override
    public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
            case 247754584:
                if (var10000.equals("CGLIB$doPrint$0(Ljava/lang/String;)Z")) {
                    return 10;
                }
                break;
            case 983113313:
                if (var10000.equals("doPrint(Ljava/lang/String;)Z")) {
                    return 1;
                }
                break;
        }
        return -1;
    }

    @Override
    public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723 var10000 = (CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
                case 1:
                    return new Boolean(var10000.doPrint((String)var3[0]));
                case 10:
                    return new Boolean(var10000.CGLIB$doPrint$0((String)var3[0]));
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }

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

在IDEA DEBUG模式下,进入MethodProxy类一探究竟:fci.i2索引值的确为10,那一切也就真相大白了!!!

代码语言:javascript复制
case 10:
        return new Boolean(var10000.CGLIB$doPrint$0((String)var3[0]));

3 总结

JDK动态代理所代理的目标类必须实现接口,否则抛出异常;另外,如果目标类的方法非接口中已定义的,那么代理类是不会对该方法进行代理的(该方法压根儿不会出现在代理类中)。而CGLIB动态代理则没有目标类必须实现接口的限制,但由于其基于继承机制,那么目标类就不能由final关键字修饰,类似地,该目标类中相关方法也不能由privatefinal关键字修饰。

4 参考文档

  1. https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/Proxy.html
  2. https://dzone.com/articles/cglib-missing-manual
  3. https://objectcomputing.com/resources/publications/sett/november-2005-create-proxies-dynamically-using-cglib-library
  4. https://github.com/cglib/cglib

0 人点赞