关于javascript:设计模式32-JDK动态代理源码分析有多香

27次阅读

共计 9053 个字符,预计需要花费 23 分钟才能阅读完成。

后面文章有说到代理模式:http://aphysia.cn/archives/dy…

那么回顾一下,代理模式怎么来的?假如有个需要:

在零碎中所有的 controller 类调用办法之前以及之后,打印一下日志。

假如原来的代码:

public class Person{public void method(){
        // 示意本人的业务逻辑
        process();}
}

如果在所有的类外面都增加打印办法,这样必定是不事实的,如果我有几百个这样的类,写到解体,况且反复代码太多,冗余,还耦合到一块了,要是我下次不打日志了,做其余的,那几百个类又全副改一遍。

public class Person{public void method(){log();
        // 示意本人的业务逻辑
        process();
        log();}
}

动态代理

怎么样写比拟柔美呢? 动态代理 这时候出场了,先把办法形象成为接口:

public class IProxy(){public void method();
}

让具体的类去实现 IProxy,写本人的业务逻辑,比方:

public class Person implements IProxy(){public void method(){
        // 示意本人的业务逻辑
        process();}
}

而后弄个代理类,对办法进行加强:

public class PersonProxy implements IProxy{
    private IProxy target;
    public PersonProxy(IProxy target){this.target = target;}
    @Override
    public void method() {log();
        target.method();
        log();}
}

调用的时候,把实在的对象放到代理类的结构器外面,就能够失去一个代理类,对它的办法进行加强,益处就是,如果下次我要改,不打日志,做其余事件,我改代理类就能够了,不必到处改我的指标类的办法,而害处还是很显著,要加强哪一个类,就要为它写一个代理类,这样如同不是很正当。

动静代理

怎么样能让他主动生成代理对象呢? 动静代理做的就是这个事件,它能够 动静 的依据咱们提供的类,生成代理类的对象。

最次要的,是在运行时,动静生成,只有传入咱们要代理加强的类相干的信息,比方类对象自身,类加载器,类的接口等,就能够生成,不必提前晓得它是 A 类,B 类还是 C 类。

动静代理次要有三种实现办法,明天咱们重点剖析 JDK 动静代理:

  • JDK 代理:应用 JDK 提供的官网的 Proxy
  • 第三方 CGlib 代理:应用 CGLib 的 Enhancer 类创立代理对象
  • javassit:Javassist 是一个开源的剖析、编辑和创立 Java 字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba(千叶 滋)所创立的。

JDK 动静代理

应用步骤

  1. 新建一个接口
  2. 新建一个类,实现该接口
  3. 创立代理类,实现 java.lang.reflect.InvocationHandler 接口

代码如下:

IPlayDao.java(玩的接口)

public interface IPlayDao {void play();
}

StudentDao.java(实现了买货色,玩的接口的学生类)

public class StudentDao implements IPlayDao {
    @Override
    public void play() {System.out.println("我是学生,我想出去玩");
    }
}

MyProxy.java 代理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyProxy {
    private Object target;
    public MyProxy(Object target){this.target=target;}
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    // 一个接口可能很多办法,要是须要针对某一个办法,那么须要在函数里判断 method
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开始事务 2");
                        // 执行指标对象办法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("提交事务 2");
                        return returnValue;
                    }
                }
        );
    }
}

测试类(Test.java)

public class Test {public static void main(String [] args){
        // 保留生成代理类的字节码文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        IPlayDao target =new StudentDao();
        System.out.println(target.getClass());
        IPlayDao proxy = (IPlayDao) new MyProxy(target).getProxyInstance();
        System.out.println(proxy.getClass());
        // 执行办法【代理对象】proxy.play();}
}

因为加了这句代码,咱们能够把生成的代理类的字节码文件保留下来, 其实通过输入也能够看到,两个对象不是同一个类,代理类是动静生成的:

System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);


![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924001916.png)

### 源码剖析

跟着源码一步步看,先从调用的中央 `Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)`:![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924002757.png)

进入办法外面,** 省略各种异样解决 **,次要剩下了 ** 生成代理类字节码 ** 以及 ** 通过构造函数反射结构新对象 **:
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    // 判空
    Objects.requireNonNull(h);
    // 安全检查
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * 查找或者生成代理对象
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);
    }

    // 获取结构器
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    final InvocationHandler ih = h;
    if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);
                return null;
            }
        });
    }
    // 反射结构代理对象
    return cons.newInstance(new Object[]{h});
}

下面正文外面说查找或者生成代理对象,为什么有查找?因为并不是每一次都生成,生成的代理对象实际上会缓存起来,如果没有,才会生成,看源码 `Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces)`:
private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {if (interfaces.length> 65535) {throw new IllegalArgumentException("interface limit exceeded");
    }
    // 调用缓存代理类的 cache 来获取类加载器
    return proxyClassCache.get(loader, interfaces);
}

如果由实现给定接口的给定加载器定义的代理类存在,这将简略地返回缓存的正本; 否则,它将通过 ProxyClassFactory 创立代理类,`proxyClassCache` 其实就是个 `weakCache`:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());


初始化的时候,proxyClassCache 指定了两个属性,一个是 `KeyFactory`, 另外一个是 `ProxyClassFactory`, 从名字就是猜到 `ProxyClassFactory` 是代理类工厂:
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                 BiFunction<K, P, V> valueFactory) {this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
    this.valueFactory = Objects.requireNonNull(valueFactory);
}
** 记住这里的 subKeyFactory,实际上就是传入的 ProxyClassFactory**,那后面 `proxyClassCache.get(loader, interfaces);` 到底是怎么操作的?![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924010200.png)

下面调用到了 `subKeyFactory.apply(key, parameter)`,这个 `subKeyFactory` 实际上是咱们传的 `ProxyClassFactory`, 进入外面去看:
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // 生成的代理类前缀
    private static final String proxyClassNamePrefix = "$Proxy";

    // 下一个生成的代理类的名字的计数器,个别是 $Proxy0,$Proxy1
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            /*
             * 测验类加载器是否能通过接口名称加载该类
             */
            Class<?> interfaceClass = null;
            try {interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) { }
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(intf + "is not visible from class loader");
            }
            /*
             * 判断接口类型
             */
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(interfaceClass.getName() + "is not an interface");
            }
            /*
             * 判断是否反复
             */
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException("repeated interface:" + interfaceClass.getName());
            }
        }
        // 代理包名字
        String proxyPkg = null;
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        /*
         * 记录非公共代理接口的包,以便在同一个包中定义代理类。确认所有非公共代理接口都在同一个包中。*/
        for (Class<?> intf : interfaces) {int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {proxyPkg = pkg;} else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException("non-public interfaces from different packages");
                }
            }
        }

        if (proxyPkg == null) {
            // 如果没有非公共代理接口,请应用 com.sun.proxy 包
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        /*
         * 为要生成的代理类抉择一个名称。*/
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        /*
         * 生成指定的代理类。(这里是重点!!!)*/
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
        try {
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            /*
             * 这里的 ClassFormatError 意味着 (禁止代理类生成代码中的谬误) 提供给代理类创立的参数有其余一些有效方面 (比方超出了虚拟机限度)。*/
            throw new IllegalArgumentException(e.toString());
        }
    }
}

下面调用一个办法生成代理类,咱们看看 IDEA 反编译的代码:
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) {
        // 开启高权限写入
        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;
}
 生成代理文件实际上和咱们想的差不多,就是一些 hashCode(),toString(),equals(), 原办法,代理办法等:![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924011447.png)

这与咱们之前看到的文件统一:![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210924011628.png)

而后之所以咱们代码要设置写入磁盘,是因为这个变量, 管制了写磁盘操作:
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));

** 为什么只反对接口实现,不反对继承一般类?**
因为代理类继承了 Proxy 类,并且实现了接口,Java 不容许多继承,所以不能代理一般类的形式,并且在动态代码块外面,用反射形式获取了所有的代理办法。JDK 代理看起来像是个黑盒,实际上,每一句代码,都有其原因。其实实质上也是动静的为咱们的原始类,动静生成代理类。生成的代理类外面其实对原始类进行加强(比方 `play()` 办法)的时候, 调用了 `super.h.invok()` 办法,其实这里的 `h` 是什么呢?`h` 是父类的 `h`, 生成的代理类的父类是 `Proxy`,Proxy 的 `h`,就是咱们传入的 `InvocationHandler`:
public final void play() throws  {
    try {super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
    }

生成的代码外面通过反射调用到的其实是咱们本人重写的那局部逻辑,所以就有了加强的性能,不得不说,这种设计的确奇妙:![](https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20210925020135.png)

## 动静代理有多香

动静代理是 Java 语言外面的一个很弱小的个性,能够用来做一些切面,比方拦截器,登录验证等等,然而它并不是独立存在的,任何一个知识点都不能独立阐明语言的弱小,重要的是它们的组合。动静代理要实现弱小的性能,个别须要和反射,注解等一起单干,比方对某些申请进行拦挡,拦挡后做一些登录验证,或者日志性能。最重要的一点,它可能在缩小耦合度的前提下实现加强。**【作者简介】**:秦怀,公众号【** 秦怀杂货店 **】作者,技术之路不在一时,山高水长,纵使迟缓,驰而不息。集体写作方向:`Java 源码解析 `,`JDBC`,`Mybatis`,`Spring`,`redis`,` 分布式 `,` 剑指 Offer`,`LeetCode` 等,认真写好每一篇文章,不喜爱题目党,不喜爱花里胡哨,大多写系列文章,不能保障我写的都完全正确,然而我保障所写的均通过实际或者查找材料。脱漏或者谬误之处,还望斧正。[剑指 Offer 全副题解 PDF](http://aphysia.cn/archives/jianzhiofferpdf)

[2020 年我写了什么?](http://aphysia.cn/archives/2020)

[开源编程笔记](https://damaer.github.io/Coding/#/)

正文完
 0