JDK动态代理源码学习

一、使用示例:

1、定义DTO:

public class User {

    private String userName;

    private String passWord;

    private String email;

    public String getUserName() {

        return userName;

    }

    public void setUserName(String userName) {

        this.userName = userName;

    }

    public String getPassWord() {

        return passWord;

    }

    public void setPassWord(String passWord) {

        this.passWord = passWord;

    }

    public String getEmail() {

        return email;

    }

    public void setEmail(String email) {

        this.email = email;

    }

}

2、定义接口:

public interface UserDao {

    void addUser(User user);

}

3、实现接口:

public class UserDaoImpl implements UserDao {

    @Override

    public void addUser(User user) {

        System.out.println("connect to mySQL dataBase.......");

        System.out.println("添加用户信息成功...");

    }

}

4、实现被代理对象的增强类:

public class LogHandler implements InvocationHandler {

    private Object target;

    public LogHandler(Object target) {

        this.target = target;

    }

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("开始记录日志");

        method.invoke(target, args);

        System.out.println("日志记录完成");

        return null;

    }

}

5、调用测试:

public static void main(String[] args) {

    UserDao dao = new UserDaoImpl();

    LogHandler handler = new LogHandler(dao);

    UserDao proxy = (UserDao) Proxy.newProxyInstance(dao.getClass().getClassLoader(), dao.getClass().getInterfaces(), handler);    

    proxy.addUser(user);

}

二、动态生成的代理文件查看:

1、在调用动态代理的main方法中加上:

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

2、利用ProxyGenerator生成:

public static void main(String[] args) {

        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", UserDaoImpl.class.getInterfaces());

        String path = "E:\\$Proxy0.class";

        try(FileOutputStream fos = new FileOutputStream(path)) {

            fos.write(classFile);

            fos.flush();

            System.out.println("代理类class文件写入成功");

        } catch (Exception e) {

            System.out.println("写文件错误");

        }

}

这样在运行代码的时候就会在项目的根目录(或者指定目录)下生成 com.sun.proxy.$ProxyX.class 了,反编译查看:

public final class $Proxy0 extends Proxy implements UserDao {

    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 void addUser(User var1) throws  {

        try {

            super.h.invoke(this, m3, new Object[]{var1});

        } catch (RuntimeException | Error var3) {

            throw var3;

        } catch (Throwable var4) {

            throw new UndeclaredThrowableException(var4);

        }

    }

  

    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.ucar.test.service.UserDao").getMethod("addUser", Class.forName("com.ucar.test.dto.User"));

            m0 = Class.forName("java.lang.Object").getMethod("hashCode");

        } catch (NoSuchMethodException var2) {

            throw new NoSuchMethodError(var2.getMessage());

        } catch (ClassNotFoundException var3) {

            throw new NoClassDefFoundError(var3.getMessage());

        }

    }

}

可以看到:

1) 代理类$Proxy0Proxy派生而来,并且实现了UserDao接口;

2) static块中生成了Object类的三个方法:toStringequalshashCode,并且也生成了UserDao接口的addUser方法;

3) 调用端通过Proxy.newProxyInstance获取的对象实例也就是这个$Proxy0类型的对象,默认情况下这个类是不保存到磁盘上的,直接在内存中通过ClassLoader加载,因此在断点调试时是无法定位到这个类里面的;

4) 当调用UserDao接口的addUser方法时,其实是调用InvocationHandler.invoke方法,InvocationHandler的实现也就是我们前面实现的LogHandler类,这样就将调用转到LogHandler.invoke方法;

三、源码分析:

1、首先从Proxy.newProxyInstance开始分析:

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

        // null检查,hnull就抛出NullPointerException

        Objects.requireNonNull(h);

        // 将接口类对象数组clone一份。

        final Class<?>[] intfs = interfaces.clone();

        

        //执行权限检查

        final SecurityManager sm = System.getSecurityManager();

        if (sm != null) {

            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);

        }

        /*

         * Look up or generate the designated proxy class.

         */

         // 查找或者是生成一个特定的代理类对象

        Class<?> cl = getProxyClass0(loader, intfs);

        /*

         * Invoke its constructor with the designated invocation handler.

         */

        try {

            if (sm != null) {

                checkNewProxyPermission(Reflection.getCallerClass(), cl);

            }

            // static final 修饰的,源码: private static final Class<?>[] constructorParams ={ InvocationHandler.class };

            // 从代理类对象中查找参数为InvocationHandler的构造器

            final Constructor<?> cons = cl.getConstructor(constructorParams);

            final InvocationHandler ih = h;

            // 检测构造器是否是Public修饰,如果不是则强行转换为可以访问的。

            if (!Modifier.isPublic(cl.getModifiers())) {

                AccessController.doPrivileged(new PrivilegedAction<Void>() {

                    public Void run() {

                        cons.setAccessible(true);

                        return null;

                    }

                });

            }

            // 通过反射,将h作为参数,实例化代理类,返回代理类实例。

            return cons.newInstance(new Object[]{h});

        } catch (IllegalAccessException|InstantiationException e) {

            throw new InternalError(e.toString(), e);

        } catch (InvocationTargetException e) {

            Throwable t = e.getCause();

            if (t instanceof RuntimeException) {

                throw (RuntimeException) t;

            } else {

                throw new InternalError(t.toString(), t);

            }

        } catch (NoSuchMethodException e) {

            throw new InternalError(e.toString(), e);

        }

    }

这个方法主要工作:

1) getProxyClass0(loader, intfs)方法生成了上面的代理类$Proxy0,生成时主要根据intfs接口获取接口并生成,因此这就是JDK动态代理的类必须要定义接口的愿意;

2) cons.newInstance(new Object[]{h})利用反射技术实例化代理类,并返回实例化对象;

2、getProxyClass0方法:

/**

     * 生成一个代理类对象,

     * Generate a proxy class.  Must call the checkProxyAccess method

     * to perform permission checks before calling this.

     */

    private static Class<?> getProxyClass0(ClassLoader loader,

                                           Class<?>... interfaces) {

        // 接口类对象数组不能大于65535个,否则抛出异常

        if (interfaces.length > 65535) {

            throw new IllegalArgumentException("interface limit exceeded");

        }

        // 从代理类对象缓存中,根据类加载器和接口类对象数组查找代理类对象,

        // If the proxy class defined by the given loader implementing

        // the given interfaces exists, this will simply return the cached copy;

        // otherwise, it will create the proxy class via the ProxyClassFactory

        return proxyClassCache.get(loader, interfaces);

    }

在这个方法中,是直接从一个叫proxyClassCache缓存中读取的,来看一下这个缓存的声明:

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

看一下WeakCache类的大概结构:

final class WeakCache<K, P, V> {

    private final ReferenceQueue<K> refQueue= new ReferenceQueue<>();

    // the key type is Object for supporting null key

    // key的类型为Object,支持null key,这里的null key并不是真的可以使用null最为key,而是一个new Objdec()对象实例。ConcurrentHashMap,不允许键或值null,而HashMap可以。ConcurrentHashMap是线程安全的,HashMap不是。

    private final ConcurrentMap<Supplier<V>, Boolean> reverseMap= new ConcurrentHashMap<>();

    

    private final BiFunction<K, P, ?> subKeyFactory;

    private final BiFunction<K, P, V> valueFactory;

    //构造方法

    public WeakCache(BiFunction<K, P, ?> subKeyFactory,

                     BiFunction<K, P, V> valueFactory) {

        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);

        this.valueFactory = Objects.requireNonNull(valueFactory);

    }

    

    public V get(K key, P parameter) {

       //下面会详细介绍这个方法

    }

  ......

}

3、WeakCache.get方法:

// key是类加载器,parameter为接口类对象数组

    public V get(K key, P parameter) {

        // 接口类对象数组null检查。

        Objects.requireNonNull(parameter);

        

        // 删除过时的条目

        expungeStaleEntries();

        // 生成缓存key对象实例,如果key = nullcacheKey = new Object();

        Object cacheKey = CacheKey.valueOf(key, refQueue);

        // lazily install the 2nd level valuesMap for the particular cacheKey

        // 从缓存map中读取指定cacheKey的缓存数据valuesMap

        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);

        

        if (valuesMap == null) {

            //如果valuesMapnull,则新增

            //putIfAbsent方法解释:如果值存在则返回值,并且不对原来的值做任何更改,如果不存在则新增,并返回null

            //Absent的意思是缺席,不在

            ConcurrentMap<Object, Supplier<V>> oldValuesMap= map.putIfAbsent(cacheKey,valuesMap = new ConcurrentHashMap<>());

            if (oldValuesMap != null) {

                valuesMap = oldValuesMap;

            }

        }

        // create subKey and retrieve the possible Supplier<V> stored by that

        // subKey from valuesMap

        // 获取subKey,这里用到了上面提到的Proxy的静态内部类KeyFactory:subKeyFactory.apply(ket,parameter)

        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

        // valuesMap中获取supplier

        Supplier<V> supplier = valuesMap.get(subKey);

        Factory factory = null;

        while (true) {

            if (supplier != null) {

                // supplier might be a Factory or a CacheValue<V> instance

                // 4,从工厂中获取代理类对象

                V value = supplier.get();

                if (value != null) {

                    // 5,返回

                    return value;

                }

            }

            // else no supplier in cache

            // or a supplier that returned null (could be a cleared CacheValue

            // or a Factory that wasn't successful in installing the CacheValue)

            // lazily construct a Factory

            if (factory == null) {

                //1,实例化工厂

                factory = new Factory(key, parameter, subKey, valuesMap);

            }

            if (supplier == null) {

                //2,保存到valuesMap

                supplier = valuesMap.putIfAbsent(subKey, factory);

                if (supplier == null) {

                    // successfully installed Factory

                    // 3,赋值

                    supplier = factory;

                }

                // else retry with winning supplier

            } else {

                if (valuesMap.replace(subKey, supplier, factory)) {

                    // successfully replaced

                    // cleared CacheEntry / unsuccessful Factory

                    // with our Factory

                    supplier = factory;

                } else {

                    // retry with current supplier

                    supplier = valuesMap.get(subKey);

                }

            }

        }

    }

其中supplier就是factory

4、Factory.get方法

private final class Factory implements Supplier<V> {

        private final K key;

        private final P parameter;

        private final Object subKey;

        private final ConcurrentMap<Object, Supplier<V>> valuesMap;

        Factory(K key, P parameter, Object subKey,

                ConcurrentMap<Object, Supplier<V>> valuesMap) {

            this.key = key;

            this.parameter = parameter;

            this.subKey = subKey;

            this.valuesMap = valuesMap;

        }

        @Override

        public synchronized V get() { // serialize access

            // re-check

            // 检查

            Supplier<V> supplier = valuesMap.get(subKey);

            if (supplier != this) {

                // something changed while we were waiting:

                // might be that we were replaced by a CacheValue

                // or were removed because of failure ->

                // return null to signal WeakCache.get() to retry

                // the loop

                return null;

            }

            // else still us (supplier == this)

            // create new value

            V value = null;

            try {

                // valueFactory就是WeakCachevalueFactory属性,因为FactoryWeakCache的内部类,所以可以直接访问WeakCachevalueFactory属性

                value = Objects.requireNonNull(valueFactory.apply(key, parameter));

            } finally {

                if (value == null) { // remove us on failure

                    valuesMap.remove(subKey, this);

                }

            }

            // the only path to reach here is with non-null value

            assert value != null;

            // wrap value with CacheValue (WeakReference)

            CacheValue<V> cacheValue = new CacheValue<>(value);

            // try replacing us with CacheValue (this should always succeed)

            if (valuesMap.replace(subKey, this, cacheValue)) {

                // put also in reverseMap

                reverseMap.put(cacheValue, Boolean.TRUE);

            } else {

                throw new AssertionError("Should not reach here");

            }

            // successfully replaced us with new CacheValue -> return the value

            // wrapped by it

            return value;

        }

    }

这里最关键的代码:valueFactory.apply(key, parameter)valueFactory就是Proxy的静态内部类ProxyClassFactory

5、ProxyClassFactory.apply方法

/**

     * 一个利用给定的类加载器和接口类数组生成,定义并返回代理类对象的工厂方法

     * A factory function that generates, defines and returns the proxy class given

     * the ClassLoader and array of interfaces.

     */

    private static final class ProxyClassFactory

        implements BiFunction<ClassLoader, Class<?>[], Class<?>>

    {

        // prefix for all proxy class names

        // 所有代理类对象的前缀

        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names

        // 用于生成唯一代理类名称的下一个数字

        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) {

                /*

                 * Verify that the class loader resolves the name of this

                 * interface to the same Class object.

                 */

                Class<?> interfaceClass = null;

                try {

                    // 加载接口类,获得接口类的类对象,第二个参数为false表示不进行实例化

                    interfaceClass = Class.forName(intf.getName(), false, loader);

                } catch (ClassNotFoundException e) {

                }

                if (interfaceClass != intf) {

                    throw new IllegalArgumentException(

                        intf + " is not visible from class loader");

                }

                /*

                 * Verify that the Class object actually represents an

                 * interface.

                 */

                if (!interfaceClass.isInterface()) {

                    throw new IllegalArgumentException(

                        interfaceClass.getName() + " is not an interface");

                }

                /*

                 * Verify that this interface is not a duplicate.

                 */

                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {

                    throw new IllegalArgumentException(

                        "repeated interface: " + interfaceClass.getName());

                }

            }

            // package to define proxy class in

            // 代理类的包名

            String proxyPkg = null;     

            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*

             * Record the package of a non-public proxy interface so that the

             * proxy class will be defined in the same package.  Verify that

             * all non-public proxy interfaces are in the same package.

             */

            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) {

                // if no non-public proxy interfaces, use com.sun.proxy package

                proxyPkg = com.sun.proxy package

                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";

            }

            /*

             * 生成代理类的类名

             * Choose a name for the proxy class to generate.

             */

            long num = nextUniqueNumber.getAndIncrement();

            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*

             * Generate the specified proxy class.

             */

            //生成代理类class文件

            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

            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());

            }

        }

    }

}

这里的ProxyGenerator.generateProxyClass生成了代理类的字节码文件;

6、ProxyGenerator.generateProxyClass方法:

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() {

            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),

                                                                               new String[0]);

                        Files.createDirectories(var3, new FileAttribute[0]);

                        var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");

                    } else {

                        var2 = Paths.get(var0 + ".class", new String[0]);

                    }

                    Files.write(var2, var4, new OpenOption[0]);

                    return null;

                } catch (IOException var4x) {

                    throw new InternalError("I/O exception saving generated file: " + var4x);

                }

            }

        });

    }

    return var4;

}

其中generateClassFile是真正用于生成代理类字节码文件的方法;saveGeneratedFiles判断是否要将代理类字节码文件保存到本地磁盘文件,其定义为:

private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));

可以看到saveGeneratedFiles是从系统环境变量中读取的”sun.misc.ProxyGenerator.saveGeneratedFiles”变量并转换成布尔类型;

7、generateClassFile方法:

private byte[] generateClassFile() {

    //下面一系列的addProxyMethod方法是将接口中的方法和Object中的方法添加到代理方法中(proxyMethod)

    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);

        }

    }

    Iterator var11 = this.proxyMethods.values().iterator();

    //验证具有相同方法签名的方法的返回类型是否一致

    List var12;

    while(var11.hasNext()) {

        var12 = (List)var11.next();

        checkReturnTypes(var12);

    }

    //后面一系列的步骤用于写代理类Class文件

    Iterator var15;

    try {

         //生成代理类的构造函数

        this.methods.add(this.generateConstructor());

        var11 = this.proxyMethods.values().iterator();

        while(var11.hasNext()) {

            var12 = (List)var11.next();

            var15 = var12.iterator();

            while(var15.hasNext()) {

                ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();

                //将代理类字段声明为Method,并且字段修饰符为 private static.

               //因为 10 ACC_PRIVATEACC_STATIC的与运算 故代理类的字段都是 private static Method ***

                this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName,

                               "Ljava/lang/reflect/Method;", 10));

               //生成代理类的方法

                this.methods.add(var16.generateMethod());

            }

        }

       //为代理类生成静态代码块对某些字段进行初始化

        this.methods.add(this.generateStaticInitializer());

    } catch (IOException var10) {

        throw new InternalError("unexpected I/O Exception", var10);

    }

    if(this.methods.size() > '\uffff') { //代理类中的方法数量超过65535就抛异常

        throw new IllegalArgumentException("method limit exceeded");

    } else if(this.fields.size() > '\uffff') {// 代理类中字段数量超过65535也抛异常

        throw new IllegalArgumentException("field limit exceeded");

    } else {

        // 后面是对文件进行处理的过程

        this.cp.getClass(dotToSlash(this.className));

        this.cp.getClass("java/lang/reflect/Proxy");

        var1 = this.interfaces;

        var2 = var1.length;

        for(var3 = 0; var3 < var2; ++var3) {

            var4 = var1[var3];

            this.cp.getClass(dotToSlash(var4.getName()));

        }

        this.cp.setReadOnly();

        ByteArrayOutputStream var13 = new ByteArrayOutputStream();

        DataOutputStream var14 = new DataOutputStream(var13);

        try {

            var14.writeInt(-889275714);

            var14.writeShort(0);

            var14.writeShort(49);

            this.cp.write(var14);

            var14.writeShort(this.accessFlags);

            var14.writeShort(this.cp.getClass(dotToSlash(this.className)));

            var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));

            var14.writeShort(this.interfaces.length);

            Class[] var17 = this.interfaces;

            int var18 = var17.length;

            for(int var19 = 0; var19 < var18; ++var19) {

                Class var22 = var17[var19];

                var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));

            }

            var14.writeShort(this.fields.size());

            var15 = this.fields.iterator();

            while(var15.hasNext()) {

                ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();

                var20.write(var14);

            }

            var14.writeShort(this.methods.size());

            var15 = this.methods.iterator();

            while(var15.hasNext()) {

                ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();

                var21.write(var14);

            }

            var14.writeShort(0);

            return var13.toByteArray();

        } catch (IOException var9) {

            throw new InternalError("unexpected I/O Exception", var9);

        }

    }

}

其中addProxyMethod实现将接口与Object中一些方法添加到代理类中;

8、addProxyMethod

private void addProxyMethod(Method var1, Class<?> var2) {

    String var3 = var1.getName();//获得方法名称

    Class[] var4 = var1.getParameterTypes();//获得方法参数类型

    Class var5 = var1.getReturnType();//获得方法返回类型

    Class[] var6 = var1.getExceptionTypes();//异常类型

    String var7 = var3 + getParameterDescriptors(var4);//获得方法签名

    Object var8 = (List)this.proxyMethods.get(var7);//根据方法前面获得proxyMethodvalue

    if(var8 != null) {//处理多个代理接口中方法重复的情况

        Iterator var9 = ((List)var8).iterator();

        while(var9.hasNext()) {

            ProxyGenerator.ProxyMethod var10 = (ProxyGenerator.ProxyMethod)var9.next();

            if(var5 == var10.returnType) {

                ArrayList var11 = new ArrayList();

                collectCompatibleTypes(var6, var10.exceptionTypes, var11);

                collectCompatibleTypes(var10.exceptionTypes, var6, var11);

                var10.exceptionTypes = new Class[var11.size()];

                var10.exceptionTypes = (Class[])var11.toArray(var10.exceptionTypes);

                return;

            }

        }

    } else {

        var8 = new ArrayList(3);

        this.proxyMethods.put(var7, var8);

    }

    ((List)var8).add(new ProxyGenerator.ProxyMethod(var3, var4, var5, var6, var2, null));

}

JDK动态代理的原理是根据定义好的规则,用传入的接口创建一个新类,JDK的动态代理只能代理接口中的方法,是针对接口生成代理类。这就是为什么采用动态代理时为什么只能用接口引用指向代理,而不能用传入的类引用执行动态类。

四、参考资料:

https://www.jianshu.com/p/471c80a7e831

https://blog.csdn.net/u012834750/article/details/82499648

https://www.cnblogs.com/zhangchengzi/p/9713807.html

猜你喜欢

转载自www.cnblogs.com/laoxia/p/11121962.html