动态代理_动态代理面试 - CSDN
精华内容
参与话题
  • 轻松学,Java 中的代理模式及动态代理

    万次阅读 多人点赞 2017-06-29 22:08:55
    前几天我写了《秒懂,Java 注解 (Annotation)你可以这样学》,因为注解其实算反射技术中的一部分,然后我想了一下,反射技术中还有个常见的概念就是动态代理,于是索性再写一篇关于动态代理的博文好了。...

    前几天我写了《秒懂,Java 注解 (Annotation)你可以这样学》,因为注解其实算反射技术中的一部分,然后我想了一下,反射技术中还有个常见的概念就是动态代理,于是索性再写一篇关于动态代理的博文好了。

    我们先来分析代理这个词。

    代理

    代理是英文 Proxy 翻译过来的。我们在生活中见到过的代理,大概最常见的就是朋友圈中卖面膜的同学了。

    她们从厂家拿货,然后在朋友圈中宣传,然后卖给熟人。

    这里写图片描述

    按理说,顾客可以直接从厂家购买产品,但是现实生活中,很少有这样的销售模式。一般都是厂家委托给代理商进行销售,顾客跟代理商打交道,而不直接与产品实际生产者进行关联。

    所以,代理就有一种中间人的味道。

    接下来,我们说说软件中的代理模式。

    代理模式

    代理模式是面向对象编程中比较常见的设计模式。
    这里写图片描述

    这是常见代理模式常见的 UML 示意图。

    需要注意的有下面几点:

    1. 用户只关心接口功能,而不在乎谁提供了功能。上图中接口是 Subject。
    2. 接口真正实现者是上图的 RealSubject,但是它不与用户直接接触,而是通过代理。
    3. 代理就是上图中的 Proxy,由于它实现了 Subject 接口,所以它能够直接与用户接触。
    4. 用户调用 Proxy 的时候,Proxy 内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强 RealSubject 操作。

    如果难于理解的话,我用事例说明好了。值得注意的是,代理可以分为静态代理和动态代理两种。先从静态代理讲起。

    静态代理

    我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?

    电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。

    现在用代码来进行模拟。

    首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。

    
    package com.frank.test;
    
    public interface Movie {
    	void play();
    }
    
    

    然后,我们要有一个真正的实现这个 Movie 接口的类,和一个只是实现接口的代理类。

    package com.frank.test;
    
    public class RealMovie implements Movie {
    
    	@Override
    	public void play() {
    		// TODO Auto-generated method stub
    		System.out.println("您正在观看电影 《肖申克的救赎》");
    	}
    
    }
    

    这个表示真正的影片。它实现了 Movie 接口,play() 方法调用时,影片就开始播放。那么 Proxy 代理呢?

    package com.frank.test;
    
    public class Cinema implements Movie {
    	
    	RealMovie movie;
    	
    	public Cinema(RealMovie movie) {
    		super();
    		this.movie = movie;
    	}
    
    
    	@Override
    	public void play() {
    		
    		guanggao(true);
    		
    		movie.play();
    		
    		guanggao(false);
    	}
    	
    	public void guanggao(boolean isStart){
    		if ( isStart ) {
    			System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
    		} else {
    			System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
    		}
    	}
    
    }
    
    

    Cinema 就是 Proxy 代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。现在,我们编写测试代码。

    package com.frank.test;
    
    public class ProxyTest {
    
    	public static void main(String[] args) {
    		
    		RealMovie realmovie = new RealMovie();
    		
    		Movie movie = new Cinema(realmovie);
    		
    		movie.play();
    
    	}
    
    }
    
    

    然后观察结果:

    电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!
    您正在观看电影 《肖申克的救赎》
    电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!
    
    

    现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。

    上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类。下面要介绍的内容就是动态代理。

    动态代理

    既然是代理,那么它与静态代理的功能与目的是没有区别的,唯一有区别的就是动态与静态的差别。

    那么在动态代理的中这个动态体现在什么地方?

    上一节代码中 Cinema 类是代理,我们需要手动编写代码让 Cinema 实现 Movie 接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 Movie 接口的代理,而不需要去定义 Cinema 这个类。这就是它被称为动态的原因。

    也许概念比较抽象。现在实例说明一下情况。

    假设有一个大商场,商场有很多的柜台,有一个柜台卖茅台酒。我们进行代码的模拟。

    package com.frank.test;
    
    public interface SellWine {
    	
    	 void mainJiu();
    
    }
    

    SellWine 是一个接口,你可以理解它为卖酒的许可证。

    package com.frank.test;
    
    public class MaotaiJiu implements SellWine {
    
    	@Override
    	public void mainJiu() {
    		// TODO Auto-generated method stub
    		System.out.println("我卖得是茅台酒。");
    
    	}
    
    }
    
    

    然后创建一个类 MaotaiJiu,对的,就是茅台酒的意思。

    我们还需要一个柜台来卖酒:

    package com.frank.test;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    
    public class GuitaiA implements InvocationHandler {
    	
    	private Object pingpai;
    	
    	
    	public GuitaiA(Object pingpai) {
    		this.pingpai = pingpai;
    	}
    
    
    
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args)
    			throws Throwable {
    		// TODO Auto-generated method stub
    		System.out.println("销售开始  柜台是: "+this.getClass().getSimpleName());
    		method.invoke(pingpai, args);
    		System.out.println("销售结束");
    		return null;
    	}
    
    }
    
    

    GuitaiA 实现了 InvocationHandler 这个类,这个类是什么意思呢?大家不要慌张,待会我会解释。

    然后,我们就可以卖酒了。

    package com.frank.test;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    
    public class Test {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		
    		MaotaiJiu maotaijiu = new MaotaiJiu();
    		
    		
    		InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
    
    		
    		SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
    				MaotaiJiu.class.getInterfaces(), jingxiao1);
    	
    		dynamicProxy.mainJiu();
    			
    	}
    
    }
    
    
    

    这里,我们又接触到了一个新的概念,没有关系,先别管,先看结果。

    销售开始  柜台是: GuitaiA
    我卖得是茅台酒。
    销售结束
    
    

    看到没有,我并没有像静态代理那样为 SellWine 接口实现一个代理类,但最终它仍然实现了相同的功能,这其中的差别,就是之前讨论的动态代理所谓“动态”的原因。

    动态代理语法

    放轻松,下面我们开始讲解语法,语法非常简单。

    动态代码涉及了一个非常重要的类 Proxy。正是通过 Proxy 的静态方法 newProxyInstance 才会动态创建代理。

    Proxy

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

    下面讲解它的 3 个参数意义。

    • loader 自然是类加载器
    • interfaces 代码要用来代理的接口
    • h 一个 InvocationHandler 对象

    初学者应该对于 InvocationHandler 很陌生,我马上就讲到这一块。

    InvocationHandler

    InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。

    public interface InvocationHandler {
    
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }
    
    

    InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。

    • proxy 代理对象
    • method 代理对象调用的方法
    • args 调用的方法中的参数

    因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。

    public class GuitaiA implements InvocationHandler {
    	
    	private Object pingpai;
    	
    	
    	public GuitaiA(Object pingpai) {
    		this.pingpai = pingpai;
    	}
    
    
    
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args)
    			throws Throwable {
    		// TODO Auto-generated method stub
    		System.out.println("销售开始  柜台是: "+this.getClass().getSimpleName());
    		method.invoke(pingpai, args);
    		System.out.println("销售结束");
    		return null;
    	}
    
    }
    
    

    GuitaiA 就是实际上卖酒的地方。

    现在,我们加大难度,我们不仅要卖茅台酒,还想卖五粮液

    package com.frank.test;
    
    public class Wuliangye implements SellWine {
    
    	@Override
    	public void mainJiu() {
    		// TODO Auto-generated method stub
    		System.out.println("我卖得是五粮液。");
    
    	}
    
    }
    
    

    Wuliangye 这个类也实现了 SellWine 这个接口,说明它也拥有卖酒的许可证,同样把它放到 GuitaiA 上售卖。

    public class Test {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		
    		MaotaiJiu maotaijiu = new MaotaiJiu();
    		
    		Wuliangye wu = new Wuliangye();
    		
    		InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
    		InvocationHandler jingxiao2 = new GuitaiA(wu);
    		
    		SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
    				MaotaiJiu.class.getInterfaces(), jingxiao1);
    		SellWine dynamicProxy1 = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
    				MaotaiJiu.class.getInterfaces(), jingxiao2);
    		
    		dynamicProxy.mainJiu();
    		
    		dynamicProxy1.mainJiu();
    		
    	}
    
    }
    
    

    我们来看结果:

    销售开始  柜台是: GuitaiA
    我卖得是茅台酒。
    销售结束
    销售开始  柜台是: GuitaiA
    我卖得是五粮液。
    销售结束
    
    

    有人会问,dynamicProxy 和 dynamicProxy1 什么区别没有?他们都是动态产生的代理,都是售货员,都拥有卖酒的技术证书。

    我现在扩大商场的经营,除了卖酒之外,还要卖烟。

    首先,同样要创建一个接口,作为卖烟的许可证。

    package com.frank.test;
    
    public interface SellCigarette {
    	void sell();
    }
    
    

    然后,卖什么烟呢?我是湖南人,那就芙蓉王好了。

    public class Furongwang implements SellCigarette {
    
    	@Override
    	public void sell() {
    		// TODO Auto-generated method stub
    		System.out.println("售卖的是正宗的芙蓉王,可以扫描条形码查证。");
    	}
    
    }
    
    

    然后再次测试验证:

    package com.frank.test;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    
    public class Test {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		
    		MaotaiJiu maotaijiu = new MaotaiJiu();
    		
    		Wuliangye wu = new Wuliangye();
    		
    		Furongwang fu = new Furongwang();
    		
    		InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
    		InvocationHandler jingxiao2 = new GuitaiA(wu);
    		
    		InvocationHandler jingxiao3 = new GuitaiA(fu);
    		
    		SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
    				MaotaiJiu.class.getInterfaces(), jingxiao1);
    		SellWine dynamicProxy1 = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
    				MaotaiJiu.class.getInterfaces(), jingxiao2);
    		
    		dynamicProxy.mainJiu();
    		
    		dynamicProxy1.mainJiu();
    		
    		SellCigarette dynamicProxy3 = (SellCigarette) Proxy.newProxyInstance(Furongwang.class.getClassLoader(),
    				Furongwang.class.getInterfaces(), jingxiao3);
    		
    		dynamicProxy3.sell();
    		
    	}
    
    }
    
    

    然后,查看结果:

    销售开始  柜台是: GuitaiA
    我卖得是茅台酒。
    销售结束
    销售开始  柜台是: GuitaiA
    我卖得是五粮液。
    销售结束
    销售开始  柜台是: GuitaiA
    售卖的是正宗的芙蓉王,可以扫描条形码查证。
    销售结束
    
    
    

    结果符合预期。大家仔细观察一下代码,同样是通过 Proxy.newProxyInstance() 方法,却产生了 SellWine 和 SellCigarette 两种接口的实现类代理,这就是动态代理的魔力。

    动态代理的秘密

    一定有同学对于为什么 Proxy 能够动态产生不同接口类型的代理感兴趣,我的猜测是肯定通过传入进去的接口然后通过反射动态生成了一个接口实例。
    比如 SellWine 是一个接口,那么 Proxy.newProxyInstance() 内部肯定会有

    
    new SellWine();
    

    这样相同作用的代码,不过它是通过反射机制创建的。那么事实是不是这样子呢?直接查看它们的源码好了。需要说明的是,我当前查看的源码是 1.8 版本。

    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);
    
            final Class<?>[] intfs = interfaces.clone();
    
    
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
    
    
                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);
            }
        }
    
    
    

    newProxyInstance 的确创建了一个实例,它是通过 cl 这个 Class 文件的构造方法反射生成。cl 由 getProxyClass0() 方法获取。

    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
        return proxyClassCache.get(loader, interfaces);
    }
    
    

    直接通过缓存获取,如果获取不到,注释说会通过 ProxyClassFactory 生成。

    /**
         * 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<?>>
        {
            // Proxy class 的前缀是 “$Proxy”,
            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
                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 = 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.
                 */
                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());
                }
            }
        }
    
    
    

    这个类的注释说,通过指定的 ClassLoader 和 接口数组 用工厂方法生成 proxy class。 然后这个 proxy class 的名字是:

    
    // Proxy class 的前缀是 “$Proxy”,
    private static final String proxyClassNamePrefix = "$Proxy";
    
    long num = nextUniqueNumber.getAndIncrement();
    
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
    

    所以,动态生成的代理类名称是包名+$Proxy+id序号

    生成的过程,核心代码如下:

    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
    
             
    return defineClass0(loader, proxyName,
                        proxyClassFile, 0, proxyClassFile.length);
    
    

    这两个方法,我没有继续追踪下去,defineClass0() 甚至是一个 native 方法。我们只要知道,动态创建代理这回事就好了。

    现在我们还需要做一些验证,我要检测一下动态生成的代理类的名字是不是包名+$Proxy+id序号

    public class Test {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		
    		MaotaiJiu maotaijiu = new MaotaiJiu();
    		
    		Wuliangye wu = new Wuliangye();
    		
    		Furongwang fu = new Furongwang();
    		
    		InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
    		InvocationHandler jingxiao2 = new GuitaiA(wu);
    		
    		InvocationHandler jingxiao3 = new GuitaiA(fu);
    		
    		SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
    				MaotaiJiu.class.getInterfaces(), jingxiao1);
    		SellWine dynamicProxy1 = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
    				MaotaiJiu.class.getInterfaces(), jingxiao2);
    		
    		dynamicProxy.mainJiu();
    		
    		dynamicProxy1.mainJiu();
    		
    		SellCigarette dynamicProxy3 = (SellCigarette) Proxy.newProxyInstance(Furongwang.class.getClassLoader(),
    				Furongwang.class.getInterfaces(), jingxiao3);
    		
    		dynamicProxy3.sell();
    		
    		System.out.println("dynamicProxy class name:"+dynamicProxy.getClass().getName());
    		System.out.println("dynamicProxy1 class name:"+dynamicProxy1.getClass().getName());
    		System.out.println("dynamicProxy3 class name:"+dynamicProxy3.getClass().getName());
    		
    	}
    
    }
    
    

    结果如下:

    销售开始  柜台是: GuitaiA
    我卖得是茅台酒。
    销售结束
    销售开始  柜台是: GuitaiA
    我卖得是五粮液。
    销售结束
    销售开始  柜台是: GuitaiA
    售卖的是正宗的芙蓉王,可以扫描条形码查证。
    销售结束
    
    dynamicProxy class name:com.sun.proxy.$Proxy0
    dynamicProxy1 class name:com.sun.proxy.$Proxy0
    dynamicProxy3 class name:com.sun.proxy.$Proxy1
    
    

    SellWine 接口的代理类名是:com.sun.proxy.$Proxy0
    SellCigarette 接口的代理类名是:com.sun.proxy.$Proxy1

    这说明动态生成的 proxy class 与 Proxy 这个类同一个包。

    下面用一张图让大家记住动态代理涉及到的角色。
    这里写图片描述
    红框中 $Proxy0就是通过 Proxy 动态生成的。
    $Proxy0实现了要代理的接口。
    $Proxy0通过调用 InvocationHandler来执行任务。

    代理的作用

    可能有同学会问,已经学习了代理的知识,但是,它们有什么用呢?

    主要作用,还是在不修改被代理对象的源码上,进行功能的增强。

    这在 AOP 面向切面编程领域经常见。

    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

    主要功能
    日志记录,性能统计,安全控制,事务处理,异常处理等等。

    上面的引用是百度百科对于 AOP 的解释,至于,如何通过代理来进行日志记录功能、性能统计等等,这个大家可以参考 AOP 的相关源码,然后仔细琢磨。

    同注解一样,很多同学可能会有疑惑,我什么时候用代理呢?

    这取决于你自己想干什么。你已经学会了语法了,其他的看业务需求。对于实现日志记录功能的框架来说,正合适。

    至此,静态代理和动态代理者讲完了。

    总结

    1. 代理分为静态代理和动态代理两种。
    2. 静态代理,代理类需要自己编写代码写成。
    3. 动态代理,代理类通过 Proxy.newInstance() 方法生成。
    4. 不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。
    5. 静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
    6. 动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。
    7. 代理模式本质上的目的是为了增强现有代码的功能。

    读者们都在下面的二维码所示的免费的知识星球问我问题:
    在这里插入图片描述

    展开全文
  • java动态代理原理及解析

    万次阅读 多人点赞 2016-09-06 17:25:05
    java动态代理, jdk反射与代理模式

    代理:设计模式

    代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个真实对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

    这里写图片描述

    通过代理层这一中间层,有效的控制对于真实委托类对象的直接访问,同时可以实现自定义的控制策略(Spring的AOP机制),设计上获得更大的灵活性。

    java动态代理的类和接口(jdk1.6源码)

    1,java.lang.reflect.Proxy:动态代理机制的主类,提供一组静态方法为一组接口动态的生成对象和代理类。

    // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
    public static InvocationHandler getInvocationHandler(Object proxy) 
    
    // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    public static Class<?> getProxyClass(ClassLoader loader, 
    Class<?>... interfaces)
    
    // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
    public static boolean isProxyClass(Class<?> cl) 
    
    // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    public static Object newProxyInstance(ClassLoader loader,
     Class<?>[] interfaces,InvocationHandler h)

    2,java.lang.reflect.InvocationHandler:调用处理器接口,自定义invokle方法,用于实现对于真正委托类的代理访问。

    /**
     该方法负责集中处理动态代理类上的所有方法调用。
     第一个参数既是代理类实例,
     第二个参数是被调用的方法对象
     第三个方法是调用参数。
     调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
    */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

    3,java.lang.ClassLoader:类装载器类,将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy类与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中
    每次生成动态代理类对象时都需要指定一个类装载器对象:newProxyInstance()方法第一个参数

    动态代理机制

    java动态代理创建对象的过程为如下步骤:
    1,通过实现 InvocationHandler 接口创建自己的调用处理器;

    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
    InvocationHandler handler = new InvocationHandlerImpl(..); 

    2,通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;

    // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
    Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 

    3,通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;

    // 通过反射从生成的类对象获得构造函数对象
    Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });

    4,通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

    // 通过构造函数对象创建动态代理类实例
    Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

    为了简化对象创建过程,Proxy类中的newProxyInstance方法封装了2~4,只需两步即可完成代理对象的创建。

    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    InvocationHandler handler = new InvocationHandlerImpl(..); 
    
    // 通过 Proxy 直接创建动态代理类实例
    Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
         new Class[] { Interface.class }, 
         handler );

    动态代理的注意点:
    1,包:代理接口是public,则代理类被定义在顶层包(package为空),否则(default),代理类被定义在该接口所在包,

    这里写图片描述

    2,生成的代理类为public final,不能被继承,

    3,类名:格式是“$ProxyN”,N是逐一递增的数字,代表Proxy被第N次动态生成的代理类,要注意,对于同一组接口(接口的排列顺序也相同),不会重复创建动态代理类,而是返回一个先前已经创建并缓存了的代理类对象。提高了效率。

    这里写图片描述

    4,类继承关系:

    这里写图片描述
    Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。(也算是java动态代理的一处缺陷,java不支持多继承,所以无法实现对class的动态代理,只能对于Interface的代理)而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

    5,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,
    代码在反编译中
    这里写图片描述

    一个动态代理的demo

    import java.lang.reflect.InvocationHandler;  
    import java.lang.reflect.Method;  
    import java.lang.reflect.Proxy;  
    
    public class HelloServiceProxy implements InvocationHandler {  
    
    
        private Object target;    
        /**  
         * 绑定委托对象并返回一个【代理占位】 
         * @param target 真实对象 
         * @return  代理对象【占位】 
         */    
        public  Object bind(Object target, Class[] interfaces) {    
            this.target = target;    
            //取得代理对象    
           return Proxy.newProxyInstance(target.getClass().getClassLoader(),    
                    target.getClass().getInterfaces(), this);
        }    
    
        @Override    
        /** 
         * 同过代理对象调用方法首先进入这个方法. 
         * @param proxy --代理对象 
         * @param method -- 方法,被调用方法. 
         * @param args -- 方法的参数 
         */  
        public Object invoke(Object proxy , Method method, Object[] args) throws Throwable {    
            System.err.println("############我是JDK动态代理################");  
            Object result = null;    
            //反射方法前调用  
            System.err.println("我准备说hello。");    
            //反射执行方法  相当于调用target.sayHelllo;  
            result=method.invoke(target, args);  
            //反射方法后调用.  
            System.err.println("我说过hello了");    
            return result;    
        }    
    } 

    其中,bind方法中的newProxyInstanc方法,就是生成一个代理对象,第一个参数是类加载器,第二个参数是真实委托对象所实现的的接口(代理对象挂在那个接口下),第三个参数this代表当前HelloServiceProxy类,换句话说是使用HelloServiceProxy作为对象的代理。

    invoke方法有三个参数:第一个proxy是代理对象,第二个是当前调用那个方法,第三个是方法的参数。

    public class ProxyTest {  
    
        public static void main(String[] args) {   
            HelloServiceProxy proxy = new HelloServiceProxy();  
            HelloService service = new HelloServiceImpl();  
            //绑定代理对象。  
            service = (HelloService) proxy.bind(service, new Class[] {HelloService.class});  
            //这里service经过绑定,就会进入invoke方法里面了。  
            service.sayHello("张三");  
        }  
    }  

    测试结果:

    ############我是JDK动态代理################  
    我准备说hello。  
    hello 张三  
    我说过hello了

    源码跟踪

    Proxy 类

    // 映射表:用于维护类装载器对象到其对应的代理类缓存
    private static Map loaderToCache = new WeakHashMap(); 
    
    // 标记:用于标记一个动态代理类正在被创建中
    private static Object pendingGenerationMarker = new Object(); 
    
    // 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
    private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); 
    
    // 关联的调用处理器引用
    protected InvocationHandler h;

    Proxy 静态方法 newProxyInstance

    public static Object newProxyInstance(ClassLoader loader, 
                Class<?>[] interfaces, 
                InvocationHandler h) 
                throws IllegalArgumentException { 
    
        // 检查 h 不为空,否则抛异常
        if (h == null) { 
            throw new NullPointerException(); 
        } 
    
        // 获得与制定类装载器和一组接口相关的代理类类型对象
        /*
         * Look up or generate the designated proxy class.
         */
            Class<?> cl = getProxyClass0(loader, interfaces); 
    
        // 通过反射获取构造函数对象并生成代理类实例
        /*
         * 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());
        } 
        }
    
    private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
            try {
                return cons.newInstance(new Object[] {h} );
            } catch (IllegalAccessException e) {
                throw new InternalError(e.toString());
            } catch (InstantiationException e) {
                throw new InternalError(e.toString());
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString());
                }
            }
        }

    动态代理真正的关键是在 getProxyClass0 方法,

    getProxyClass0方法分析

    方法分为四个步骤:
    1,对这组接口进行一定程度的安全检查
    检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是 interface 类型而不是 class 类型。

    这里写图片描述

    2,从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到 loaderToCache。
    loaderToCache存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。

    /*
         * Find or create the proxy class cache for the class loader.
         */
        Map cache;
        synchronized (loaderToCache) {
            cache = (Map) loaderToCache.get(loader);
            if (cache == null) {
            cache = new HashMap();
            loaderToCache.put(loader, cache);
            }
         }
    。。。。。
    do { 
        // 以接口名字列表作为关键字获得对应 cache 值
        Object value = cache.get(key); 
        if (value instanceof Reference) { 
            proxyClass = (Class) ((Reference) value).get(); 
        } 
        if (proxyClass != null) { 
            // 如果已经创建,直接返回
            return proxyClass; 
        } else if (value == pendingGenerationMarker) { 
            // 代理类正在被创建,保持等待
            try { 
                cache.wait(); 
            } catch (InterruptedException e) { 
            } 
            // 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
            continue; 
        } else { 
            // 标记代理类正在被创建
            cache.put(key, pendingGenerationMarker); 
            // break 跳出循环已进入创建过程
            break; 
    } while (true);

    3,动态创建代理类的class对象

    /**
             * Choose a name for the proxy class to generate.
             */
            long num;
            synchronized (nextUniqueNumberLock) {
                num = nextUniqueNumber++;
            }
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
            /*
             * Verify that the class loader hasn't already
             * defined a class with the chosen name.
             */
    
            // 动态地生成代理类的字节码数组
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces);
            try {
            // 动态地定义新生成的代理类
                proxyClass = 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());
            }
            // 把生成的代理类的类对象记录进 proxyClasses 表
            proxyClasses.put(proxyClass, null);             

    首先根据规则(接口public与否),生成代理类的名称,$ProxyN格式,然后动态生成代理类。
    所有的代码生成的工作都由 ProxyGenerator 所完成了,该类在rt.jar中,需要反编译

    public static byte[] generateProxyClass(final String name,  
                                               Class[] interfaces)  
       {  
           ProxyGenerator gen = new ProxyGenerator(name, interfaces);  
        // 这里动态生成代理类的字节码,由于比较复杂就不进去看了  
           final byte[] classFile = gen.generateClassFile();  
    
        // 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上  
           if (saveGeneratedFiles) {  
               java.security.AccessController.doPrivileged(  
               new java.security.PrivilegedAction<Void>() {  
                   public Void run() {  
                       try {  
                           FileOutputStream file =  
                               new FileOutputStream(dotToSlash(name) + ".class");  
                           file.write(classFile);  
                           file.close();  
                           return null;  
                       } catch (IOException e) {  
                           throw new InternalError(  
                               "I/O exception saving generated file: " + e);  
                       }  
                   }  
               });  
           }  
    
        // 返回代理类的字节码  
           return classFile;  
       }

    4,代码生成过程进入结尾部分,根据结果更新缓存表,如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。

    finally {
            /*
             * We must clean up the "pending generation" state of the proxy
             * class cache entry somehow.  If a proxy class was successfully
             * generated, store it in the cache (with a weak reference);
             * otherwise, remove the reserved entry.  In all cases, notify
             * all waiters on reserved entries in this cache.
             */
            synchronized (cache) {
            if (proxyClass != null) {
                cache.put(key, new WeakReference(proxyClass));
            } else {
                cache.remove(key);
            }
            cache.notifyAll();
            }
        }
        return proxyClass;

    InvocationHandler解析

    代码参考:http://rejoy.iteye.com/blog/1627405
    通过getProxyClass0方法中生成具体的class文件的过程,定义path,讲class文件写到指定的磁盘中,反编译生成的代理class文件。
    发现在静态代码块中获取了的方法有:Object中的equals方法、Object中的hashCode方法、Object中toString方法 , 以及invoke的接口方法。

    后语

    至此,JDK是动态生成代理类,并通过调用解析器,执行接口实现的方法的原理已经一目了然。动态代理加上反射,是很多框架的基础。比如Spring的AOP机制,自定义前置后置通知等控制策略,以及mybatis中的运用反射和动态代理来实现插件技术等等。

    文章参考:
    http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html
    http://blog.csdn.net/ykzhen2015/article/details/50312651

    展开全文
  • 10分钟看懂动态代理设计模式

    万次阅读 多人点赞 2018-03-14 10:38:00
    原文作者:欧阳锋点击打开链接动态代理是Java语言中非常经典的一种设计模式,也是所有设计模式中最难理解的一种。本文将通过一个简单的例子模拟JDK动态代理实现,让你彻底明白动态代理设计模式的本质,文章中可能会...

    原文作者:欧阳锋点击打开链接

    动态代理是Java语言中非常经典的一种设计模式,也是所有设计模式中最难理解的一种。本文将通过一个简单的例子模拟JDK动态代理实现,让你彻底明白动态代理设计模式的本质,文章中可能会涉及到一些你没有学习过的知识点或概念。如果恰好遇到了这些知识盲点,请先去学习这部分知识,再来阅读这篇文章。

    什么是代理

    从字面意思来看,代理比较好理解,无非就是代为处理的意思。举个例子,你在上大学的时候,总是喜欢逃课。因此,你拜托你的同学帮你答到,而自己却窝在宿舍玩游戏... 你的这个同学恰好就充当了代理的作用,代替你去上课。

    是的,你没有看错,代理就是这么简单!

    理解了代理的意思,你脑海中恐怕还有两个巨大的疑问:

    • 怎么实现代理模式
    • 代理模式有什么实际用途

    要理解这两个问题,看一个简单的例子:

    public interface Flyable {
        void fly();
    }
    
    public class Bird implements Flyable {
    
        @Override
        public void fly() {
            System.out.println("Bird is flying...");
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    很简单的一个例子,用一个随机睡眠时间模拟小鸟在空中的飞行时间。接下来问题来了,如果我要知道小鸟在天空中飞行了多久,怎么办?

    有人说,很简单,在Bird->fly()方法的开头记录起始时间,在方法结束记录完成时间,两个时间相减就得到了飞行时间。

       @Override
        public void fly() {
            long start = System.currentTimeMillis();
            System.out.println("Bird is flying...");
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println("Fly time = " + (end - start));
        }
    

    的确,这个方法没有任何问题,接下来加大问题的难度。如果Bird这个类来自于某个SDK(或者说Jar包)提供,你无法改动源码,怎么办?

    一定会有人说,我可以在调用的地方这样写:

    public static void main(String[] args) {
            Bird bird = new Bird();
            long start = System.currentTimeMillis();
            bird.fly();
            long end = System.currentTimeMillis();
            System.out.println("Fly time = " + (end - start));
    }
    

    这个方案看起来似乎没有问题,但其实你忽略了准备这些方法所需要的时间,执行一个方法,需要开辟栈内存、压栈、出栈等操作,这部分时间也是不可以忽略的。因此,这个解决方案不可行。那么,还有什么方法可以做到呢?

    a)使用继承

    继承是最直观的解决方案,相信你已经想到了,至少我最开始想到的解决方案就是继承。
    为此,我们重新创建一个类Bird2,在Bird2中我们只做一件事情,就是调用父类的fly方法,在前后记录时间,并打印时间差:

    public class Bird2 extends Bird {
    
        @Override
        public void fly() {
            long start = System.currentTimeMillis();
            
            super.fly();
            
            long end = System.currentTimeMillis();
            System.out.println("Fly time = " + (end - start));
        }
    }
    

    这是一种解决方案,还有一种解决方案叫做:聚合,其实也是比较容易想到的。
    我们再次创建新类Bird3,在Bird3的构造方法中传入Bird实例。同时,让Bird3也实现Flyable接口,并在fly方法中调用传入的Bird实例的fly方法:

    public class Bird3 implements Flyable {
        private Bird bird;
    
        public Bird3(Bird bird) {
            this.bird = bird;
        }
    
        @Override
        public void fly() {
            long start = System.currentTimeMillis();
    
            bird.fly();
    
            long end = System.currentTimeMillis();
            System.out.println("Fly time = " + (end - start));
        }
    }
    

    为了记录Bird->fly()方法的执行时间,我们在前后添加了记录时间的代码。同样地,通过这种方法我们也可以获得小鸟的飞行时间。那么,这两种方法孰优孰劣呢?咋一看,不好评判!

    继续深入思考,用问题推导来解答这个问题:

    问题一:如果我还需要在fly方法前后打印日志,记录飞行开始和飞行结束,怎么办?
    有人说,很简单!继承Bird2并在在前后添加打印语句即可。那么,问题来了,请看问题二。

    问题二:如果我需要调换执行顺序,先打印日志,再获取飞行时间,怎么办?
    有人说,再新建一个类Bird4继承Bird,打印日志。再新建一个类Bird5继承Bird4,获取方法执行时间。

    问题显而易见:使用继承将导致类无限制扩展,同时灵活性也无法获得保障。那么,使用 聚合 是否可以避免这个问题呢?
    答案是:可以!但我们的类需要稍微改造一下。修改Bird3类,将聚合对象Bird类型修改为Flyable

    public class Bird3 implements Flyable {
        private Flyable flyable;
    
        public Bird3(Flyable flyable) {
            this.flyable = flyable;
        }
    
        @Override
        public void fly() {
            long start = System.currentTimeMillis();
    
            flyable.fly();
    
            long end = System.currentTimeMillis();
            System.out.println("Fly time = " + (end - start));
        }
    }
    

    为了让你看的更清楚,我将Bird3更名为BirdTimeProxy,即用于获取方法执行时间的代理的意思。同时我们新建BirdLogProxy代理类用于打印日志:

    public class BirdLogProxy implements Flyable {
        private Flyable flyable;
    
        public BirdLogProxy(Flyable flyable) {
            this.flyable = flyable;
        }
    
        @Override
        public void fly() {
            System.out.println("Bird fly start...");
    
            flyable.fly();
    
            System.out.println("Bird fly end...");
        }
    }
    

    接下来神奇的事情发生了,如果我们需要先记录日志,再获取飞行时间,可以在调用的地方这么做:

        public static void main(String[] args) {
            Bird bird = new Bird();
            BirdLogProxy p1 = new BirdLogProxy(bird);
            BirdTimeProxy p2 = new BirdTimeProxy(p1);
    
            p2.fly();
        }
    

    反过来,可以这么做:

     public static void main(String[] args) {
            Bird bird = new Bird();
            BirdTimeProxy p2 = new BirdTimeProxy(bird);
            BirdLogProxy p1 = new BirdLogProxy(p2);
    
            p1.fly();
     }
    

    看到这里,有同学可能会有疑问了。虽然现象看起来,聚合可以灵活调换执行顺序。可是,为什么 聚合 可以做到,而继承不行呢。我们用一张图来解释一下:

    静态代理

    接下来,观察上面的类BirdTimeProxy,在它的fly方法中我们直接调用了flyable->fly()方法。换而言之,BirdTimeProxy其实代理了传入的Flyable对象,这就是典型的静态代理实现。

    从表面上看,静态代理已经完美解决了我们的问题。可是,试想一下,如果我们需要计算SDK中100个方法的运行时间,同样的代码至少需要重复100次,并且创建至少100个代理类。往小了说,如果Bird类有多个方法,我们需要知道其他方法的运行时间,同样的代码也至少需要重复多次。因此,静态代理至少有以下两个局限性问题:

    • 如果同时代理多个类,依然会导致类无限制扩展
    • 如果类中有多个方法,同样的逻辑需要反复实现

    那么,我们是否可以使用同一个代理类来代理任意对象呢?我们以获取方法运行时间为例,是否可以使用同一个类(例如:TimeProxy)来计算任意对象的任一方法的执行时间呢?甚至再大胆一点,代理的逻辑也可以自己指定。比如,获取方法的执行时间,打印日志,这类逻辑都可以自己指定。这就是本文重点探讨的问题,也是最难理解的部分:动态代理

    动态代理

    继续回到上面这个问题:是否可以使用同一个类(例如:TimeProxy)来计算任意对象的任一方法的执行时间呢。

    这个部分需要一定的抽象思维,我想,你脑海中的第一个解决方案应该是使用反射。反射是用于获取已创建实例的方法或者属性,并对其进行调用或者赋值。很明显,在这里,反射解决不了问题。但是,再大胆一点,如果我们可以动态生成TimeProxy这个类,并且动态编译。然后,再通过反射创建对象并加载到内存中,不就实现了对任意对象进行代理了吗?为了防止你依然一头雾水,我们用一张图来描述接下来要做什么:


    动态生成Java源文件并且排版是一个非常繁琐的工作,为了简化操作,我们使用 JavaPoet 这个第三方库帮我们生成TimeProxy的源码。希望 JavaPoet 不要成为你的负担,不理解 JavaPoet 没有关系,你只要把它当成一个Java源码生成工具使用即可。

    PS:你记住,任何工具库的使用都不会太难,它是为了简化某些操作而出现的,目标是简化而不是繁琐。因此,只要你适应它的规则就轻车熟路了。

    第一步:生成TimeProxy源码
    public class Proxy {
    
        public static Object newProxyInstance() throws IOException {
            TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("TimeProxy")
                    .addSuperinterface(Flyable.class);
    
            FieldSpec fieldSpec = FieldSpec.builder(Flyable.class, "flyable", Modifier.PRIVATE).build();
            typeSpecBuilder.addField(fieldSpec);
    
            MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(Flyable.class, "flyable")
                    .addStatement("this.flyable = flyable")
                    .build();
            typeSpecBuilder.addMethod(constructorMethodSpec);
    
            Method[] methods = Flyable.class.getDeclaredMethods();
            for (Method method : methods) {
                MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())
                        .addModifiers(Modifier.PUBLIC)
                        .addAnnotation(Override.class)
                        .returns(method.getReturnType())
                        .addStatement("long start = $T.currentTimeMillis()", System.class)
                        .addCode("\n")
                        .addStatement("this.flyable." + method.getName() + "()")
                        .addCode("\n")
                        .addStatement("long end = $T.currentTimeMillis()", System.class)
                        .addStatement("$T.out.println(\"Fly Time =\" + (end - start))", System.class)
                        .build();
                typeSpecBuilder.addMethod(methodSpec);
            }
    
            JavaFile javaFile = JavaFile.builder("com.youngfeng.proxy", typeSpecBuilder.build()).build();
            // 为了看的更清楚,我将源码文件生成到桌面
            javaFile.writeTo(new File("/Users/ouyangfeng/Desktop/"));
    
            return null;
        }
    
    }
    

    在main方法中调用Proxy.newProxyInstance(),你将看到桌面已经生成了TimeProxy.java文件,生成的内容如下:

    package com.youngfeng.proxy;
    
    import java.lang.Override;
    import java.lang.System;
    
    class TimeProxy implements Flyable {
      private Flyable flyable;
    
      public TimeProxy(Flyable flyable) {
        this.flyable = flyable;
      }
    
      @Override
      public void fly() {
        long start = System.currentTimeMillis();
    
        this.flyable.fly();
    
        long end = System.currentTimeMillis();
        System.out.println("Fly Time =" + (end - start));
      }
    }
    
    第二步:编译TimeProxy源码

    编译TimeProxy源码我们直接使用JDK提供的编译工具即可,为了使你看起来更清晰,我使用一个新的辅助类来完成编译操作:

    public class JavaCompiler {
    
        public static void compile(File javaFile) throws IOException {
            javax.tools.JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
            Iterable iterable = fileManager.getJavaFileObjects(javaFile);
            javax.tools.JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, iterable);
            task.call();
            fileManager.close();
        }
    }
    

    在Proxy->newProxyInstance()方法中调用该方法,编译顺利完成:

    // 为了看的更清楚,我将源码文件生成到桌面
    String sourcePath = "/Users/ouyangfeng/Desktop/";
    javaFile.writeTo(new File(sourcePath));
    
    // 编译
    JavaCompiler.compile(new File(sourcePath + "/com/youngfeng/proxy/TimeProxy.java"));
    
    第三步:加载到内存中并创建对象
      URL[] urls = new URL[] {new URL("file:/" + sourcePath)};
      URLClassLoader classLoader = new URLClassLoader(urls);
      Class clazz = classLoader.loadClass("com.youngfeng.proxy.TimeProxy");
      Constructor constructor = clazz.getConstructor(Flyable.class);
      Flyable flyable = (Flyable) constructor.newInstance(new Bird());
      flyable.fly();
    

    通过以上三个步骤,我们至少解决了下面两个问题:

    • 不再需要手动创建TimeProxy
    • 可以代理任意实现了Flyable接口的类对象,并获取接口方法的执行时间

    可是,说好的任意对象呢?

    第四步:增加InvocationHandler接口

    查看Proxy->newProxyInstance()的源码,代理类继承的接口我们是写死的,为了增加灵活性,我们将接口类型作为参数传入:


    接口的灵活性问题解决了,TimeProxy的局限性依然存在,它只能用于获取方法的执行时间,而如果要在方法执行前后打印日志则需要重新创建一个代理类,显然这是不妥的!

    为了增加控制的灵活性,我们考虑针将代理的处理逻辑也抽离出来(这里的处理就是打印方法的执行时间)。新增InvocationHandler接口,用于处理自定义逻辑:

    public interface InvocationHandler {
        void invoke(Object proxy, Method method, Object[] args);
    }
    

    想象一下,如果客户程序员需要对代理类进行自定义的处理,只要实现该接口,并在invoke方法中进行相应的处理即可。这里我们在接口中设置了三个参数(其实也是为了和JDK源码保持一致):

    • proxy => 这个参数指定动态生成的代理类,这里是TimeProxy
    • method => 这个参数表示传入接口中的所有Method对象
    • args => 这个参数对应当前method方法中的参数

    引入了InvocationHandler接口之后,我们的调用顺序应该变成了这样:

    MyInvocationHandler handler = new MyInvocationHandler();
    Flyable proxy = Proxy.newProxyInstance(Flyable.class, handler);
    proxy.fly();
    
    方法执行流:proxy.fly() => handler.invoke()
    

    为此,我们需要在Proxy.newProxyInstance()方法中做如下改动:

    • 在newProxyInstance方法中传入InvocationHandler
    • 在生成的代理类中增加成员变量handler
    • 在生成的代理类方法中,调用invoke方法
      public static Object newProxyInstance(Class inf, InvocationHandler handler) throws Exception {
            TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("TimeProxy")
                    .addModifiers(Modifier.PUBLIC)
                    .addSuperinterface(inf);
    
            FieldSpec fieldSpec = FieldSpec.builder(InvocationHandler.class, "handler", Modifier.PRIVATE).build();
            typeSpecBuilder.addField(fieldSpec);
    
            MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(InvocationHandler.class, "handler")
                    .addStatement("this.handler = handler")
                    .build();
    
            typeSpecBuilder.addMethod(constructorMethodSpec);
    
            Method[] methods = inf.getDeclaredMethods();
            for (Method method : methods) {
                MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())
                        .addModifiers(Modifier.PUBLIC)
                        .addAnnotation(Override.class)
                        .returns(method.getReturnType())
                        .addCode("try {\n")
                        .addStatement("\t$T method = " + inf.getName() + ".class.getMethod(\"" + method.getName() + "\")", Method.class)
                        // 为了简单起见,这里参数直接写死为空
                        .addStatement("\tthis.handler.invoke(this, method, null)")
                        .addCode("} catch(Exception e) {\n")
                        .addCode("\te.printStackTrace();\n")
                        .addCode("}\n")
                        .build();
                typeSpecBuilder.addMethod(methodSpec);
            }
    
            JavaFile javaFile = JavaFile.builder("com.youngfeng.proxy", typeSpecBuilder.build()).build();
            // 为了看的更清楚,我将源码文件生成到桌面
            String sourcePath = "/Users/ouyangfeng/Desktop/";
            javaFile.writeTo(new File(sourcePath));
    
            // 编译
            JavaCompiler.compile(new File(sourcePath + "/com/youngfeng/proxy/TimeProxy.java"));
    
            // 使用反射load到内存
            URL[] urls = new URL[] {new URL("file:" + sourcePath)};
            URLClassLoader classLoader = new URLClassLoader(urls);
            Class clazz = classLoader.loadClass("com.youngfeng.proxy.TimeProxy");
            Constructor constructor = clazz.getConstructor(InvocationHandler.class);
            Object obj = constructor.newInstance(handler);
    
            return obj;
     }
    

    上面的代码你可能看起来比较吃力,我们直接调用该方法,查看最后生成的源码。在main方法中测试newProxyInstance查看生成的TimeProxy源码:

    测试代码

    Proxy.newProxyInstance(Flyable.class, new MyInvocationHandler(new Bird()));
    

    生成的TimeProxy.java源码

    package com.youngfeng.proxy;
    
    import java.lang.Override;
    import java.lang.reflect.Method;
    
    public class TimeProxy implements Flyable {
      private InvocationHandler handler;
    
      public TimeProxy(InvocationHandler handler) {
        this.handler = handler;
      }
    
      @Override
      public void fly() {
        try {
            Method method = com.youngfeng.proxy.Flyable.class.getMethod("fly");
            this.handler.invoke(this, method, null);
        } catch(Exception e) {
            e.printStackTrace();
        }
      }
    }
    

    MyInvocationHandler.java

    public class MyInvocationHandler implements InvocationHandler {
        private Bird bird;
    
        public MyInvocationHandler(Bird bird) {
            this.bird = bird;
        }
    
        @Override
        public void invoke(Object proxy, Method method, Object[] args) {
            long start = System.currentTimeMillis();
    
            try {
                method.invoke(bird, new Object[] {});
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
    
            long end = System.currentTimeMillis();
            System.out.println("Fly time = " + (end - start));
        }
    }
    

    至此,整个方法栈的调用栈变成了这样:


    看到这里,估计很多同学已经晕了,在静态代理部分,我们在代理类中传入了被代理对象。可是,使用newProxyInstance生成动态代理对象的时候,我们居然不再需要传入被代理对象了。我们传入了的实际对象是InvocationHandler实现类的实例,这看起来有点像生成了InvocationHandler的代理对象,在动态生成的代理类的任意方法中都会间接调用InvocationHandler->invoke(proxy, method, args)方法。

    其实的确是这样。TimeProxy真正代理的对象就是InvocationHandler,不过这里设计的巧妙之处在于,InvocationHandler是一个接口,真正的实现由用户指定。另外,在每一个方法执行的时候,invoke方法都会被调用 ,这个时候如果你需要对某个方法进行自定义逻辑处理,可以根据method的特征信息进行判断分别处理。

    如何使用

    上面这段解释是告诉你在执行Proxy->newProxyInstance方法的时候真正发生的事情,而在实际使用过程中你完全可以忘掉上面的解释。按照设计者的初衷,我们做如下简单归纳:

    • Proxy->newProxyInstance(infs, handler) 用于生成代理对象
    • InvocationHandler:这个接口主要用于自定义代理逻辑处理
    • 为了完成对被代理对象的方法拦截,我们需要在InvocationHandler对象中传入被代理对象实例。

    查看上面的代码,你可以看到我将Bird实例已经传入到了MyInvocationHandler中,原因就是第三点。

    这样设计有什么好处呢?有人说,我们大费周章,饶了一大圈,最终变成了这个样子,到底图什么呢?

    想象一下,到此为止,如果我们还需要对其它任意对象进行代理,是否还需要改动newProxyInstance方法的源码,答案是:完全不需要!

    只要你在newProxyInstance方法中指定代理需要实现的接口,指定用于自定义处理的InvocationHandler对象,整个代理的逻辑处理都在你自定义的InvocationHandler实现类中进行处理。至此,而我们终于可以从不断地写代理类用于实现自定义逻辑的重复工作中解放出来了,从此需要做什么,交给InvocationHandler。

    事实上,我们之前给自己定下的目标“使用同一个类来计算任意对象的任一方法的执行时间”已经实现了。严格来说,是我们超额完成了任务,TimeProxy不仅可以计算方法执行的时间,也可以打印方法执行日志,这完全取决于你的InvocationHandler接口实现。因此,这里取名为TimeProxy其实已经不合适了。我们可以修改为和JDK命名一致,即$Proxy0,感兴趣的同学请自行实践,本篇文章的代码将放到我的Github仓库,文章结尾会给出代码地址。

    JDK实现揭秘

    通过上面的这些步骤,我们完成了一个简易的仿JDK实现的动态代理逻辑。接下来,我们一起来看一看JDK实现的动态代理和我们到底有什么不同。

    Proxy.java

    InvocationHandler

    可以看到,官方版本Proxy类提供的方法多一些,而我们主要使用的接口newProxyInstance参数也和我们设计的不太一样。这里给大家简单解释一下,每个参数的意义:

    • Classloader:类加载器,你可以使用自定义的类加载器,我们的实现版本为了简化,直接在代码中写死了Classloader。
    • Class<?>[]:第二个参数也和我们的实现版本不一致,这个其实很容易理解,我们应该允许我们自己实现的代理类同时实现多个接口。前面设计只传入一个接口,只是为了简化实现,让你专注核心逻辑实现而已。

    最后一个参数就不用说了,和我们实现的版本完全是一样的。

    仔细观察官方版本的InvocationHandler,它和我们自己的实现的版本也有一个细微的差别:官方版本invoke方法有返回值,而我们的版本中是没有返回值的。那么,返回值到底有什么作用呢?直接来看官方文档:


    核心思想:这里的返回值类型必须和传入接口的返回值类型一致,或者与其封装对象的类型一致。

    遗憾的是,这里并没有说明返回值的用途,其实这里稍微发挥一下想象力就知道了。在我们的版本实现中,Flyable接口的所有方法都是没有返回值的,问题是,如果有返回值呢?是的,你没有猜错,这里的invoke方法对应的就是传入接口中方法的返回值。

    答疑解惑

    invoke方法的第一个参数proxy到底有什么作用?

    这个问题其实也好理解,如果你的接口中有方法需要返回自身,如果在invoke中没有传入这个参数,将导致实例无法正常返回。在这种场景中,proxy的用途就表现出来了。简单来说,这其实就是最近非常火的链式编程的一种应用实现。

    动态代理到底有什么用?

    学习任何一门技术,一定要问一问自己,这到底有什么用。其实,在这篇文章的讲解过程中,我们已经说出了它的主要用途。你发现没,使用动态代理我们居然可以在不改变源码的情况下,直接在方法中插入自定义逻辑。这有点不太符合我们的一条线走到底的编程逻辑,这种编程模型有一个专业名称叫 AOP。所谓的AOP,就像刀一样,抓住时机,趁机插入。

    基于这样一种动态特性,我们可以用它做很多事情,例如:

    • 事务提交或回退(Web开发中很常见)
    • 权限管理
    • 自定义缓存逻辑处理
    • SDK Bug修复
      ...

    如果你阅读过 Android_Slide_To_Close 的源码会发现,它也在某个地方使用了动态代理设计模式。

    总结

    到此为止,关于动态代理的所有讲解已经结束了,原谅我使用了一个诱导性的标题“骗”你进来阅读这篇文章。如果你不是一个久经沙场的“老司机”,10分钟完全看懂动态代理设计模式还是有一定难度的。但即使没有看懂也没关系,如果你在第一次阅读完这篇文章后依然一头雾水,就不妨再仔细阅读一次。在阅读的过程中,一定要跟着文章思路去敲代码。反反复复,一定会看懂的。我在刚刚学习动态代理设计模式的时候就反复看了不下5遍,并且亲自敲代码实践了多次。

    为了让你少走弯路,我认为看懂这篇文章,你至少需要学习以下知识点:

    • 至少已经理解了面向对象语言的多态特性
    • 了解简单的反射用法
    • 会简单使用 JavaPoet 生成Java源码

    如果你在阅读文章的过程中,有任何不理解的问题或者建议,欢迎在文章下方留言告诉我!

    本篇文章例子代码:https://github.com/yuanhoujun/java-dynamic-proxy



    作者:欧阳锋
    链接:https://www.jianshu.com/p/fc285d669bc5
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    展开全文
  • Java两种动态代理JDK动态代理和CGLIB动态代理

    万次阅读 多人点赞 2018-08-07 15:33:35
    JDK动态代理 cglib动态代理 测试 代理模式 代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是...

    目录

    代理模式

    JDK动态代理

    cglib动态代理

    测试


    代理模式

    代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态“织入”。

    使用代理好处还不止这些,一个工程如果依赖另一个工程给的接口,但是另一个工程的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要一一修改业务代码。从这个意义上说,所有调外界的接口,我们都可以这么做,不让外界的代码对我们的代码有侵入,这叫防御式编程。代理其他的应用可能还有很多。

    上述例子中,类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,jdk动态代理和cglib动态代理。

    JDK动态代理

    jdk动态代理是jre提供给我们的类库,可以直接使用,不依赖第三方。先看下jdk动态代理的使用代码,再理解原理。

    首先有个“明星”接口类,有唱、跳两个功能:

    package proxy;
    
    public interface Star
    {
        String sing(String name);
        
        String dance(String name);
    }
    

    再有个明星实现类“刘德华”:

    package proxy;
    
    public class LiuDeHua implements Star
    {   
        @Override
        public String sing(String name)
        {
             System.out.println("给我一杯忘情水");
    
            return "唱完" ;
        }
        
        @Override
        public String dance(String name)
        {
            System.out.println("开心的马骝");
    
            return "跳完" ;
        }
    }
    

    明星演出前需要有人收钱,由于要准备演出,自己不做这个工作,一般交给一个经纪人。便于理解,它的名字以Proxy结尾,但他不是代理类,原因是它没有实现我们的明星接口,无法对外服务,它仅仅是一个wrapper。

    package proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class StarProxy implements InvocationHandler
    {
        // 目标类,也就是被代理对象
        private Object target;
        
        public void setTarget(Object target)
        {
            this.target = target;
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            // 这里可以做增强
            System.out.println("收钱");
            
            Object result = method.invoke(target, args);
            
            return result;
        }
        
        // 生成代理类
        public Object CreatProxyedObj()
        {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }  
       
    }
    

    上述例子中,方法CreatProxyedObj返回的对象才是我们的代理类,它需要三个参数,前两个参数的意思是在同一个classloader下通过接口创建出一个对象,该对象需要一个属性,也就是第三个参数,它是一个InvocationHandler。需要注意的是这个CreatProxyedObj方法不一定非得在我们的StarProxy类中,往往放在一个工厂类中。上述代理的代码使用过程一般如下:

    1、new一个目标对象

    2、new一个InvocationHandler,将目标对象set进去

    3、通过CreatProxyedObj创建代理对象,强转为目标对象的接口类型即可使用,实际上生成的代理对象实现了目标接口。

            Star ldh = new LiuDeHua();
    
            StarProxy proxy = new StarProxy();
    
            proxy.setTarget(ldh); 
      
            Object obj = proxy.CreatProxyedObj();
            
            Star star = (Star)obj;

    Proxy(jdk类库提供)根据B的接口生成一个实现类,我们成为C,它就是动态代理类(该类型是 $Proxy+数字 的“新的类型”)。生成过程是:由于拿到了接口,便可以获知接口的所有信息(主要是方法的定义),也就能声明一个新的类型去实现该接口的所有方法,这些方法显然都是“虚”的,它调用另一个对象的方法。当然这个被调用的对象不能是对象B,如果是对象B,我们就没法增强了,等于饶了一圈又回来了。

    所以它调用的是B的包装类,这个包装类需要我们来实现,但是jdk给出了约束,它必须实现InvocationHandler,上述例子中就是StarProxy, 这个接口里面有个方法,它是所有Target的所有方法的调用入口(invoke),调用之前我们可以加自己的代码增强。

    看下我们的实现,我们在InvocationHandler里调用了对象B(target)的方法,调用之前增强了B的方法。

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
        {
            // 这里增强
            System.out.println("收钱");
            
            Object result = method.invoke(target, args);
            
            return result;
        }

    所以可以这么认为C代理了InvocationHandler,InvocationHandler代理了我们的类B,两级代理。

    整个JDK动态代理的秘密也就这些,简单一句话,动态代理就是要生成一个包装类对象,由于代理的对象是动态的,所以叫动态代理。由于我们需要增强,这个增强是需要留给开发人员开发代码的,因此代理类不能直接包含被代理对象,而是一个InvocationHandler,该InvocationHandler包含被代理对象,并负责分发请求给被代理对象,分发前后均可以做增强。从原理可以看出,JDK动态代理是“对象”的代理。

    下面看下动态代理类到底如何调用的InvocationHandler的,为什么InvocationHandler的一个invoke方法能为分发target的所有方法。C中的部分代码示例如下,通过反编译生成后的代码查看,摘自链接地址。Proxy创造的C是自己(Proxy)的子类,且实现了B的接口,一般都是这么修饰的:

    public final class XXX extends Proxy implements XXX
    

    一个方法代码如下:

    
      public final String SayHello(String paramString)
      {
        try
        {
          return (String)this.h.invoke(this, m4, new Object[] { paramString });
        }
        catch (Error|RuntimeException localError)
        {
          throw localError;
        }
        catch (Throwable localThrowable)
        {
          throw new UndeclaredThrowableException(localThrowable);
        }
    

    可以看到,C中的方法全部通过调用h实现,其中h就是InvocationHandler,是我们在生成C时传递的第三个参数。这里还有个关键就是SayHello方法(业务方法)跟调用invoke方法时传递的参数m4一定要是一一对应的,但是这些对我们来说都是透明的,由Proxy在newProxyInstance时保证的。留心看到C在invoke时把自己this传递了过去,InvocationHandler的invoke的第一个方法也就是我们的动态代理实例类,业务上有需要就可以使用它。(所以千万不要在invoke方法里把请求分发给第一个参数,否则很明显就死循环了)

    C类中有B中所有方法的成员变量

      private static Method m1;
      private static Method m3;
      private static Method m4;
      private static Method m2;
      private static Method m0;
    

    这些变量在static静态代码块初始化,这些变量是在调用invocationhander时必要的入参,也让我们依稀看到Proxy在生成C时留下的痕迹。

    static
      {
        try
        {
          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
          m3 = Class.forName("jiankunking.Subject").getMethod("SayGoodBye", new Class[0]);
          m4 = Class.forName("jiankunking.Subject").getMethod("SayHello", new Class[] { Class.forName("java.lang.String") });
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          return;
        }
        catch (NoSuchMethodException localNoSuchMethodException)
        {
          throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException localClassNotFoundException)
        {
          throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
      }
    

    从以上分析来看,要想彻底理解一个东西,再多的理论不如看源码,底层的原理非常重要。

    jdk动态代理类图如下

    cglib动态代理

    我们了解到,“代理”的目的是构造一个和被代理的对象有同样行为的对象,一个对象的行为是在类中定义的,对象只是类的实例。所以构造代理,不一定非得通过持有、包装对象这一种方式。

    通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。根据里氏代换原则(LSP),父类需要出现的地方,子类可以出现,所以cglib实现的代理也是可以被正常使用的。

    先看下代码

    package proxy;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class CglibProxy implements MethodInterceptor
    {
        // 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
        public Object CreatProxyedObj(Class<?> clazz)
        {
            Enhancer enhancer = new Enhancer();
            
            enhancer.setSuperclass(clazz);
            
            enhancer.setCallback(this);
            
            return enhancer.create();
        }
        
        @Override
        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
        {
            // 这里增强
            System.out.println("收钱");
            
            return arg3.invokeSuper(arg0, arg2);
        } 
    }
    

    从代码可以看出,它和jdk动态代理有所不同,对外表现上看CreatProxyedObj,它只需要一个类型clazz就可以产生一个代理对象, 所以说是“类的代理”,且创造的对象通过打印类型发现也是一个新的类型。不同于jdk动态代理,jdk动态代理要求对象必须实现接口(三个参数的第二个参数),cglib对此没有要求。

    cglib的原理是这样,它生成一个继承B的类型C(代理类),这个代理类持有一个MethodInterceptor,我们setCallback时传入的。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“$父类方法名$”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。

    这样的话,C中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。其中重写方法和cglib方法肯定是有映射关系的。

    C的重写方法是外界调用的入口(LSP原则),它调用MethodInterceptor的intercept方法,调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。

    这里有个疑问就是intercept的四个参数,为什么我们使用的是arg3而不是arg1?

        @Override
        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
        {
            System.out.println("收钱");
            
            return arg3.invokeSuper(arg0, arg2);
        }

     因为如果我们通过反射 arg1.invoke(arg0, ...)这种方式是无法调用到父类的方法的,子类有方法重写,隐藏了父类的方法,父类的方法已经不可见,如果硬调arg1.invoke(arg0, ...)很明显会死循环。

    所以调用的是cglib开头的方法,但是,我们使用arg3也不是简单的invoke,而是用的invokeSuper方法,这是因为cglib采用了fastclass机制,不仅巧妙的避开了调不到父类方法的问题,还加速了方法的调用。

    fastclass基本原理是,给每个方法编号,通过编号找到方法执行避免了通过反射调用。

    对比JDK动态代理,cglib依然需要一个第三者分发请求,只不过jdk动态代理分发给了目标对象,cglib最终分发给了自己,通过给method编号完成调用。cglib是继承的极致发挥,本身还是很简单的,只是fastclass需要另行理解。

    参考

    https://blog.csdn.net/jiankunking/article/details/52143504

    http://www.php.cn/java-article-407212.html

    https://www.cnblogs.com/chinajava/p/5880887.html

    https://rejoy.iteye.com/blog/1627405

    测试

       public static void main(String[] args)
        {
            int times = 1000000;
            
            Star ldh = new LiuDeHua();
            StarProxy proxy = new StarProxy();
            proxy.setTarget(ldh);
            
            long time1 = System.currentTimeMillis();
            Star star = (Star)proxy.CreatProxyedObj();
            long time2 = System.currentTimeMillis();
            System.out.println("jdk创建时间:" + (time2 - time1));
            
            CglibProxy proxy2 = new CglibProxy();
            long time5 = System.currentTimeMillis();
            Star star2 = (Star)proxy2.CreatProxyedObj(LiuDeHua.class);
            long time6 = System.currentTimeMillis();
            System.out.println("cglib创建时间:" + (time6 - time5));
            
            long time3 = System.currentTimeMillis();
            for (int i = 1; i <= times; i++)
            {
                star.sing("ss");
                
                star.dance("ss");
            }
            long time4 = System.currentTimeMillis();
            System.out.println("jdk执行时间" + (time4 - time3));
            
            long time7 = System.currentTimeMillis();
            for (int i = 1; i <= times; i++)
            {
                star2.sing("ss");
                
                star2.dance("ss");
            }
            
            long time8 = System.currentTimeMillis();
            
            System.out.println("cglib执行时间" + (time8 - time7));   
        }

    经测试,jdk创建对象的速度远大于cglib,这是由于cglib创建对象时需要操作字节码。cglib执行速度略大于jdk,所以比较适合单例模式。另外由于CGLIB的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。spring默认使用jdk动态代理,如果类没有接口,则使用cglib。

    展开全文
  • 动态代理概念: 动态代理也是实现代理模式的一种方法,他比静态代理强在了他使用了反射,可以在运行时动态创建代理类。 特点:字节码随用随创建,随用随加载 作用:不修改源码的基础上对方法增强(对被代理类的方法...
  • 动态代理

    千次阅读 2019-03-15 21:38:59
    动态代理 cglib代理 动态代理 代理模式: 静态代理 动态代理 (Spring如果发现被代理的类有接口的话,就用动态代理,否则用cglib) 动态代理 反射 JAVA的动态代理 代理模式 代理模式是常用的java设计...
  • java动态代理详解

    万次阅读 多人点赞 2018-08-21 15:32:51
    目前java动态代理的实现分为两种 1.基于JDK的动态代理 2.基于CGILB的动态代理 在业务中使用动态代理,一般是为了给需要实现的方法添加预处理或者添加后续操作,但是不干预实现类的正常业务,把一些基本业务和主要...
  • 动态代理:当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了...
  • 详解java动态代理机制以及使用场景(一)

    万次阅读 多人点赞 2017-10-19 10:29:10
    说起java动态代理,在我刚开始学java时对这项技术也是十分困惑,明明可以直接调通的对象方法为什么还要使用动态代理?随着学习的不断深入和工作经验的积累,慢慢的体会并理解了java动态代理机制。昨天再给公司新同事...
  • Java JDK 动态代理(AOP)使用及实现原理分析

    万次阅读 多人点赞 2019-05-08 21:28:06
    二、Java 动态代理类 三、JDK的动态代理怎么使用? 四、动态代理怎么实现的? 五、结论 一、什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为...
  • 【Spring基础】JDK和CGLIB动态代理区别

    万次阅读 多人点赞 2018-06-09 18:34:17
    前言 Github:... JDK动态代理实现原理(jdk8):https://blog.csdn.net/yhl_jxy/article/details/80586785 CGLIB动态代理实现原理:https://blog.csdn.net/yhl_jxy/article/details/806331...
  • 【Spring基础】CGLIB动态代理实现原理

    万次阅读 多人点赞 2018-06-09 18:11:19
    前言 ... 一 CGLIB介绍 CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库, 它可以在运行期扩展Java类与实现Java接口。Hibernate用它来实现PO(Persistent Object 持久化...
  • 上篇介绍了一下静态代理:Java中的代理模式——静态代理以及分析静态代理的缺点 也分析了一下静态代理的缺点: 1、由于静态代理中的代理类是针对某一个类去做代理的,那么假设一个系统中有100个Service,则需要...
  • 静态代理和动态代理

    万次阅读 2019-03-03 16:04:26
    代理Proxy: Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理...
  • Java笔试面试-反射和动态代理

    万次阅读 多人点赞 2019-09-19 09:28:41
    反射   反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect)的能力。简单来说就是通过反射,可以在运行期间获取、检测和调用对象的属性和方法。 反射的使用场景 1.编程工具 IDEA 或 ...
  • 秒懂Java代理与动态代理模式

    万次阅读 多人点赞 2018-06-30 17:08:23
    什么是动态代理模式?二者什么关系?具体如何实现?什么原理?如何改进?这即为我们学习一项新知识的正确打开方式,我们接下来会以此展开,让你秒懂。 概念 什么是代理模式 定义:为其他对象提供一种代理以控制...
  • jdk动态代理和cglib动态代理详解

    千次阅读 多人点赞 2018-08-22 15:14:11
    本文内容概括: 静态代理概述 基于继承方式实现静态代理 基于聚合方式实现静态代理 ...如上图,代理模式可分为动态代理和静态代理,我们比较常用的有动态代理中的jdk动态代理和Cglib代理,像spr...
  • /// 装饰器模式实现静态代理 /// AOP 在方法前后增加自定义的方法 /// </summary> public class DecoratorAOP { public static void Show() { User user = new User() { Name = "看看看看", Password = ...
  • Android 动态代理以及利用动态代理实现 ServiceHook

    万次阅读 多人点赞 2017-02-25 20:44:15
    Android 利用 ServiceHook 实现特殊功能
  • Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

    万次阅读 多人点赞 2014-06-03 15:48:04
    本文阐述:class 文件 和代码中的class 对象之间的关系;动态代理中Invocation Handler角色的由来;Javassist 和ASM框架生成字节码;类加载器
1 2 3 4 5 ... 20
收藏数 370,696
精华内容 148,278
关键字:

动态代理