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

深入浅出动态代理

作者头像
luoxn28
发布2019-11-06 17:21:12
4710
发布2019-11-06 17:21:12
举报
文章被收录于专栏:TopCoderTopCoder

代理模式是为了提供额外或者不同的操作,而插入代替”实际对象”的对象,即代理类,针对代理类的调用操作,都会涉及到与”实际对象”的通信,代理类起到中间人的作用。Java动态代理比代理的思想更进一步,它可以动态的创建代理类并处理对”实际对象”的调用,Java动态代理底层基于Proxy/InvocationHandler相关类反射技术

Java动态代理

Java动态代理底层基于Proxy/InvocationHandler相关类反射技术,注意,Java动态代理只能做接口的动态代理。下面是一个Java动态代理的示例:

代码语言:javascript
复制
public interface Hello {
    String hello(String msg);
}
public static class HelloImpl implements Hello {
    @Override
    public String hello(String msg) {
        return "hello " + msg;
    }
}

public static class HelloProxy implements InvocationHandler {
    private Object target = null;

    public Object build(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(target, args);
    }
}

public static void main(String[] args) throws Exception {
    // 设置此属性,让JVM自动生成的proxy类写入文件
    System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

    HelloImpl helloImpl = new HelloImpl();
    Hello hello = (Hello) new HelloProxy().build(helloImpl);

    System.out.println(hello.hello("world"));
}

Java动态代理为什么只能做接口的动态代理呢,这与JDK动态生成代理类机制有关,JDK动态生成代理类逻辑如下:

代码语言:javascript
复制
/**
 * var0:代理类名称,比如com.sun.proxy.$Proxy0
 * var1:代理接口
 * var2:代理类访问标识
 */
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    final byte[] var4 = var3.generateClassFile();
    if (saveGeneratedFiles) {
		// 如果开启了保存proxy文件,则进行保存
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                try {
                    int var1 = var0.lastIndexOf(46);
                    Path var2;
                    if (var1 > 0) {
                        Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                        Files.createDirectories(var3);
                        var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                    } else {
                        var2 = Paths.get(var0 + ".class");
                    }

                    Files.write(var2, var4, new OpenOption[0]);
                    return null;
                } catch (IOException var4x) {
                    throw new InternalError("I/O exception saving generated file: " + var4x);
                }
            }
        });
    }

    return var4;
}

Java动态代理自动生成代理类的逻辑是生成一个代理接口的实现类(这也是只能做接口动态代理的原因),对该类中方法调用都指向到了我们自己定义的xxxInvocationHandler,在xxxInvocationHandler中再通过反射调用真正类的方法,这样就完成了一次动态代理的方法调用。比如,示例中自动生成类com.sun.proxy.$Proxy0文件如下所示:

代码语言:javascript
复制
package com.sun.proxy;

import com.luo.demo.boot.dyn.DynMain.Hello;
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 Hello {
    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})).booleanValue();
        } 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 String hello(String var1) throws  {
        try {
            return (String)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)).intValue();
        } 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.luo.demo.boot.dyn.DynMain$Hello").getMethod("hello", 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());
        }
    }
}

Java动态代理能满足针对接口的场景,如果对没有实现接口的动态代理该如何做呢?这个时候就该cglib上场了~

cglib动态代理

cglib是一个强大的高性能代码生成包,被广泛的用于AOP框架中,比如Spring AOP就用到了cglib(Spring中如果代理类实现了接口,就使用jdk动态代理,否则使用cglib)。

CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

cglib示例代码如下:

代码语言:javascript
复制
public static class HelloServiceImpl {
    public String hello(String msg) {
        return "hello " + msg;
    }
}

// 利用Enhancer类生成代理类
public static class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] arg, MethodProxy methodProxy)
            throws Throwable {
        return methodProxy.invokeSuper(obj, arg);
    }
}

public static void main(String[] args) {
    // 设置将cglib生成的代理类字节码生成到指定位置
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\ideas2\\cglib");

    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(HelloServiceImpl.class);
    enhancer.setCallback(new MyMethodInterceptor());

    HelloServiceImpl helloService = (HelloServiceImpl) enhancer.create();
    System.out.println(helloService.hello("world"));
}

cglib生成的代理类实际上是指定类的子类,比如上面例子cglib自动生成的代理类如下所示:

代码语言:javascript
复制
public class CglibMain$HelloServiceImpl$$EnhancerByCGLIB$$15d87b9c extends HelloServiceImpl implements Factory {
	final String CGLIB$hello$0(String var1) {
        return super.hello(var1);
    }

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

        return var10000 != null ? (String)var10000.intercept(this, CGLIB$hello$0$Method, new Object[]{var1}, CGLIB$hello$0$Proxy) : super.hello(var1);
    }
	
    // 其他方法,比如equals、toString、hashCode、clone等
}

如果cglib要代理的指定类是final类型的,也就是上例中HelloServiceImpl如果是final的,则cglib在创建子类时报异常;如果指定类中方法是final的,则创建子类是OK的,但是针对该final方法的调用不会产生动态代理的效果。

小结

Java动态代理底层基于Proxy/InvocationHandler相关类反射技术,其自动生成代理类的逻辑是生成一个代理接口的实现类(这也是只能做接口动态代理的原因),对该类中方法调用都指向到了我们自己定义的xxxInvocationHandler,在xxxInvocationHandler中再通过反射调用真正类的方法。

cglib的动态代理底层通过asm生成要代理类的子类,在该子类中调用我们自定义的xxxMethodInterceptor,在xxxMethodInterceptor中调用子类中对应方法,对应方法内部直接通过supper.xxx()调用对应要代理类的方法。

参考资料:

1、深入浅出Java反射(https://topcoder.site/2018/08/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAJava%E5%8F%8D%E5%B0%84/)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-09-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 TopCoder 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • cglib动态代理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档