设计模式(九)代理模式

栏目: 后端 · 发布时间: 7年前

内容简介:设计模式之禅在讲解代理模式时用了一个游戏代练的比喻。这个比喻非常的有代表性,对于理解代理模式很有帮助。它大致的思想是:大家都有过玩游戏的经历,也知道游戏代练。那么事实上游戏的代练在帮我的游戏账号打怪升级的时候,和代理模式里面的代理类做的事情不正是一样的事情吗?代理模式的定义如下:Provide a surrogate or placeholder for another object to control acess to it.(为其他对象提供一种代理以控制对这个对象的访问)。其通用类图如下:上面是一个静

设计模式之禅在讲解代理模式时用了一个游戏代练的比喻。这个比喻非常的有代表性,对于理解代理模式很有帮助。它大致的思想是:大家都有过玩游戏的经历,也知道游戏代练。那么事实上游戏的代练在帮我的游戏账号打怪升级的时候,和代理模式里面的代理类做的事情不正是一样的事情吗?

代理模式的定义如下:Provide a surrogate or placeholder for another object to control acess to it.(为其他对象提供一种代理以控制对这个对象的访问)。其通用类图如下:

设计模式(九)代理模式
  • Subject是一个抽象类也可以是一个接口,是一个最普通的业务类型定义无特殊要求

  • RealSubject是Subject的子类或实现类,它才是被代理角色。是业务逻辑的具体执行者。

  • Proxy叫做委托类,代理类。它负责对真实对象的应用。它在真实对象处理完毕前后做预处理和善后处理工作。

上面是一个静态代理的场景。代理一般实现的模式为JDK静态代理:创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。

代理模式的优点

职责清晰

真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

高扩展性

具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。

智能化

这在我们以上讲解中还没有体现出来,不过在我们以下的动态代理章节中你就会看到代理的智能化,读者有兴趣也可以看看Struts是如何把表单元素映射到对象上的。

动态代理

动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理那一个对象,相对的来说,自己写代理类的方式就是静态代理。

现在有一个非常流行的名称叫做:面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制。

Spring AOP实现动态代理采用了两种方式,即JDK动态代理和CGLIB动态代理。

JDK动态代理是如何实现的

核心:代理模式加反射。

JDK动态代理与静态代理有相同之处,都要创建代理类,代理类都要实现接口。但是不同之处在于:

JDK静态代理是通过直接编码创建的,而JDK动态代理是利用反射机制在运行时根据指定参数创建代理类的。即JDK动态代理是更加通用的的一种方式,因为我们需要被代理的类往往是不止一个的。

若我们要自己实现一个JDK代理的话,则可以按如下核心代码实现:

public class JDKProxy implements InvocationHandler {
 
    //产生代理对象,被代理的对象必须实现一个接口
    public Object newProxy(Object targetObject) {//将目标对象传入进行代理
        Object object = Proxy.newProxyInstance(
                targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(), 
                this);//返回代理对象
        return object;
    }
 
    //实现InvocationHandler的 invoke方法
    public Object invoke(Object proxy, Method method, Object[] args)//invoke方法
            throws Throwable {
        System.out.println("before");    //一般我们进行逻辑处理的函数比如这个地方是模拟检查权限
        
        Object  ret  = method.invoke(proxy, args);       //调用invoke方法,ret存储该方法的返回值,通过反射获取代理方法然后进行调用,而在cglib中则是直接调用的,因为继承的缘故
 
        System.out.println("after"); //后置增强
        
        return ret;
    }
}

复制代码

第一个方法根据被代理类生成代理类,第二个方法实现增强逻辑,第二个方法调用被代理方法是通过反射(method.invoke)实现的。

先来看第一个方法newProxy

这个方法的目标是根据被代理对象去创建一个代理类。关键方法为

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

复制代码

此方法一共三个参数:

  • ClassLoader 对象

  • 一组interface接口

  • 一个InvocationHandler对象

该方法关键实现如下:

@CallerSensitive
    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 {
            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) {
             ...
        }
    }

复制代码
  • getProxyClass0方法的两个参数分别是classloader以及被代理类的接口,这个方法会生成一个Class对象出来。即代理类。

  • 然后使用反射获得构造器: final Constructor<?> cons = cl.getConstructor(constructorParams);

  • 返回实例:return cons.newInstance(new Object[]{h});

生成的代理类:

  • 会去实现代理的接口,由于 java 不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。

  • 提供了一个使用InvocationHandler作为参数的构造方法 生成静态代码块来初始化接口中方法的Method对象,以及Object类的equals、hashCode、toString方法。

  • 重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法。

  • 代理类实现代理接口的目标方法中,只是简单的调用了InvocationHandler的invoke方法,我们可以在invoke方法中进行一些特殊操作,甚至不调用实现的方法,直接返回。

经过我们的反编译,该类大致如下:

import com.dr.designPattern.proxy.dynamicProxy.UserManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
 
public final class $Proxy extends Proxy implements UserManager {
 
   private static Method m1;
   private static Method m3;
   private static Method m2;
   private static Method m4;
   private static Method m0;
 
 
   public $Proxy(InvocationHandler var1) throws  {
      super(var1);
   }
 
   public final boolean equals(Object var1) throws  {
      try {
         return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
      } catch (RuntimeException | Error var3) {
         throw var3;
      } catch (Throwable var4) {
         throw new UndeclaredThrowableException(var4);
      }
   }
 
   public final void addUser(String var1, String var2) throws  {
      try {
         super.h.invoke(this, m3, new Object[]{var1, var2});
      } catch (RuntimeException | Error var4) {
         throw var4;
      } catch (Throwable var5) {
         throw new UndeclaredThrowableException(var5);
      }
   }
 
   public final String toString() throws  {
      try {
         return (String)super.h.invoke(this, m2, (Object[])null);
      } catch (RuntimeException | Error var2) {
         throw var2;
      } catch (Throwable var3) {
         throw new UndeclaredThrowableException(var3);
      }
   }
 
   public final void delUser(String var1) throws  {
      try {
         super.h.invoke(this, m4, new Object[]{var1});
      } catch (RuntimeException | Error var3) {
         throw var3;
      } catch (Throwable var4) {
         throw new UndeclaredThrowableException(var4);
      }
   }
 
   public final int hashCode() throws  {
      try {
         return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
      } catch (RuntimeException | Error var2) {
         throw var2;
      } catch (Throwable var3) {
         throw new UndeclaredThrowableException(var3);
      }
   }
 
   static {
      try {
         m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
         m3 = Class.forName("com.dr.designPattern.proxy.dynamicProxy.UserManager").getMethod("addUser", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")});
         m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
         m4 = Class.forName("com.dr.designPattern.proxy.dynamicProxy.UserManager").getMethod("delUser", new Class[]{Class.forName("java.lang.String")});
         m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      } catch (NoSuchMethodException var2) {
         throw new NoSuchMethodError(var2.getMessage());
      } catch (ClassNotFoundException var3) {
         throw new NoClassDefFoundError(var3.getMessage());
      }
   }
}

复制代码

第二个方法是我们自己定义的JDKProxy类实现的InvocationHandler接口的invoke方法。可以看到上面生成的代理类就是通过调用invoke这个方法来进行目标方法的调用。

在动态代理中InvocationHandler是核心,每个代理实例都具有一个关联的调用处理程序(InvocationHandler)。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序(InvocationHandler)的 invoke 方法。所以对代理方法的调用都是通InvocationHadler的invoke来实现中,而invoke方法根据传入的代理对象,方法和参数来决定调用代理的哪个方法。invoke方法定义如下:

public Object invoke(Object proxy, Method method, Object[] args)
复制代码

这个时候我们再回头看生成的代理类$Proxy,并以其中实现的方法addUser方法为例来梳理一遍调用流程:

首先代理类$Proxy继承了Proxy类并实现了被代理类UserManager

->$Proxy类实现了被代理类中的addUser和delUser方法。

->addUser方法的实现为:调用父类Proxy类的Invoke方法->因此,调用addUser方法就变成了调用被代理类的addUser方法->进而变成了调用Proxy类的invoke方法-> 而invoke方法是我们自己实现的->进而变成了调用有我们自己实现的逻辑(增强)的invoke方法

->invoke方法不仅要实现增强,还需调用被代理方法,因此这里传入了一个方法叫m3

->m3定义即为被代理方法addUser

->因此,在最终的invoke方法里面既能实现增强,又能调用被代理方法,并且,由于被代理方法是通过反射加载调用的,因此它可以是在运行时确定的,即动态的。

通过反编译出来的代理类,我们甚至可以直接new一个它的实例出来调用它的方法,如下:

$Proxy p = new $Proxy();
p.addUser("ray","test");

复制代码

事实上这并没有什么神奇的地方,代理类和我们自己定义的普通的类并没有任何本质上的区别。

一些问题

问题1:为什么JDK动态代理只能代理实现了接口的类?

因为JDK动态代理生成的代理类需要去继承Proxy类,而java是单继承的,因此它只能去实现被代理类的接口(实现的接口)

问题2:为什么JDK动态代理生成的代理类要去继承Proxy类?

不仔细看的话,这里的代理类会给人一种继承Proxy类没有用的地方,很有迷惑性,我也是将生成的代理类代码亲自拿来试了一下才发现它的用处。注意,在代理类$Proxy中有多处方法调用,是通过使用了super关键字去拿父类中定义的InvocationHandler实例然后完成调用的。而Super关键字正是在调用父类Prioxy类的方法。

在代理类 Proxy中通过构造方法传入进来,而我们应该还记得,在Proxy的newProxyInstance方法中的第三个参数,正是InvocationHandler的一个实例。

因此这里调用方法时,自然就是使用这个实例去调用invoke方法,同时传入对应的方法参数。这下,所有的东西都串联起来了,问题三的答案事实上也已经有了。

问题3:为什么我们去调用代理类的目标方法,它会去调用invoke方法?

因为JDK生成真正的代理类中,是继承了Proxy类并实现了我们定义的被代理接口,而这个代理类在实现我们定义的接口方法时,是通过反射调用了InvocationHandlerImpl的invoke方法,然后在这个invoke方法当中,我们实现了增强的逻辑以及对被代理方法的真正调用。

问题4:JDK动态代理在哪些地方用到了反射?

经过上面的分析,至少有以下地方用到了反射:

    1. 在调用Proxy.newInstance方法时用反射获取接口传入方法
  • 2.在生成代理类$Proxy时,通过反射获取构造方法生成代理类

  • 3.在invoke方法当中调用被代理方法时,通过反射进行调用

因此,反射对于jdk动态代理的实现至关重要,而代理更多的是起到一个设计思想,代码架构上的作用。

CGLib动态代理是如何实现的

核心:代理模式加继承

具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。

方法的调用并不是通过反射来完成的,而是直接对方法进行调用,因为是继承,这一点与jdk动态代理是不一样的

另外JDK代理只能对接口进行代理,Cglib则是对实现类进行代理。重点代码如下:

public class CGLibProxy implements MethodInterceptor {
 
    //根据目标对象生成一个子类作为他的代理类
    public Object createProxyObject(Object obj) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());//设置父类为被代理类
        enhancer.setCallback(this);
        Object proxyObj = enhancer.create();
        return proxyObj;// 返回代理对象
    }
 
    public Object intercept(Object proxy, Method method, Object[] args,
            MethodProxy methodProxy) throws Throwable {
        
        System.out.println("before");
      
        Object obj = methodProxy.invoke(proxy, args);
        
        System.out.println("after");
        return obj;
    }
 
    
}

复制代码

总的来说思想和JDK动态代理差别不大,都是根据被代理类生成一个代理类,MethodInterceptor类的角色和JDK动态代理中的InvocationHandler是一样的。但是Cglib的实现逻辑更为复杂。

但是在CGLib代理类中,因为是继承的缘故,因此会重写被代理类的方法,重写的逻辑就是调用我们这里实现的intercept方法,同时传入对应的参数。

第一个方法createProxyObject

这个方法会生成一个代理类,这个代理类当中重写了被代理类中的目标方法,同时,也提供了方法直接去调用被代理类的方法。

第二个方法intercept方法

在我们实现的intercept方法当中,当然也可以使用反射直接调用method方法,但是这里采取的实现是调用了MethodProxy类的方法去调用,这个方法没有用到反射机制。它的实现如下:

public Object invoke(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f1.invoke(fci.i1, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        } catch (IllegalArgumentException var5) {
            if (this.fastClassInfo.i1 < 0) {
                throw new IllegalArgumentException("Protected method: " + this.sig1);
            } else {
                throw var5;
            }
        }
    }

复制代码

可以看到,是用了一个叫做FastClass的类来实现的。

FastClass实现机制简介

FastClass其实就是对Class对象进行特殊处理,提出下标概念index,通过索引保存方法的引用信息,将原先的反射调用,转化为方法的直接调用,从而体现所谓的fast,下面通过一个例子了解一下FastClass的实现机制。

1、定义原类

class Test {
    public void f(){
        System.out.println("f method");
    }
 
    public void g(){
        System.out.println("g method");
    }
}
复制代码

2、定义Fast类

class FastTest {
    public int getIndex(String signature){
        switch(signature.hashCode()){
        case 3078479:
            return 1;
        case 3108270:
            return 2;
        }
        return -1;
    }
 
    public Object invoke(int index, Object o, Object[] ol){
        Test t = (Test) o;
        switch(index){
        case 1:
            t.f();
            return null;
        case 2:
            t.g();
            return null;
        }
        return null;
    }
}

复制代码

在FastTest中有两个方法,getIndex中对Test类的每个方法根据hash建立索引,invoke根据指定的索引,直接调用目标方法,避免了反射调用。所以当调用methodProxy.invoke方法时,实际上是调用代理类的方法,代理类则是直接调用了被代理类的原方法(因为是继承的缘故,可以直接调用)。

在CGLibProxy类中重写的在intercept方法当中就可以进行逻辑增强,事实上,从技术上讲这里也可以通过反射调用被代理的原方法。

note:不论在哪,身在何职,身居何位,都不要忘记心中那一团似火不灭的璀璨和光芒。


以上所述就是小编给大家介绍的《设计模式(九)代理模式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Parsing Techniques

Parsing Techniques

Dick Grune、Ceriel J.H. Jacobs / Springer / 2010-2-12 / USD 109.00

This second edition of Grune and Jacobs' brilliant work presents new developments and discoveries that have been made in the field. Parsing, also referred to as syntax analysis, has been and continues......一起来看看 《Parsing Techniques》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器