JAVA 动态代理

来源:互联网 发布:centos 6.5 ftp客户端 编辑:程序博客网 时间:2024/05/23 16:56

关于JDK Proxy的动态代理使用,网上有很多文章,本文就不再写了,我想从几个问题出发,总结一下JDK的动态代理

  1. newProxyInstance三个参数起了什么作用
  2. JDK的动态代理为什么是基于interface的而不是基于类的
  3. 动态代理是如何进行分派转发的
  4. 要代理的接口中如果出现了重名且参数类型也相同的方法时,会不会出现问题

动态代理其实就是将你要委托的类和你的代理处理器(即InvocationHandler的实现)编织成一个全新的代理类,这个过程由JVM去做而不是手动写代理类,简单来说就是:
动态代理类 = 委托类+代理处理器

newProxyInstance

首先newProxyInstance为我们封装了使用动态代理机制的各种方法,它的方法签名是

public static Object newProxyInstance(    ClassLoader loader,     Class<?>[] interfaces,     InvocationHandler h) throws IllegalArgumentException

这个方法里有主要的三个部分
1、Class<?> cl = getProxyClass0(loader, intfs); 得到代理类的Class对象,生成代理类的工作就在这个方法里进行
2、final Constructor<?> cons = cl.getConstructor(constructorParams); 得到代理类的Constructor
3、return cons.newInstance(new Object[]{h}); 返回代理类的对象

由此可见核心部分在getProxyClass0 中,方法很简单,对interfaces数组做了简单的长度判断后,return proxyClassCache.get(loader, interfaces);
proxyClassCache被定义为
new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
传入两个参数,一个是生成key的工厂,一个是生成代理类的工厂

WeakCache

WeakCache的注释是这样描述这个类的

Cache mapping pairs of { (key, sub-key) -> value}. Keys and values are weakly but sub-keys are strongly referenced. Keys are passed directly to Get method which also takes a parameter. Sub-keys are calculated from keys and parameters using the subKeyFactory function which passed to the constructor. Values are calculated from keys and parameters using the valueFactory function witch passed to the constructor. Keys can be null and are compared by identity while sub-keys returned by subKeyFactory or values returned by valueFactory can not be null. Sub-keys are compared using their Equals method. Entries are expunge from cache lazily on each invocation to Get.

就是说这个Cache的数据结构是一个二级map (key, sub-key) -> value,即成员变量map的定义
private final
ConcurrentMap<Object,ConcurrentMap<Object,Supplier<V>>> map = new ConcurrentHashMap<>();

注释里接下去写到,第一级的key只是粗略的指向下一级:

在WeakCache的get方法中,一级key就是根据ClassLoader生成的
Object cacheKey = CacheKey.valueOf(key, refQueue); 虽然有两个参数,但第二个参数在实例化方时并没有赋值,也就是null)

而二级sub-key是精确指向一个对象,即代理类的Class对象,sub-key的生成是由KeyFactory()的apply方法完成的,入参是ClassLoader classLoaderClass<?>[] interfaces,但classLoader并没有用到,凭对interfaces的hashcode再hash得到sub-key,代理类的Class对象由ProxyClassFactory()的apply方法生成,核心块是

/* * Generate the specified proxy class. */byte[] proxyClassFile = ProxyGenerator.generateProxyClass(    proxyName, interfaces, accessFlags);//生成代理类字节码,至于要不要写入文件到classpath,由一个参数控制try {    return defineClass0(loader, proxyName, proxyClassFile,        0, proxyClassFile.length); //加载代理类,生成代理类的类对象} catch (ClassFormatError e) {    /*     * A ClassFormatError here means that (barring bugs in the     * proxy class generation code) there was some other     * invalid aspect of the arguments supplied to the proxy     * class creation (such as virtual machine limitations     * exceeded).     */    throw new IllegalArgumentException(e.toString());}

generateProxyClass

我们再向内部探究一步,代理类的字节码都包含哪些东西,换句话说生成的代理类class文件里面都有什么,粗略看一下generateProxyClass这个方法,返回值是final byte[] var4 = var3.generateClassFile(); 我截取了一小段

this.addProxyMethod(hashCodeMethod, Object.class);this.addProxyMethod(equalsMethod, Object.class);this.addProxyMethod(toStringMethod, Object.class);Class[] var1 = this.interfaces;int var2 = var1.length;int var3;Class var4;for(var3 = 0; var3 < var2; ++var3) {    var4 = var1[var3];    Method[] var5 = var4.getMethods();    int var6 = var5.length;    for(int var7 = 0; var7 < var6; ++var7) {        Method var8 = var5[var7];        this.addProxyMethod(var8, var4);    }}

大概意思就是通过addProxyMethod向代理类中添加各种代理方法,里面用到的都是反射的那些方法,最后生成的代理类会继承Proxy这个类并实现我们在newProxyInstance提供的Class<?>[] interfaces 的所有接口,对接口的实现方法中通过调用InvocationHandler h 的invoke方法,完成代理

至此开头提到的那三个问题的答案就有了:

  1. newProxyInstance三个参数起了什么作用

    Classloader class: 参与了WeakCache的一级key生成,并且代理类类对象的加载器就是这个Classloader,网上的一些文章通常会将诸如委托接口的加载器、main方法所在类的类加载器传入,实际上都是传入了Thread.currentThread().getContextClassLoader()
    Class<?>[] interfaces:代理类要实现的所有接口
    InvocationHandler h:提供invoke方法,可以做一些对委托类方法调用的监控操作

  2. JDK的动态代理为什么是基于interface的而不是基于类的

    主要有两个原因,1、代理类都继承Proxy,由于java不支持多继承,因此在机制上做不到;2、如果要基于类来做,那么类中的重要方法比如hashCode、equals就要被覆盖掉,很容易引发新的问题

  3. 动态代理是如何进行分派转发的

    生成的代理类通过调用InvocationHandler的invoke方法来完成

  4. 要代理的接口中如果出现了重名且参数类型也相同的方法时,会不会出现问题

    出现这种情况时,代理类总是从排在最前面的接口中获取方法对象并分派给InvocationHandler, 因此要避免代理具有相同方法的接口


补充:Spring的AOP

实现在DefaultAopProxyFactory类中

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {    if(!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {        return new JdkDynamicAopProxy(config);    } else {        Class targetClass = config.getTargetClass();        if(targetClass == null) {            throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");        } else {            return (AopProxy)(targetClass.isInterface()?new JdkDynamicAopProxy(config):new ObjenesisCglibAopProxy(config));        }    }}

如果要代理的是接口,用JDK的动态代理,否则用CGLib

原创粉丝点击