1. 程式人生 > >代理模式與AOP

代理模式與AOP

代理模式基本

設計模式之禪在講解代理模式時用了一個遊戲代練的比喻。這個比喻非常的有代表性,對於理解代理模式很有幫助。它大致的思想是:大家都有過玩遊戲的經歷,也知道遊戲代練。那麼事實上游戲的代練在幫我的遊戲賬號打怪升級的時候,和代理模式裡面的代理類做的事情不正是一樣的事情嗎?

代理模式的定義如下:Provide a surrogate or placeholder for another object to control acess to it.(為其他物件提供一種代理以控制對這個物件的訪問)。其通用類圖如下:

  • Subject是一個抽象類也可以是一個介面,是一個最普通的業務型別定義無特殊要求
  • RealSubject是Subject的子類或實現類,它才是被代理角色。是業務邏輯的具體執行者。
  • Proxy叫做委託類,代理類。它負責對真實物件的應用。它在真實物件處理完畢前後做預處理和善後處理工作

上面是一個靜態代理的場景。代理一般實現的模式為JDK靜態代理:建立一個介面,然後建立被代理的類實現該介面並且實現該介面中的抽象方法。之後再建立一個代理類,同時使其也實現這個介面。在代理類中持有一個被代理物件的引用,而後在代理類方法中呼叫該物件的方法。

代理模式的優點

  • 職責清晰

      真實的角色就是實現實際的業務邏輯,不用關心其他非本職責的事務,通過後期的代理完成一件完成事務,附帶的結果就是程式設計簡潔清晰。

  • 高擴充套件性

      具體主題角色是隨時都會發生變化的,只要它實現了介面,甭管它如何變化,都逃不脫如來佛的手掌(介面),那我們的代理類完全就可以在不做任何修改的情況下使用。

  • 智慧化

      這在我們以上講解中還沒有體現出來,不過在我們以下的動態代理章節中你就會看到代理的智慧化,讀者有興趣也可以看看Struts是如何把表單元素對映到物件上的。

動態代理

動態代理是在實現階段不用關心代理誰,而在執行階段才指定代理那一個物件,相對的來說,自己寫代理類的方式就是靜態代理。

現在有一個非常流行的名稱叫做:面向橫切面程式設計,也就是AOP(Aspect Oriented Programming),其核心就是採用了動態代理機制。

 

JDK動態代理

核心:代理模式加反射。具體說代理類動態實現介面類,在動態生成的實現類裡面委託為hanlder去呼叫原始實現類方法

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

先來看第一個方法newProxy。

這個方法的引數即被代理的目標物件。這個方法的目標是根據被代理物件去建立一個代理類。關鍵方法為

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

此方法一共三個引數:

  1. ClassLoader 物件
  2. 一組interface介面
  3. 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) {
             ...
        }
    }
  1. getProxyClass0方法的兩個引數分別是classloader以及被代理類的介面,這個方法會生成一個Class物件出來。即代理類。
  2. 然後使用反射獲得構造器: final Constructor<?> cons = cl.getConstructor(constructorParams);
  3. 返回例項:return cons.newInstance(new Object[]{h});

生成的代理類:

  1. 會去實現代理的介面,由於java不能多繼承,這裡已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支援對實現類的代理,只支援介面的代理。
  2. 提供了一個使用InvocationHandler作為引數的構造方法
  3. 生成靜態程式碼塊來初始化介面中方法的Method物件,以及Object類的equals、hashCode、toString方法。
  4. 重寫了Object類的equals、hashCode、toString,它們都只是簡單的呼叫了InvocationHandler的invoke方法,即可以對其進行特殊的操作,也就是說JDK的動態代理還可以代理上述三個方法。
  5. 代理類實現代理介面的目標方法中,只是簡單的呼叫了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());
      }
   }
}

第二個方法是實現的InvocationHandler的invoke方法。

在動態代理中InvocationHandler是核心,每個代理例項都具有一個關聯的呼叫處理程式(InvocationHandler)。對代理例項呼叫方法時,將對方法呼叫進行編碼並將其指派到它的呼叫處理程式(InvocationHandler)的 invoke 方法。所以對代理方法的呼叫都是通InvocationHadler的invoke來實現中,而invoke方法根據傳入的代理物件,方法和引數來決定呼叫代理的哪個方法。invoke方法定義如下:

 public Object invoke(Object proxy, Method method, Object[] args)

在第一個方法生成的代理類當中會呼叫這個InvocationHandler中的invoke方法,然後把需要代理的方法作為第二個引數傳進去。我們在實現invoke方法時,事實上就是利用反射原理,去呼叫目標方法,同時前後就可以做一些增強。

注意,這裡的代理類是動態生成的,根據目標類和目標方法,因此自然而然,呼叫目標方法以及它的前後增強也就是動態的。

問題1:為什麼JDK動態代理只能代理實現了介面的類?

因為JDK動態代理生成的代理類需要去繼承Proxy類,而java是單繼承的,因此它只能去實現被代理類的介面(實現的介面)

問題2:為什麼JDK動態代理生成的代理類要去繼承Proxy類?

不仔細看的話,這裡的代理類會給人一種繼承Proxy類沒有用的地方,很有迷惑性,我也是將生成的代理類程式碼親自拿來試了一下才發現它的用處。注意,在代理類$Proxy中有多處方法呼叫都是使用的super關鍵字。而Super關鍵字不就正是在呼叫父類的方法嗎,super又不能調有介面中的方法,介面中的方法也沒有任何實現邏輯。

在代理類$Proxy中呼叫父類的方法都是使用了InvocationHandler類的例項,在代理類$Proxy中通過構造方法傳入進來,而我們應該還記得,在Proxy的newProxyInstance方法中的第三個引數,正是InvocationHandler的一個例項。

因此這裡呼叫方法時,自然就是使用這個例項去呼叫invoke方法,同時傳入對應的方法引數。這下,所有的東西都串聯起來了,問題三的答案事實上也已經有了。

這裡同時把oracle介紹代理的官方文件地址放在這裡,作為參考。

問題3:為什麼我們去呼叫代理類的目標方法,它會去呼叫invoke方法?

因為JDK生成真正的代理類中,是繼承了Proxy類並實現了我們定義的被代理介面,而這個代理類在實現我們定義的介面方法時,是通過反射呼叫了InvocationHandlerImpl的invoke方法,然後在這個invoke方法當中,我們實現了增強的邏輯以及對被代理方法的真正呼叫。

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方法當中就可以進行邏輯增強,事實上,從技術上講這裡也可以通過反射呼叫被代理的原方法。