前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java动态代理

Java动态代理

作者头像
程序猿杜小头
发布2022-12-01 21:36:29
8950
发布2022-12-01 21:36:29
举报
文章被收录于专栏:程序猿杜小头程序猿杜小头

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
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序猿杜小头 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java动态代理
    • 1 JDK
      • 1.1 代理目标
      • 1.2 拓展逻辑
      • 1.3 实现InvocationHandler接口,封装拓展逻辑
      • 1.4 生成运行时代理类
      • 1.5 时序图
      • 1.6 源码解读
    • 2 CGLIB
      • 2.1 代理目标
      • 2.2 拓展逻辑
      • 2.3 实现MethodInterceptor接口,封装拓展逻辑
      • 2.4 生成运行时代理类
      • 2.5 时序图
      • 2.6 源码解读
    • 3 总结
      • 4 参考文档
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档