java梳理-静态代理和动态代理有什么区别

 一 概念
为某个对象提供一个代理,以控制对这个对象的访问。 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。   
   代理模式是经典设计模式中的一种,属于对象创建型设计模式。

  从图中可以看出,代理接口(Subject)、代理类(ProxySubject)、委托类(RealSubject)形成一个“品”字结构。  
根据代理类的生成时间不同可以将代理分为静态代理和动态代理两种。 
下面以一个模拟需求说明静态代理和动态代理:
二 静态代理
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。 
清单1:代理接口 

/** * 代理接口。处理给定名字的任务。 */ public interface Subject { /** * 执行给定名字的任务。 * @param taskName 任务名 */ public void dealTask(String taskName); }

RealSubject(真实角色):

/** * 真实类,处理具体业务 * @author zhangliang * * 2016年4月5日 下午6:53:16 */ public class RealSubject implements Subject { @Override public void dealTask(String taskName) { // TODO Auto-generated method stub System.out.println("realSubject正在执行任务:"+taskName); } }

ProxySubject(代理类)

/** * 代理类,实现类代理接口 * @author zhangliang * 2016年4月5日 下午6:58:06 */ public class ProxySubject implements Subject { //代理类持有一个委托类的对象引用 private Subject delegate; public ProxySubject(Subject delegate) { this.delegate = delegate; } @Override public void dealTask(String taskName) { // TODO Auto-generated method stub System.out.println("do something before"); delegate.dealTask(taskName); System.out.println("do something after"); } }

测试类:

静态代理类优缺点  
优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。 
缺点: 
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。 
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。 
三 动态代理
代理类处理的逻辑很简单:在调用某个方法前及方法后做一些额外的业务。换一种思路就是:在触发(invoke)真实角色的方法之前或者之后做一些额外的业务。那么,为了构造出具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是Invocation Handler。

动态代理模式的结构跟上面的静态代理模式稍微有所不同,多引入了一个InvocationHandler角色。

先解释一下InvocationHandler的作用:

在静态代理中,代理Proxy中的方法,都指定了调用了特定的realSubject中的对应的方法:

在上面的静态代理模式下,Proxy所做的事情,无非是调用在不同的request时,调用触发realSubject对应的方法;更抽象点看,Proxy所作的事情;在Java中 方法(Method)也是作为一个对象来看待了,动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法。如下图所示:



在这种模式之中:代理Proxy 和RealSubject 应该实现相同的功能,这一点相当重要。(我这里说的功能,可以理解为某个类的public方法)

在面向对象的编程之中,如果我们想要约定Proxy 和RealSubject可以实现相同的功能,有两种方式:

    a.一个比较直观的方式,就是定义一个功能接口,然后让Proxy 和RealSubject来实现这个接口。

    b.还有比较隐晦的方式,就是通过继承。因为如果Proxy 继承自RealSubject,这样Proxy则拥有了RealSubject的功能,Proxy还可以通过重写RealSubject中的方法,来实现多态。

其中JDK中提供的创建动态代理的机制,是以a 这种思路设计的,而cglib 则是以b思路设计的。

JDK的动态代理创建机制----通过接口

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。 


 
1、先看看与动态代理紧密关联的Java API。 
1)java.lang.reflect.Proxy 
这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
 // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器  
static InvocationHandler getInvocationHandler(Object proxy)     
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象  
static Class getProxyClass(ClassLoader loader, Class[] interfaces)     
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类  
static boolean isProxyClass(Class cl)     
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例  
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 

  2)java.lang.reflect.InvocationHandler  
这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。 
invoke(Object obj,Method method, Object[] args)。在实际使用时,第一个参数obj一般是指代理 类,method是被代理的方法,args为该方法的参数数组。 这个抽 象方法在代理类中动态实现。
2、动态代理实现步骤  
具体步骤是: 
a. 实现InvocationHandler接口创建自己的调用处理器 
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类 
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数 
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象 
demo如下:

/** * 动态代理类对应的调用处理程序类 */ public class SubjectInvocationHandler implements InvocationHandler { //代理类持有一个委托类的对象引用 private Object delegate; public SubjectInvocationHandler(Object delegate) { this.delegate = delegate; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("proxy:"+proxy.getClass().getName()); System.out.println("method:"+method.getName()); System.out.println("args:"+args[0].getClass().getName()); System.out.println("Before invoke method..."); Object object=method.invoke(delegate, args);//普通的Java反射代码,通过反射执行某个类的某方法 System.out.println("After invoke method..."); return object; } }


后来看了下,补充如下动态代理的核心其实就是代理对象的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler)。

源码如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }

        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
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler. 获取待invocation handler参数的构造器
         */
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            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());
        }
    }

其中newInstance只是调用Constructor.newInstance来构造相应的代理类实例,我们看下getProxyClass(loader, interfaces)方法用于获取代理类,它主要做了三件事情:在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地JVM里。清单三:查找代理类。

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        // 代理的接口数量不能超过65535(没有这种变态吧)
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        // JDK对代理进行了缓存,如果已经存在相应的代理类,则直接返回,否则才会通过ProxyClassFactory来创建代理
        return proxyClassCache.get(loader, interfaces);
    }

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

        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            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
            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);
                }
            }
        }
    }

具体的缓存逻辑这里暂不关心,在这个get方法里,我们看到了如下代码:
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

ProxyClassFactory是Proxy的一个静态内部类,实现了WeakCache的内部接口BiFunction的apply方法,主要生成并加载代码:

  /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces);
            try {
                return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
此处分别是:生成代理类的字节码文件并保存到硬盘中(默认不保存到硬盘)   

使用类加载器将字节码加载到内存中 

ProxyGenerator是sun.misc包中的类,它没有开源,感兴趣的可以用反编译软件打开jre\lib\rt.jar。        

再回顾下,由Proxy类的getProxyClass0()方法生成目标代理类,然后拿到该类的构造方法,最后通过反射的newInstance方法,产生代理类的实例对象。


使用Java动态代理机制的好处:

1、减少编程的工作量:假如需要实现多种代理处理逻辑,只要写多个代理处理器就可以了,无需每种方式都写一个代理类。 2、系统扩展性和维护性增强,程序修改起来也方便多了(一般只要改代理处理器类就行了)。

缺点:
代理类和委托类需要都实现同一个接口。也就是说只有实现了某个接口的类可以使用Java动态代理机制。但是,事实上使用中并不是遇到的所有类都会给你实现一个接口。因此,对于没有实现接口的类,目前无法使用该机制。 那怎么办呢?幸好我们有cglib。“CGLIB(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。”待整理。

回答代理使用场景:
我理解的两个作用:
1,方法增强, 让你可以在不修改源码的情况下,增强一些方法,比如添加日志等。
2. 以用作远程调用,好多rpc框架就是用代理方式实现的。
总结:
代理这块与反射有关系,调用了其中的方法,动态代理除了jdk的接口实现方式外还有cglib,以及相关的springAOP。还有RPC框架也是代理实现的。 从设计模式角度去看,还有跟装饰模式有区别。最近梳理知识点,越来越发现好多东西都是相关的。 本文也是学习笔记,没有从源码角度看下动态代理核心接口的实现。先理解其中的角色及关键步骤。
参考:
http://layznet.iteye.com/blog/1182924
http://my.oschina.net/feichexia/blog/201907
http://www.cnblogs.com/machine/archive/2013/02/21/2921345.html
http://blog.csdn.net/luanlouis/article/details/24589193
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值