zoukankan      html  css  js  c++  java
  • java代理,手把手交你写java代理

    一:常用的java代理模式

            一般经常做java开发的知道java的代理模式一共有三种,第一种也就是静态代理,这种用法比较简单,没有什么魔法棒,比较好理解,另外两种分别是JDK代理和cglib代理,他们分别是对接口代理和对class类本身进行代理,jdk代理要求类必须实现有一个或者多个接口,对接口进行字节码增强在内存中实现新的class类去反射调用用户target的实现类,这里需要说明的是不管是cglic代理也好还是jdk代理他们在内存中都要占据方法区资源(jdk8 叫原空间),从而达到代理目的,而cglib代理是对class类本身进行字节码增强配合fastclass来实现代理,关于更多的cglib和jdk代理相关的内容大家可以google搜索一下,网上有很多这里不做再多的说明。下面我们摒弃jdk,和cglib的复杂源码来自己实现一个代理模式,来更深刻的了解一下代理究竟是怎么形成的。

    二:java原生jdk代理demo和源码分析

           代理模式是指给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。这种模式有什么用呢?它可以在原对象的基础上增强原对象的功能,比如在原对象调用一个方法的前后进行日志、事务操作等。Spring AOP就使用了代理模式。如何实现代理模式呢?首先来看静态代理。静态代理是指在程序运行前就已经存在的编译好的代理类是为静态代理。实现静态代理有四个步骤:
         ①定义业务接口;
         ②被代理类实现业务接口;
         ③定义代理类并实现业务接口;
         ④最后便可通过客户端进行调用。(这里可以理解成程序的main方法里的内容)
         我们按照这个步骤去实现静态代理。需求:在向数据库添加一个用户时前后打印日志。
     
    JDK DEMO示例
     
    IUserService.java
    public interface IUserService {
        void add(String name);  
    }
    

    UserServiceImpl.java 

    public class UserServiceImpl implements IUserService{
    
        @Override
        public void add(String name) {
            System.out.println("数据库中插入:  "+name+" 的用户");
        }
    
    }

    MyInvocationHandler.java
    public class MyInvocationHandler implements InvocationHandler {
        //被代理对象,Object类型
        private Object target;
        
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            System.out.println("准备向数据库中插入数据");
            Object returnvalue = method.invoke(target, args);
            System.out.println("插入数据库成功");
    
            return returnvalue;
        }
    }
    

      

    测试类

    public static void main(String[] args) {
    
            IUserService target = new UserServiceImpl();
            MyInvocationHandler handler = new MyInvocationHandler(target);
            IUserService proxyObject = (IUserService) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(),
                    target.getClass().getInterfaces(), handler);
            proxyObject.add("张玉龙");
        }
    

      使用上非常简单、网上demo也很多,不做充分讲解,对jdk代理用法的小伙伴如果还不熟悉这块代码,就先了解一下jdk代理的使用方式,然后在回来继续看下面的源码分析


    JDK代理源码深度分析
     
    这部分如果想要更快更好的理解,建议一边对着源码(本文JDK 1.8),一边看着博客。毕竟自己亲身实践效果才好嘛。Proxy.newProxyInstance( ClassLoaderloader, Class[] interfaces, InvocationHandler h)产生了代理对象,所以我们进到newProxyInstance的实现:
     
    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);
        }
    
        /*
         * 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);
            }
    
            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});
        } 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);
        }
    }
    

      

     
     
    这段代码核心就是通过getProxyClass0(loader, intfs)得到代理类的Class对象,然后通过Class对象得到构造方法,进而创建代理对象。下一步看getProxyClass0这个方法。

    //此方法也是Proxy类下的方法
        private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            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
            //意思是:如果代理类被指定的类加载器loader定义了,并实现了给定的接口interfaces,
            //那么就返回缓存的代理类对象,否则使用ProxyClassFactory创建代理类。
            return proxyClassCache.get(loader, interfaces);
        }
    

     这里看到proxyClassCache,有Cache便知道是缓存的意思,正好呼应了前面Look up or generate the designated proxy class。查询(在缓存中已经有)或生成指定的代理类的class对象这段注释。

    proxyClassCache是个WeakCache类的对象,调用proxyClassCache.get(loader, interfaces); 可以得到缓存的代理类或创建代理类(没有缓存的情况)。说明WeakCache中有get这个方法。先看下WeakCache类的定义(这里先只给出变量的定义和构造函数):

     
    //K代表key的类型,P代表参数的类型,V代表value的类型。
    // WeakCache<ClassLoader, Class<?>[], Class<?>>  proxyClassCache  说明proxyClassCache存的值是Class<?>对象,正是我们需要的代理类对象。
    final class WeakCache<K, P, V> {
    
        private final ReferenceQueue<K> refQueue
            = new ReferenceQueue<>();
        // the key type is Object for supporting null key
        private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
            = new ConcurrentHashMap<>();
        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);
        }
    

      

     其中map变量是实现缓存的核心变量,他是一个双重的Map结构: (key, sub-key) -> value。其中key是传进来的Classloader进行包装后的对象,sub-key是由WeakCache构造函数传人的KeyFactory()生成的。value就是产生代理类的对象,是由WeakCache构造函数传人的ProxyClassFactory()生成的

     好,大体上说完WeakCache这个类的作用,我们回到刚才proxyClassCache.get(loader, interfaces);这句代码。get是WeakCache里的方法。源码如下
    //K和P就是WeakCache定义中的泛型,key是类加载器,parameter是接口类数组
    public V get(K key, P parameter) {
            //检查parameter不为空
            Objects.requireNonNull(parameter);
             //清除无效的缓存
            expungeStaleEntries();
            // cacheKey就是(key, sub-key) -> value里的一级key,
            Object cacheKey = CacheKey.valueOf(key, refQueue);
    
            // lazily install the 2nd level valuesMap for the particular cacheKey
            //根据一级key得到 ConcurrentMap<Object, Supplier<V>>对象。如果之前不存在,则新建一个ConcurrentMap<Object, Supplier<V>>和cacheKey(一级key)一起放到map中。
            ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
            if (valuesMap == null) {
                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
            //这部分就是调用生成sub-key的代码,上面我们已经看过怎么生成的了
            Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
            //通过sub-key得到supplier
            Supplier<V> supplier = valuesMap.get(subKey);
            //supplier实际上就是这个factory
            Factory factory = null;
    
            while (true) {
                //如果缓存里有supplier ,那就直接通过get方法,得到代理类对象,返回,就结束了,一会儿分析get方法。
                if (supplier != null) {
                    // supplier might be a Factory or a CacheValue<V> instance
                    V value = supplier.get();
                    if (value != null) {
                        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
                //下面的所有代码目的就是:如果缓存中没有supplier,则创建一个Factory对象,把factory对象在多线程的环境下安全的赋给supplier。
                //因为是在while(true)中,赋值成功后又回到上面去调get方法,返回才结束。
                if (factory == null) {
                    factory = new Factory(key, parameter, subKey, valuesMap);
                }
    
                if (supplier == null) {
                    supplier = valuesMap.putIfAbsent(subKey, factory);
                    if (supplier == null) {
                        // successfully installed Factory
                        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);
                    }
                }
            }
        }
    

      所以接下来我们看Factory类中的get方法。

     public synchronized V get() { // serialize access
                // re-check
                Supplier<V> supplier = valuesMap.get(subKey);
                //重新检查得到的supplier是不是当前对象
                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生成的
                     //valueFactory就是我们传入的 new ProxyClassFactory()
                    //一会我们分析ProxyClassFactory()的apply方法
                    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)
                //把value包装成弱引用
                CacheValue<V> cacheValue = new CacheValue<>(value);
    
                // put into reverseMap
                // reverseMap是用来实现缓存的有效性
                reverseMap.put(cacheValue, Boolean.TRUE);
    
                // try replacing us with CacheValue (this should always succeed)
                if (!valuesMap.replace(subKey, this, cacheValue)) {
                    throw new AssertionError("Should not reach here");
                }
    
                // successfully replaced us with new CacheValue -> return the value
                // wrapped by it
                return value;
            }
        }
    

      拨云见日,来到ProxyClassFactory的apply方法,代理类就是在这里生成的。

     //这里的BiFunction<T, U, R>是个函数式接口,可以理解为用T,U两种类型做参数,得到R类型的返回值
    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 {
                        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());
                    }
                }
                //生成的代理类的包名 
                String proxyPkg = null;     // package to define proxy class in
                //代理类访问控制符: public ,final
                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.
                 */
                //验证所有非公共的接口在同一个包内;公共的就无需处理
                //生成包名和类名的逻辑,包名默认是com.sun.proxy,类名默认是$Proxy 加上一个自增的整数值
                //如果被代理类是 non-public proxy interface ,则用和被代理类接口一样的包名
                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 = ReflectUtil.PROXY_PACKAGE + ".";
                }
    
                /*
                 * Choose a name for the proxy class to generate.
                 */
                long num = nextUniqueNumber.getAndIncrement();
                //代理类的完全限定名,如com.sun.proxy.$Proxy0.calss
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
                /*
                 * Generate the specified proxy class.
                 */
                //核心部分,生成代理类的字节码
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
                try {
                    //把代理类加载到JVM中,至此动态代理过程基本结束了
                    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());
                }
            }
        }
    

      到这里其实已经分析完了,但是本着深究的态度,决定看看JDK生成的动态代理字节码是什么,于是我们将字节码保存到磁盘上的class文件中。代码如下:

     public static void main(String[] args) {
    
            IUserService target = new UserServiceImpl();
            MyInvocationHandler handler = new MyInvocationHandler(target);
            //第一个参数是指定代理类的类加载器(我们传入当前测试类的类加载器)
            //第二个参数是代理类需要实现的接口(我们传入被代理类实现的接口,这样生成的代理类和被代理类就实现了相同的接口)
            //第三个参数是invocation handler,用来处理方法的调用。这里传入我们自己实现的handler
            IUserService proxyObject = (IUserService) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(),
                    target.getClass().getInterfaces(), handler);
            proxyObject.add("张玉龙");
            
            String path = "D:/$Proxy0.class";
            byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", HelloworldImpl.class.getInterfaces());
            FileOutputStream out = null;
    
            try {
                out = new FileOutputStream(path);
                out.write(classFile);
                out.flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
        }
    

       运行这段代码,会在D盘生成一个名为$Proxy0.class的文件。通过反编译工具,得到JDK为我们生成的代理类是这样的:

    // Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
    // Jad home page: http://kpdus.tripod.com/jad.html
    // Decompiler options: packimports(3) fieldsfirst ansi space 
    
    import com.zhb.jdk.proxy.IUserService;
    import java.lang.reflect.*;
    
    public final class $Proxy0 extends Proxy
        implements IUserService
    {
    
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
        //代理类的构造函数,其参数正是是InvocationHandler实例,
        //Proxy.newInstance方法就是通过通过这个构造函数来创建代理实例的
        public $Proxy0(InvocationHandler invocationhandler)
        {
            super(invocationhandler);
        }
         // Object类中的三个方法,equals,toString, hashCode
        public final boolean equals(Object obj)
        {
            try
            {
                return ((Boolean)super.h.invoke(this, m1, new Object[] {
                    obj
                })).booleanValue();
            }
            catch (Error ) { }
            catch (Throwable throwable)
            {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final String toString()
        {
            try
            {
                return (String)super.h.invoke(this, m2, null);
            }
            catch (Error ) { }
            catch (Throwable throwable)
            {
                throw new UndeclaredThrowableException(throwable);
            }
        }
        //接口代理方法
        public final void add(String s)
        {
            try
            {
                // invocation handler的 invoke方法在这里被调用
                super.h.invoke(this, m3, new Object[] {
                    s
                });
                return;
            }
            catch (Error ) { }
            catch (Throwable throwable)
            {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final int hashCode()
        {
            try
            {
                // 在这里调用了invoke方法。
                return ((Integer)super.h.invoke(this, m0, null)).intValue();
            }
            catch (Error ) { }
            catch (Throwable throwable)
            {
                throw new UndeclaredThrowableException(throwable);
            }
        }
        
        // 静态代码块对变量进行一些初始化工作
        static 
        {
            try
            {
                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                    Class.forName("java.lang.Object")
                });
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m3 = Class.forName("com.zhb.jdk.proxy.IUserService").getMethod("add", new Class[] {
                    Class.forName("java.lang.String")
                });
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            }
            catch (NoSuchMethodException nosuchmethodexception)
            {
                throw new NoSuchMethodError(nosuchmethodexception.getMessage());
            }
            catch (ClassNotFoundException classnotfoundexception)
            {
                throw new NoClassDefFoundError(classnotfoundexception.getMessage());
            }
        }
    }
    

    生成了Object类的三个方法:toString,hashCode,equals。还有我们需要被代理的方法。

    JDK代理类的cache clear机制

    大家都知道、在项目中被代理的class越来越多,所以jdk会搞一个cache的方式来防止相同的代理接口重复生成class,影响性能不说,实现也不是很优雅,那么现在就会有一个问题了,当classloader已经在内存中没有依赖的时候,被代理的proxy class其实也没有什么意义了,这样就需要清空无用的cache,java Proxy采用了非常巧妙的“弱引用机制”,我们来看下面的代码

    我们还是继续看get方法的源码

    public V get(K key, P parameter) {
            Objects.requireNonNull(parameter);
    
            expungeStaleEntries();
    
            Object cacheKey = CacheKey.valueOf(key, refQueue);
    
            // lazily install the 2nd level valuesMap for the particular cacheKey
            ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
            if (valuesMap == null) {
                ConcurrentMap<Object, Supplier<V>> oldValuesMap
                    = map.putIfAbsent(cacheKey,
                                      valuesMap = new ConcurrentHashMap<>());
                if (oldValuesMap != null) {
                    valuesMap = oldValuesMap;
                }
            }
    .......
    }

    其中源码中有一个方法expungeStaleEntries、我们进去这个方法一窥究竟

     private void expungeStaleEntries() {
            CacheKey<K> cacheKey;
            while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
                cacheKey.expungeFrom(map, reverseMap);
            }
        }
    

     在看看expungeFrom方法源码干了些什么

    void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
                             ConcurrentMap<?, Boolean> reverseMap) {
                // removing just by key is always safe here because after a CacheKey
                // is cleared and enqueue-ed it is only equal to itself
                // (see equals method)...
                ConcurrentMap<?, ?> valuesMap = map.remove(this);
                // remove also from reverseMap if needed
                if (valuesMap != null) {
                    for (Object cacheValue : valuesMap.values()) {
                        reverseMap.remove(cacheValue);
                    }
                }
            }
    

      代码很清晰了,清空被代理的对象。现在的关键就是refQueue对象是怎么来的。我们继续找一下跟refQueue相关的源码、在get中还有一段代码是这样的

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

     

     private static final class CacheKey<K> extends WeakReference<K> {
    
            // a replacement for null keys
            private static final Object NULL_KEY = new Object();
    
            static <K> Object valueOf(K key, ReferenceQueue<K> refQueue) {
                return key == null
                       // null key means we can't weakly reference it,
                       // so we use a NULL_KEY singleton as cache key
                       ? NULL_KEY
                       // non-null key requires wrapping with a WeakReference
                       : new CacheKey<>(key, refQueue);
            }
    
            private final int hash;
    
            private CacheKey(K key, ReferenceQueue<K> refQueue) {
                super(key, refQueue);
                this.hash = System.identityHashCode(key);  // compare by identity
            }
    .....
    }
    

     这样看就非常清晰了、原来是CacheKey继承了WeakReference弱引用机制,当弱引用依赖的key没有引用的时候,当前失效的对象就会进入ReferenceQueue中来实现清空cache的功能、这种实现思路和ThreadLocal的实现原理是一样的、大家有兴趣可以去阅读以下相关源码。

    三:手把手写基于接口的java代理

    上面我们分析了jdk动态代理源码、那我们是不是可以自己用自己的方式去写一个属于自己的jdk代理呢,答案是可以的

    首先我们写一个基类,当然我并没有在基类里面写什么东西,只是模拟java中的proxy类而已,当然我们也可以丰富的去拓展一下这个类的方法,来实现更多的功能,读者可以通过读完这篇文章之后自己去考虑一下如何来拓展。

    1 package meituan.zylproxy.handlder;
    2 public class ZylProxy {
    3     public ZylProxy(){
    4     }
    5 }

    代理的核心接口,我们去做代理的时候一定是通过反射去调用的,不管jdk也好还是cglib也好,永远也无法脱离反射,我们照猫画虎,自己写一个代理接口核心类,这并不是什么难题,看起来和jdk的核心类接口也没有什么区别。

    1 package meituan.zylproxy.handlder;
    2 
    3 import java.lang.reflect.Method;
    4 
    5 public interface ZYLInvocationHandler {
    6 
    7     public Object invoke(Object proxy, Method method, Object[] args)
    8         throws Exception;
    9 }

    说明一下 第一个参数proxy是代表代理类,而不是用户自己写的原生类实现。参数Method是接口的方法,args是运行时参数列表,在运行时传递过来的实际上就是实现类的参数,好了,下面让我们去深入核心。

    我们自定义两个接口和接口的实现Idto,Idto2,和Dtoimpl如下:

    1 package meituan.zylproxy.test.i;
    2 
    3 public interface Idto {
    4 
    5     public void add();
    6     
    7     public String get();
    8     
    9 }
    package meituan.zylproxy.test.i;
    
    public interface Idto2 {
    
        public void adda();
        
        public String geta();
        
    }
    package meituan.zylproxy.test.i.impl;
    
    import meituan.zylproxy.test.i.Idto;
    import meituan.zylproxy.test.i.Idto2;
    
    public class DtoImpl implements Idto,Idto2{
    
        @Override
        public void add() {
            System.out.println("add");
            
        }
    
        @Override
        public String get() {
            System.out.println("get");
            return "return get";
        }
    
        @Override
        public void adda() {
            System.out.println("adda");
        }
    
        @Override
        public String geta() {
            System.out.println("geta");
            return "return geta";
        }
    
    }

    这是几个再简单不过的接口和实现类了,也没有什么可说的。接下来我们想对接口进行代理,无非是我们动态将接口进行实现,从而达到对使用者进行自定义handle接口暴露而已,下面看一下我们需要生成一个什么样的代理类。

    import java.lang.reflect.Method;
    
    import meituan.zylproxy.handlder.ZylProxy;
    import meituan.zylproxy.handlder.ZYLInvocationHandler;
    import meituan.zylproxy.test.i.Idto;
    import meituan.zylproxy.test.i.Idto2;
    
    public class IdtoPorxy extends ZylProxy implements Idto, Idto2 {
        public ZYLInvocationHandler zYLInvocationHandler;
        public static Method add1;
        public static Method get2;
        public static Method adda3;
        public static Method geta4;
    
        static {
            try {
                add1 = Class.forName ( "meituan.zylproxy.test.i.Idto" ).getMethod ( "add", new Class[0] );
                get2 = Class.forName ( "meituan.zylproxy.test.i.Idto" ).getMethod ( "get", new Class[0] );
                adda3 = Class.forName ( "meituan.zylproxy.test.i.Idto2" ).getMethod ( "adda", new Class[0] );
                geta4 = Class.forName ( "meituan.zylproxy.test.i.Idto2" ).getMethod ( "geta", new Class[0] );
            } catch (Exception e) {
            }
        }
    
        public IdtoPorxy(ZYLInvocationHandler zYLInvocationHandler) {
            this.zYLInvocationHandler = zYLInvocationHandler;
        }
    
        public void add() {
            Object[] o = {};
            try {
                this.zYLInvocationHandler.invoke ( this, add1, o );
                return;
            } catch (Throwable e) {
                e.printStackTrace ();
            }
        }
    
        public java.lang.String get() {
            Object[] o = {};
            try {
                return (java.lang.String) this.zYLInvocationHandler.invoke ( this, get2, o );
            } catch (Exception e) {
                e.printStackTrace ();
            }
            return null;
        }
    
        public void adda() {
            Object[] o = {};
            try {
                this.zYLInvocationHandler.invoke ( this, adda3, o );
                return;
            } catch (Throwable e) {
                e.printStackTrace ();
            }
        }
    
        public java.lang.String geta() {
            Object[] o = {};
            try {
                return (java.lang.String) this.zYLInvocationHandler.invoke ( this, geta4, o );
            } catch (Exception e) {
                e.printStackTrace ();
            }
            return null;
        }
    }

    这个类不是由用户写的,而是我们动态生成的,对于jdk来说是生成了字节码,对cglib来说是通过字节码增强,其实实现的方式有多种,后面为了更方便大家理解我用字符串的形式来动态生成这么一个"家伙",先看看这个类干了些什么吧,也很简单。

    public class IdtoPorxy extends ZylProxy implements Idto, Idto2 

    首先是继承了刚才我们所说的ZylProxy,留着今后拓展,可以参照java的Proxy,然后并且动态的实现了这两个接口。很简单

    public ZYLInvocationHandler zYLInvocationHandler;
    public IdtoPorxy(ZYLInvocationHandler zYLInvocationHandler) {
    this.zYLInvocationHandler = zYLInvocationHandler;
    }

    这个是通过构造函数传进来一个handler对象,对实现类的操作都靠它了。

    public static Method add1;
        public static Method get2;
        public static Method adda3;
        public static Method geta4;
    
        static {
            try {
                add1 = Class.forName ( "meituan.zylproxy.test.i.Idto" ).getMethod ( "add", new Class[0] );
                get2 = Class.forName ( "meituan.zylproxy.test.i.Idto" ).getMethod ( "get", new Class[0] );
                adda3 = Class.forName ( "meituan.zylproxy.test.i.Idto2" ).getMethod ( "adda", new Class[0] );
                geta4 = Class.forName ( "meituan.zylproxy.test.i.Idto2" ).getMethod ( "geta", new Class[0] );
            } catch (Exception e) {
            }
        }

    枚举出来所有的接口的方法,通过class.forname来获取到Method元数据。备用

     public void add() {
            Object[] o = {};
            try {
                this.zYLInvocationHandler.invoke ( this, add1, o );
                return;
            } catch (Throwable e) {
                e.printStackTrace ();
            }
        }
    
        public java.lang.String get() {
            Object[] o = {};
            try {
                return (java.lang.String) this.zYLInvocationHandler.invoke ( this, get2, o );
            } catch (Exception e) {
                e.printStackTrace ();
            }
            return null;
        }
    
        public void adda() {
            Object[] o = {};
            try {
                this.zYLInvocationHandler.invoke ( this, adda3, o );
                return;
            } catch (Throwable e) {
                e.printStackTrace ();
            }
        }
    
        public java.lang.String geta() {
            Object[] o = {};
            try {
                return (java.lang.String) this.zYLInvocationHandler.invoke ( this, geta4, o );
            } catch (Exception e) {
                e.printStackTrace ();
            }
            return null;
        }

    上面是要枚举出来所有的方法的实现,很简单都一个模样,把实现交给handler去做就可以了。至于怎么实现靠handler,我们动态生成的这个类只负责委托,不做任何事情。看到这里大家一定急不可待的想知道这个类怎么生成的了,我把我写的源码给大家贴出来看一下。

    package meituan.zylproxy.util;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    
    import meituan.zylproxy.test.i.Idto;
    import meituan.zylproxy.test.i.Idto2;
    
    public class ClassUtil {
    
        public static String mackProxyClass(Class<?> c) throws Exception{
            if(!c.isInterface()){
                throw new Exception("代理的类必须是接口");
            }
    
            StringBuffer importsp = new StringBuffer();
            importsp.append("import java.lang.reflect.Method;
    ");
            importsp.append("import meituan.zylproxy.handlder.ZylProxy;
    ");
            importsp.append("import meituan.zylproxy.handlder.ZYLInvocationHandler;
    ");
    
            importsp.append("import " +c.getName() + ";
    ");
    
            StringBuilder publicStaticMethods = new StringBuilder();
            
            //public static Method add;
            StringBuilder publicMethods = new StringBuilder();
            publicMethods.append("public ZYLInvocationHandler zYLInvocationHandler;
    ");
    
            StringBuilder constructorsp = new StringBuilder();
            String interFaceName = c.getName().substring(c.getName().lastIndexOf(".")+1);
            constructorsp.append("public ").append("" + interFaceName + "Porxy").
                       append("(ZYLInvocationHandler zYLInvocationHandler) { "
                               + "this.zYLInvocationHandler = zYLInvocationHandler;"
                               + "}");
    
            publicStaticMethods.append(" static { try {  ");
    
            StringBuilder classsp = new StringBuilder();
            classsp.append("public class").append(" " + interFaceName + "Porxy").append(" extends ZylProxy implements ").append(interFaceName).append("{");
            
         
            StringBuilder allMethods = new StringBuilder();
            Method[] Methods = c.getMethods();
            
            int curr=0;
            for (Method m_:Methods) {
                curr++;
                publicMethods.append("public static Method ").append(m_.getName() + String.valueOf(curr)).append(";
    ");
                
                publicStaticMethods.append("").append(m_.getName() +  String.valueOf(curr)).append("=");
    
                publicStaticMethods.append("Class.forName("" + c.getName() + "")" + ".getMethod(""+ m_.getName() +"", ");
                
                StringBuilder sp =new StringBuilder();
                StringBuilder spArgs = new StringBuilder();
                spArgs.append("Object[] o ={");
                //public
                 sp.append(Modifier.toString(m_.getModifiers()).replace("abstract", "")).append(" ");
                //void | java.lang.String
                sp.append(m_.getReturnType().getName()).append(" ");
                //add()|get()
                sp.append(m_.getName().concat("("));
    
                StringBuilder methodCLass = new StringBuilder();
                 if(m_.getParameterTypes().length>0){
                    Class<?>[] claszz = m_.getParameterTypes();
                    int methodOffset = 0;
                    methodCLass.append("new Class[] { ");
                    for (Class<?> c_ : claszz) {
                        String paramStr = "obj" + String.valueOf(++methodOffset);
                        spArgs.append(paramStr.concat(","));
                        sp.append(c_.getName().toString().concat(" ").concat(paramStr)).append(",");
                        methodCLass.append("Class.forName("" + c_.getName()).append(""),");
                    }
                    sp = new StringBuilder(sp.substring(0, sp.length()-1));
                     spArgs = new StringBuilder(spArgs.substring(0, spArgs.length()-1));
                     methodCLass = new StringBuilder(methodCLass.substring(0, methodCLass.length()-1));
                }
    
                 if(methodCLass.length()>0){
                     methodCLass.append("}");
                 } else{
                     methodCLass.append("new Class[0]");
                 }
                 sp.append("){
    ");
                spArgs.append("}");
                sp.append(spArgs+";
    ");
                
                if(sp.toString().contains("void")){
                    sp.append("try {
     this.zYLInvocationHandler.invoke(this,").append(m_.getName() + String.valueOf(curr)).append(",").append("o);
     return;
    ");
                    sp.append("} catch (Throwable e) {e.printStackTrace();}}");
    
                } else{
                    sp.append("try {return "
                            + "("
                            + m_.getReturnType().getName()
                            + ")"
                            + "this.zYLInvocationHandler.invoke(this,").append(m_.getName() + String.valueOf(curr)).append(",").append("o);
    ");
                
                    sp.append("} catch (Exception e) {e.printStackTrace();} return null;");
    
                }
    
                publicStaticMethods.append(methodCLass).append(");
    ");
                 allMethods.append(sp);
            }
            publicStaticMethods.append("} catch(Exception e){}}");
            classsp.append(publicMethods)
                   .append(publicStaticMethods)
                   .append(constructorsp).append(allMethods).append("}");
            classsp.append("}");
            importsp.append(classsp);
            return importsp.toString();
         }
    
        
        public static String mackMultiProxyClass(Class<?>[] cs) throws Exception{
    
            StringBuffer importsp = new StringBuffer();
            importsp.append("import java.lang.reflect.Method;
    ");
            importsp.append("import meituan.zylproxy.handlder.ZylProxy;
    ");
            importsp.append("import meituan.zylproxy.handlder.ZYLInvocationHandler;
    ");
            
            StringBuilder publicStaticMethods = new StringBuilder();
            publicStaticMethods.append(" static { try {  ");
            
            //public static Method add;
            StringBuilder publicMethods = new StringBuilder();
            publicMethods.append("public ZYLInvocationHandler zYLInvocationHandler;
    ");
                    
            int curr=0;
            
            StringBuilder constructorsp = new StringBuilder();
            String interFaceName = cs[0].getName().substring(cs[0].getName().lastIndexOf(".")+1);
            constructorsp.append("public ").append("" + interFaceName + "Porxy").
                       append("(ZYLInvocationHandler zYLInvocationHandler) { "
                               + "this.zYLInvocationHandler = zYLInvocationHandler;"
                               + "}");
            
            StringBuilder allMethods = new StringBuilder();
            
            StringBuilder classsp = new StringBuilder();
            classsp.append("public class").append(" " + interFaceName + "Porxy").append(" extends ZylProxy implements ");
            
            for (Class<?> c:cs) {
                if(!c.isInterface()){
                    throw new Exception("代理的类必须是接口");
                }
                
                classsp.append(c.getName().substring(c.getName().lastIndexOf(".")+1)).append(",");
                
                importsp.append("import " +c.getName() + ";
    ");
                
                
                Method[] Methods = c.getMethods();
                
                
                for (Method m_:Methods) {
                       curr++;
                    publicMethods.append("public static Method ").append(m_.getName() + String.valueOf(curr)).append(";
    ");
                    
                    publicStaticMethods.append("").append(m_.getName() +  String.valueOf(curr)).append("=");
    
                    publicStaticMethods.append("Class.forName("" + c.getName() + "")" + ".getMethod(""+ m_.getName() +"", ");
                    
                    StringBuilder sp =new StringBuilder();
                    StringBuilder spArgs = new StringBuilder();
                    spArgs.append("Object[] o ={");
                    //public
                     sp.append(Modifier.toString(m_.getModifiers()).replace("abstract", "")).append(" ");
                    //void | java.lang.String
                    sp.append(m_.getReturnType().getName()).append(" ");
                    //add()|get()
                    sp.append(m_.getName().concat("("));
    
                    StringBuilder methodCLass = new StringBuilder();
                     if(m_.getParameterTypes().length>0){
                        Class<?>[] claszz = m_.getParameterTypes();
                        int methodOffset = 0;
                        methodCLass.append("new Class[] { ");
                        for (Class<?> c_ : claszz) {
                            String paramStr = "obj" + String.valueOf(++methodOffset);
                            spArgs.append(paramStr.concat(","));
                            sp.append(c_.getName().toString().concat(" ").concat(paramStr)).append(",");
                            methodCLass.append("Class.forName("" + c_.getName()).append(""),");
                        }
                        sp = new StringBuilder(sp.substring(0, sp.length()-1));
                         spArgs = new StringBuilder(spArgs.substring(0, spArgs.length()-1));
                         methodCLass = new StringBuilder(methodCLass.substring(0, methodCLass.length()-1));
                    }
    
                     if(methodCLass.length()>0){
                         methodCLass.append("}");
                     } else{
                         methodCLass.append("new Class[0]");
                     }
                     sp.append("){
    ");
                    spArgs.append("}");
                    sp.append(spArgs+";
    ");
                    
                    if(sp.toString().contains("void")){
                        sp.append("try {
     this.zYLInvocationHandler.invoke(this,").append(m_.getName() + String.valueOf(curr)).append(",").append("o);
     return;
    ");
                        sp.append("} catch (Throwable e) {e.printStackTrace();}}");
    
                    } else{
                        sp.append("try {return "
                                + "("
                                + m_.getReturnType().getName()
                                + ")"
                                + "this.zYLInvocationHandler.invoke(this,").append(m_.getName() + String.valueOf(curr)).append(",").append("o);
    ");
                    
                        sp.append("} catch (Exception e) {e.printStackTrace();} return null;}");
    
                    }
    
                    publicStaticMethods.append(methodCLass).append(");
    ");
                     allMethods.append(sp);
                }
                
            }
            
            classsp = new StringBuilder(classsp.substring(0, classsp.length()-1)).append("{");
             
            publicStaticMethods.append("} catch(Exception e){}}");
            classsp.append(publicMethods)
                   .append(publicStaticMethods)
                   .append(constructorsp).append(allMethods).append("");
            classsp.append("}");
            importsp.append(classsp);
            return importsp.toString();
         }
        
        
        public static void main(String[] args) throws Exception {
            System.out.println(mackMultiProxyClass(new Class<?>[]{Idto.class}));
        }
    }

    看起来很复杂,仔细看一下就看了那么几个事情,把一个接口class或者多个接口class变成纯字符串的过程,一共两个方法,一个是单接口的实现,很早之前写的,第二个方法是多接口的实现支持多接口,只需要传一个class对象就会生成代理类的字符串,这里仅仅是字符串,需要编译成class使用。那么如何编译成class呢。通过java中的工具类 JavaCompiler很简单的就可以生成了。我们来看两个工具类实现

    package meituan.zylproxy;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FilterOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.Reader;
    import java.io.StringReader;
    import java.net.URI;
    import java.nio.CharBuffer;
    import java.nio.file.WatchEvent.Kind;
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.tools.FileObject;
    import javax.tools.ForwardingJavaFileManager;
    import javax.tools.JavaFileManager;
    import javax.tools.JavaFileObject;
    import javax.tools.SimpleJavaFileObject;
    
    @SuppressWarnings("unchecked")
    final class MemoryJavaFileManager extends ForwardingJavaFileManager {
    
        private final static String EXT = ".java";
    
        private Map<String, byte[]> classBytes;
    
        public MemoryJavaFileManager(JavaFileManager fileManager) {
            super(fileManager);
            classBytes = new HashMap<String, byte[]>();
        }
    
        public Map<String, byte[]> getClassBytes() {
            return classBytes;
        }
    
        public void close() throws IOException {
            classBytes = new HashMap<String, byte[]>();
        }
    
        public void flush() throws IOException {
        }
     
        private static class StringInputBuffer extends SimpleJavaFileObject {
            final String code;
    
            StringInputBuffer(String name, String code) {
                super(toURI(name), Kind.SOURCE);
                this.code = code;
            }
    
            public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
                return CharBuffer.wrap(code);
            }
    
            public Reader openReader() {
                return new StringReader(code);
            }
        }
     
        private class ClassOutputBuffer extends SimpleJavaFileObject {
            private String name;
    
            ClassOutputBuffer(String name) {
                super(toURI(name), Kind.CLASS);
                this.name = name;
            }
    
            public OutputStream openOutputStream() {
                return new FilterOutputStream(new ByteArrayOutputStream()) {
                    public void close() throws IOException {
                        out.close();
                        ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
                        classBytes.put(name, bos.toByteArray());
                    }
                };
            }
        }
    
        public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location,
                                                   String className,
                                                   JavaFileObject.Kind kind,
                                                   FileObject sibling) throws IOException {
            if (kind == JavaFileObject.Kind.CLASS) {
                return new ClassOutputBuffer(className);
            } else {
                return super.getJavaFileForOutput(location, className, kind, sibling);
            }
        }
    
        static JavaFileObject makeStringSource(String name, String code) {
            return new StringInputBuffer(name, code);
        }
    
        static URI toURI(String name) {
            File file = new File(name);
            if (file.exists()) {
                return file.toURI();
            } else {
                try {
                    final StringBuilder newUri = new StringBuilder();
                    newUri.append("mfm:///");
                    newUri.append(name.replace('.', '/'));
                    if (name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
                    return URI.create(newUri.toString());
                } catch (Exception exp) {
                    return URI.create("mfm:///com/sun/script/java/java_source");
                }
            }
        }
    }
    package meituan.zylproxy;
    
    import java.io.IOException;
    import java.net.URL;
    import java.net.URLClassLoader;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    import javax.tools.JavaCompiler;
    import javax.tools.JavaFileObject;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.ToolProvider;
    
    public class DynamicLoader {
    
        public static Map<String, byte[]> compile(String javaSrc) {
            Pattern pattern = Pattern.compile("public\s+class\s+(\w+)");
    
            Matcher matcher = pattern.matcher(javaSrc);
    
            if (matcher.find())
                return compile(matcher.group(1) + ".java", javaSrc);
            return null;
        }
        
    
        public static Map<String, byte[]> compile(String javaName, String javaSrc) {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);
    
            try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) {
                JavaFileObject javaFileObject = manager.makeStringSource(javaName, javaSrc);
                JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));
                if (task.call())
                    return manager.getClassBytes();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static class MemoryClassLoader extends URLClassLoader {
    
            Map<String, byte[]> classBytes = new HashMap<String, byte[]>();
    
            public MemoryClassLoader(Map<String, byte[]> classBytes) {
                super(new URL[0], MemoryClassLoader.class.getClassLoader());
                this.classBytes.putAll(classBytes);
            }
    
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                byte[] buf = classBytes.get(name);
                if (buf == null) {
                    return super.findClass(name);
                }
                classBytes.remove(name);
                return defineClass(name, buf, 0, buf.length);
            }
        }
    }

    通过DynamicLoader的compile方法可以把纯字符串的str转成byte[]数组,有了byte[]数组就可以很方便的获取到class对象了,自定义一个MemoryClassLoader通过defineClass方法来获取到class对象。这样基本所有的事情都做完了。下面我们写一个工厂类来获取代理类。

    package meituan.zylproxy.util;
    
    import java.util.Map;
    
    import meituan.zylproxy.DynamicLoader;
    import meituan.zylproxy.handlder.ZYLInvocationHandler;
    
    public class PorxyFactory {
    
        
        //单interface的时候用
        public static Object newProxyInstance(Class<?> c,ZYLInvocationHandler h) throws Exception{
    
            String classStr = ClassUtil.mackProxyClass(c);
            Map<String, byte[]> m = DynamicLoader.compile(classStr);
            DynamicLoader.MemoryClassLoader classLoader = new DynamicLoader.MemoryClassLoader(m);
            Class<?> proxy =classLoader.loadClass(m.keySet().toArray(new String[0])[0]);
            return proxy.getConstructor(ZYLInvocationHandler.class).newInstance(h);
        }
    
        //多interface的时候用
        public static Object newProxyInstancewWithMultiClass(Class<?>[] c,ZYLInvocationHandler h) throws Exception{
    
            String classStr = ClassUtil.mackMultiProxyClass(c);
            System.out.println (classStr);
            Map<String, byte[]> m = DynamicLoader.compile(classStr);
            DynamicLoader.MemoryClassLoader classLoader = new DynamicLoader.MemoryClassLoader(m);
            Class<?> proxy =classLoader.loadClass(m.keySet().toArray(new String[0])[0]);
            return proxy.getConstructor(ZYLInvocationHandler.class).newInstance(h);
        }
    }

    最后一步我们测试一下结果吧,写一个测试类

    package meituan.zylproxy.test;
    
    import meituan.zylproxy.handlder.Hander;
    import meituan.zylproxy.test.i.Idto;
    import meituan.zylproxy.test.i.impl.DtoImpl;
    import meituan.zylproxy.util.PorxyFactory;
    
    public class ZylPorxyTest {
    
         public static void main(String[] args) throws Exception {
    
             Idto d = (Idto) PorxyFactory.newProxyInstancewWithMultiClass(DtoImpl.class.getInterfaces(), new Hander(new DtoImpl()));
             d.add();
        }
    }

    很简单,第一个参数是所有的接口,第二个是handler实现。最后我们看看结果。

    大功告成。

    作者简介:就职美团外卖业务架构组,有更多的更多的源码交流,请加群825199617交流,spring源码,spring mvc源码,手写企业级高可用rpc框架等等更多精彩源码,等你来。

  • 相关阅读:
    LF 第三章 装饰器和迭代器相关
    Python 文件管理
    Python 强制类型转换
    安装模块
    LF 第三章
    pep8 Python编码规则
    Help on module pyclbr:
    Help on class timedelta in module datetime:
    Help on function meshgrid in module numpy.lib.function_base:
    Help on module matplotlib.cm in matplotlib:
  • 原文地址:https://www.cnblogs.com/zyl2016/p/11841492.html
Copyright © 2011-2022 走看看