美文网首页
掌握java动态代理及原理有多难?

掌握java动态代理及原理有多难?

作者: 程序员ken | 来源:发表于2021-04-14 11:45 被阅读0次

    前言:使用的jdk是1.7,需要了解反射机制 泛型 字节码登概念!

    一、代理模式

    代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

    file

    二、编写一个java动态代理

    1. 准备两个接口

    分别是ProductService(生产接口)和 FactoryService(工厂接口)

    代码如下:

    package test.myproxy;
    
    /**
     * <ul>
     * <li>Title: FactoryService</li>
     * </ul>
     *
     * @author ken
     * @date 2021/4/13 0013 上午 10:05
     */
    public interface FactoryService {
    
        void addProduce(int num);
    
    }
    
    
    package test.myproxy;
    
    /**
     * <ul>
     * <li>Title: ProductService</li>
     * </ul>
     *
     * @author ken
     * @date 2021/4/12 0012 下午 17:46
     */
    public interface ProductService {
    
        /**
         * 添加产品
         * @param productName
         */
        void addProduct(String productName);
    
    }
    
    

    2. MyServiceImpl类实现上述两个接口

    package test.myproxy;
    
    /**
     * <ul>
     * <li>Title: MyServiceImpl</li>
     * <li>Description: TODO </li>
     * </ul>
     *
     * @author ken
     * @date 2021/4/12 0012 下午 17:47
     */
    public class MyServiceImpl implements ProductService,FactoryService {
    
        @Override
        public void addProduct(String productName) {
            System.out.println("正在添加"+productName);
        }
    
    
        @Override
        public void addProduce(int num) {
            System.out.println("准备生成"+num+"件商品");
        }
    }
    
    
    

    3.实现动态代理的功能

    3.1 jdk中需要实现InvocationHandler这个接口,重写invoke方法
    3.2 很多文章中为了便于理解,长把下面代码中的target 和getInstance的参数写成实际的接口,如上面的ProductService,这边不想这么写,是为了让大家能理解泛型,getInstance的接口里面我也明确指出参数是与代理对象是继承关系/实现接口的关系。

    代码如下:

    
    package test.myproxy;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * <ul>
     * <li>Title: JdkInvocationHandler</li>
     * <li>Description: TODO </li>
     * </ul>
     *
     * @author ken
     * @date 2021/4/12 0012 下午 17:45
     */
    public class JdkInvocationHandler<T> implements InvocationHandler {
        //代理对象
        private T target;
            
        public <B extends T> T getInstance(B target){
            this.target = target;
            Class clazz = this.target.getClass();
            // 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3
            return (T)Proxy.newProxyInstance(clazz.getClassLoader(),
                    clazz.getInterfaces(),
                    this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String currentDate  = simpleDateFormat.format(new Date());
            System.out.println("日期【"+currentDate + "】添加了一款产品");
            return method.invoke(target,args);
    
        }
    }
    
    
    3.3 测试

    这里的示例,用了泛型和强转两种方式:

     public static void main(String[] args) throws Exception {
            ProductService proxy =  new JdkInvocationHandler<ProductService>().getInstance(new MyServiceImpl());
            proxy.addProduct("iphone");
                    
            FactoryService proxyFactory = (FactoryService) new JdkInvocationHandler().getInstance(new MyServiceImpl());
            proxyFactory.addProduce(500);
    
            // 这里我们将jdk生成的代理类输出了出来,方便后面分析使用
           /* byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{productService.getClass()});
            FileOutputStream os = new FileOutputStream("Proxy0.class");
            os.write(bytes);
            os.close();*/
        }
    
    
    file

    4.分析如何生成动态代理

    在上面的 JdkInvocationHandler类 实现了 InvocationHandler接口,在getInstance方法中最关键的一行代码是Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(), this);

    下面跟随这个代码进一步剖析内容:

    下面给大家介绍是
    位置: java.lang.reflect
    类: Proxy

    在这个类里面有一些成员属性,先了解一下后面有些会用到。

    file
    4.1 newProxyInstance方法的内容

    (为了看起来简洁 我把一些注释删掉了)

    
     public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
            throws IllegalArgumentException
        {
            if (h == null) {
                throw new NullPointerException();
            }
    
            /*
             * 查找或生成指定的代理类。
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, interfaces); // stack walk magic: do not refactor
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                SecurityManager sm = System.getSecurityManager();
                if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                    // create proxy instance with doPrivilege as the proxy class may
                    // implement non-public interfaces that requires a special permission
                    return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                        public Object run() {
                            return newInstance(cons, ih);
                        }
                    });
                } else {
                    return newInstance(cons, ih);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString());
            }
        }
    
    

    这段代码是通过 c1这个Class类信息 ,根据构造函数参数,动态生成反射类。这里很重要的在getProxyClass0这行代码,注释上的英文的意思是 运行在堆上,且这部分代码不要重构。由此也可知道这部分很重要。下面我把里面的代码展示出来:

    4.2 getProxyClass0方法的内容
    file

    (这个是getProxyClass0方法上的注释)

    
    private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                final int CALLER_FRAME = 3; // 0: Reflection, 1: getProxyClass0 2: Proxy 3: caller
                final Class<?> caller = Reflection.getCallerClass(CALLER_FRAME);
                final ClassLoader ccl = caller.getClassLoader();
                checkProxyLoader(ccl, loader);
                ReflectUtil.checkProxyPackageAccess(ccl, interfaces);
            }
    
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
    
            Class<?> proxyClass = null;
            String[] interfaceNames = new String[interfaces.length];
    
            // for detecting duplicates
            Set<Class<?>> interfaceSet = new HashSet<>();
    
            for (int i = 0; i < interfaces.length; i++) {
                String interfaceName = interfaces[i].getName();
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(interfaceName, false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != interfaces[i]) {
                    throw new IllegalArgumentException(
                        interfaces[i] + " is not visible from class loader");
                }
    
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
    
                if (interfaceSet.contains(interfaceClass)) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
                interfaceSet.add(interfaceClass);
    
                interfaceNames[i] = interfaceName;
            }
    
            List<String> key = Arrays.asList(interfaceNames);
    
            /*
             * Find or create the proxy class cache for the class loader.
             */
            Map<List<String>, Object> cache;
            synchronized (loaderToCache) {
                cache = loaderToCache.get(loader);
                if (cache == null) {
                    cache = new HashMap<>();
                    loaderToCache.put(loader, cache);
                }
            }
    
            synchronized (cache) {
                do {
                    Object value = cache.get(key);
                    if (value instanceof Reference) {
                        proxyClass = (Class<?>) ((Reference) value).get();
                    }
                    if (proxyClass != null) {
                        // proxy class already generated: return it
                        return proxyClass;
                    } else if (value == pendingGenerationMarker) {
                        // proxy class being generated: wait for it
                        try {
                            cache.wait();
                        } catch (InterruptedException e) {
                        }
                        continue;
                    } else {
    
                        cache.put(key, pendingGenerationMarker);
                        break;
                    }
                } while (true);
            }
    
            try {
                String proxyPkg = null;     // package to define proxy class in
                for (int i = 0; i < interfaces.length; i++) {
                    int flags = interfaces[i].getModifiers();
                    if (!Modifier.isPublic(flags)) {
                        String name = interfaces[i].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 + ".";
                }
                {
    
                    long num;
                    synchronized (nextUniqueNumberLock) {
                        num = nextUniqueNumber++;
                    }
                    String proxyName = proxyPkg + proxyClassNamePrefix + num;
                    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                        proxyName, interfaces);
                    try {
                        proxyClass = defineClass0(loader, proxyName,
                            proxyClassFile, 0, proxyClassFile.length);
                    } catch (ClassFormatError e) {
                        throw new IllegalArgumentException(e.toString());
                    }
                }
                // add to set of all generated proxy classes, for isProxyClass
                proxyClasses.put(proxyClass, null);
    
            } finally {
    
                synchronized (cache) {
                    if (proxyClass != null) {
                        cache.put(key, new WeakReference<Class<?>>(proxyClass));
                    } else {
                        cache.remove(key);
                    }
                    cache.notifyAll();
                }
            }
            return proxyClass;
        }
    
    
    

    下面分析这段代码:
    这部代码的意义

    4.2.1. 如果类实现的接口超过65535,直接抛出 “interface limit exceeded”的异常,这部分应该是为了性能,一般情况下 我们的实现的接口 大多在200个以下,这部分可以忽略
    4.2.2 类的所有接口实例化,并放入集合中 ,然后查找或创建类加载器的代理类缓存(loaderToCache 就是存放类加载器的缓存,key为类加载器,value为 Map<List<String>,Object>,该map的key则为 类实现的接口集合),后面是设置代理类的包名 如果有non-public的接口[即接口包名使用这个类的包名,否则默认包名是com.sun.proxy,类名通常是类名前缀+num,类名前缀为$Proxy,这部分后面会提到],最终返回的是生成代理类[注意自动生成代理类名],后面凭借“这个类名”可以调用方法
    4.2.3 类的所有接口实例化
      Class <?> interfaceClass = Class.forName(interfaceName, false, loader);
    
    4.2.4 类所有实现的接口 放入集合中,这个部分后面
            List<String> key = Arrays.asList(interfaceNames);
    
    4.2.5 查找或创建类加载器的代理类缓存。
     Map<List<String>, Object> cache;
            synchronized (loaderToCache) {
                cache = loaderToCache.get(loader);
                if (cache == null) {
                    cache = new HashMap<>();
                    loaderToCache.put(loader, cache);
                }
                /*
                 * This mapping will remain valid for the duration of this
                 * method, without further synchronization, because the mapping
                 * will only be removed if the class loader becomes unreachable.
                 */
            }
    
    4.2.6 这部分是记录 当前创建代理的个数,防止生成的代理类的”类名”重复
                    long num;
                    synchronized (nextUniqueNumberLock) {
                        num = nextUniqueNumber++;
                    }
                    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
    4.2.7 其中这一块就是创建 代理类的 字节码 ,defineClass0是通过类加载器、代理类名、字节码生成代理类。
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
            proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
    

    【这块调用的c语言的本地方法】

    file

    ps:在很多文章里面都用了 ProxyGenerator.generateProxyClass的方法,然后用文件输出流把字节码内容写入文件里面,看到这里估计你应该知道为什么会如此写了吧。

    file

    下面简单看下 输出的内容

    该类所有的方法都被“反射”创建了,实际调用时就是调用这个针对的方法。

    file
    file
    file

    看到最后,我们可以知道一个反射 无非把 自身的“所有方法” 赋予了另一个“凭空产生的”对象,让他使用自己的“权利”而已。

    本文来源于:程序员ken,专属平台有csdn、思否(SegmentFault)、 简书、 开源中国(oschina)、掘金,转载请注明出处。

    相关文章

      网友评论

          本文标题:掌握java动态代理及原理有多难?

          本文链接:https://www.haomeiwen.com/subject/ebuklltx.html