大話設計模式之代理模式

    今天我們來總結一下一個常見的設計模式---代理設計模式。首先我們看一下代理模式的UML圖:

                                

    然後呢我們根據UML圖把代碼寫一下:

 public interface Subject{
        public void Request();
    }
    public class RealSubject implements Subject{

        @Override
        public void Request()
        {
            System.out.println("Operate request!");
        }
        
    }
    public class Proxy implements Subject{
        private RealSubject realSubject;
        public Proxy(RealSubject realSubject) {  
        super();  
        this.realSubject= realSubject;  
    }   
        @Override
        public void Request()
        {
            if(realSubject == null){
                realSubject = new RealSubject(); 
            }
            realSubject.Request();  
        }
        
    }
    public static void main(String[] args){ 
        Proxy proxy = new Proxy();
        proxy.Request();
        
    }

    代理(Proxy)是一種設計模式,提供了對目標對象另外的訪問方式;即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能。
    這裏使用到編程中的一個思想:不要隨意去修改別人已經寫好的代碼或者方法,如果需改修改,可以通過代理的方式來擴展該方法。

    舉個例子來說明代理的作用:假設我們想邀請一位明星,那麼並不是直接連接明星,而是聯繫明星的經紀人,來達到同樣的目的.明星就是一個目標對象,他只要負責活動中的節目,而其他瑣碎的事情就交給他的代理人(經紀人)來解決.這就是代理思想在現實中的一個例子。

    

    

    上面講的代理模式是靜態代理:即需要代理的類和代理類必須同時繼承或者實現同一接口,上述採用實現的方式這時代理類需聚合被代理類,這樣可以實現靈活多變。

動態代理

一般來說,對代理模式而言,一個主題類與一個代理類一一對應,這也是靜態代理模式的特點。

但是,也存在這樣的情況,有n各主題類,但是代理類中的“前處理、後處理”都是一樣的,僅調用主題不同。也就是說,多個主題類對應一個代理類,共享“前處理,後處理”功能,動態調用所需主題,大大減小了程序規模,這就是動態代理模式的特點。JDK動態代理

實現

[java] view plain copy
  1. //1. 抽象主題  
  2. public interface Moveable {  
  3.     void move()  throws Exception;  
  4. }  
  5. //2. 真實主題  
  6. public class Car implements Moveable {  
  7.     public void move() throws Exception {  
  8.         Thread.sleep(new Random().nextInt(1000));  
  9.         System.out.println("汽車行駛中…");  
  10.     }  
  11. }  
  12. //3.事務處理器  
  13. public class TimeHandler implements InvocationHandler {  
  14.     private Object target;  
  15.      
  16.     public TimeHandler(Object target) {  
  17.         super();  
  18.         this.target = target;  
  19.     }  
  20.    
  21.     /** 
  22.      * 參數: 
  23.      *proxy 被代理的對象 
  24.      *method 被代理對象的方法 
  25.      *args 方法的參數 
  26.      * 返回: 
  27.      *Object 方法返回值 
  28.      */  
  29.     public Object invoke(Object proxy, Method method, Object[] args)  
  30.             throws Throwable {  
  31.         long startTime = System.currentTimeMillis();  
  32.         System.out.println("汽車開始行駛…");  
  33.         method.invoke(target, args);  
  34.         long stopTime = System.currentTimeMillis();  
  35.         System.out.println("汽車結束行駛…汽車行駛時間:" + (stopTime - startTime) + "毫秒!");  
  36.         return null;  
  37.     }  
  38.    
  39. }  
  40. //測試類  
  41. public class Test {  
  42.     public static void main(String[] args) throws Exception{  
  43.         Car car = new Car();  
  44.         InvocationHandler h = new TimeHandler(car);  
  45.         Class<?> cls = car.getClass();  
  46.         /** 
  47.          *loader 類加載器 
  48.          *interfaces 實現接口 
  49.          *h InvocationHandler 
  50.          */  
  51.         Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);  
  52.         m.move();  
  53.     }  
  54. }  
代碼講解:

在測試代碼中,Proxy.newProxyInstance()方法需要3個參數:類加載器(要進行代理的類)、被代理類實現的接口,事務處理器。所以先實例化Car,實例化InvocationHandler的子類TimeHandler,將各參數傳入Proxy的靜態方法newProxyInstance()即可獲得Car的代理類,前面的靜態代理,代理類是我們編寫好的,而動態代理則不需要我們去編寫代理類,是在程序中動態生成的。

JDK動態代理步驟

1.       創建一個實現InvocationHandler接口的類,它必須實現invoke()方法

2.       創建被代理的類及接口

3.       調用Proxy的靜態方法,創建一個代理類

4.       通過代理調用方法

而爲什麼要進行如此操作,可以從Proxy和InvocationHandler的源碼中找打答案。對源碼不感興趣的可以將下面的源碼部分小節略過。

JDK動態代理原理與源碼

newProxyInstance()方法的源碼:

[java] view plain copy
  1.     public static Object newProxyInstance(ClassLoader loader,  
  2.                                          Class<?>[] interfaces,  
  3.                                          InvocationHandler h)  
  4.         throws IllegalArgumentException{  
  5.         if (h == null) {  
  6.             throw new NullPointerException();  
  7.         }  
  8.         final Class<?>[] intfs = interfaces.clone();  
  9.         final SecurityManager sm = System.getSecurityManager();  
  10.         if (sm != null) {  
  11.             checkProxyAccess(Reflection.getCallerClass(), loader, intfs);  
  12.         }  
  13.         /*查找或生成指定的代理類*/  
  14.         Class<?> cl = getProxyClass0(loader, intfs);  
  15.         /*用指定的調用處理程序調用它的構造函數.*/  
  16.         try {  
  17.         //獲得類的構造函數  
  18.             final Constructor<?> cons =cl.getConstructor(constructorParams);  
  19.             final InvocationHandler ih = h;  
  20.             if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {  
  21.               //當需要代理的類實現了一個非public的接口時,因爲這樣的接口需要特殊的權限,因此調用doPrivilege(native 修飾的方法)創建代理實例。  
  22.                 return AccessController.doPrivileged(newPrivilegedAction<Object>() {  
  23.                     public Object run() {  
  24.                         return newInstance(cons,ih);  
  25.                     }  
  26.                 });  
  27.             } else {  
  28.                 return newInstance(cons,ih);  
  29.             }  
  30.         } catch (NoSuchMethodException e) {  
  31.             throw new InternalError(e.toString());  
  32.         }  
  33. }  
可以看到,獲得代理類的代碼是

Class<?>cl = getProxyClass0(loader,intfs);

並由此獲得代理類的構造函數,生成代理類的實例返回給該方法的調用者。

繼續跟進getProxyClass0()方法:

[java] view plain copy
  1. /** 生成代理類。調用該方法前必須使用checkproxyaccess方法執行權限檢查。*/  
  2.     private static Class<?> getProxyClass0(ClassLoader loader,  
  3.                                           Class<?>... interfaces) {  
  4.     //檢查實現的接口數,65535這個數字好特殊,端口數好像也是這個,這個數字是由虛擬機所決定的,2^16-1個  
  5.     if (interfaces.length > 65535) {  
  6.             throw new IllegalArgumentException("interface limit exceeded");  
  7.         }  
  8.         // 如果代理類已經通過實現給定接口的類加載器創建了,則返回緩存中的該類的副本;否則將通過ProxyClassFactory創建代理類  
  9.         return proxyClassCache.get(loader, interfaces);  
  10. }  
還是沒有看到代理類是怎麼生成的,只知道代理類是從proxyClassCache中取得的,這個變量是與緩存相關的一個對象,查看該變量的聲明與初始化:

[java] view plain copy
  1. private static final WeakCache<ClassLoader, Class<?>[], Class<?>>  
  2.         proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());  
可以發現proxyClassCache是個用來緩存代理類的類變量,大家知道類變量的特點是與類一一對應,在一個虛擬機中類只有一個,對應着在一個虛擬機中類變量也只有一個,且在此處,在Proxy類被加載的時候就賦值了。在賦值操作的參數中有ProxyClassFactory()這麼一個構造函數,這個是動態代理中的關鍵:生成代理類的類文件字節碼。繼續跟進去,找到代理類的生成之處了:

[java] view plain copy
  1. /** 根據給定的類加載器和接口數組生成代理類的工廠類*/  
  2. private static final class ProxyClassFactory  
  3.     implements BiFunction<ClassLoader,Class<?>[], Class<?>>  
  4. {  
  5.     // 所有代理類名稱的前綴  
  6.     private static final String proxyClassNamePrefix = "$Proxy";  
  7.   
  8.     //用於生成唯一代理類名稱的下一個序號  
  9.     private static final AtomicLong nextUniqueNumber = new AtomicLong();  
  10.   
  11.     @Override  
  12.     public Class<?> apply(ClassLoader loader,Class<?>[] interfaces) {  
  13.   
  14.         Map<Class<?>, Boolean>interfaceSet = new IdentityHashMap<>(interfaces.length);  
  15.         for (Class<?> intf : interfaces) {  
  16.             /* 驗證類加載器將此接口的名稱解析爲實際對象的名稱。*/  
  17.             Class<?> interfaceClass =null;  
  18.             try {  
  19.                 interfaceClass = Class.forName(intf.getName(),false, loader);  
  20.             } catch (ClassNotFoundException e) {  
  21.             }  
  22.             if (interfaceClass != intf) {  
  23.                 throw new IllegalArgumentException(  
  24.                     intf + " is not visible from classloader");  
  25.             }  
  26.             /* 驗證類對象確實是一個接口。*/  
  27.             if (!interfaceClass.isInterface()) {  
  28.                 throw new IllegalArgumentException(  
  29.                    interfaceClass.getName() + " is not an interface");  
  30.             }  
  31.             /*確保接口唯一*/  
  32.             if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {  
  33.                 throw new IllegalArgumentException(  
  34.                     "repeated interface: " + interfaceClass.getName());  
  35.             }  
  36.         }  
  37.   
  38.         String proxyPkg = null;     // 代理類的包名  
  39.   
  40.         /*記錄非公開代理接口的包,以便將代理類定義在同一個包中。確認所有非公共代理接口都在同一個包中。*/  
  41.         for (Class<?> intf : interfaces) {  
  42.             int flags = intf.getModifiers();  
  43.             if (!Modifier.isPublic(flags)) {  
  44.                 String name =intf.getName();  
  45.                 int n = name.lastIndexOf('.');  
  46.                 String pkg = ((n == -1) ? "" : name.substring(0, n + 1));  
  47.                 if (proxyPkg == null) {  
  48.                     proxyPkg = pkg;  
  49.                 } else if (!pkg.equals(proxyPkg)) {  
  50.                     throw new IllegalArgumentException(  
  51.                         "non-public interfaces fromdifferent packages");  
  52.                 }  
  53.             }  
  54.         }  
  55.   
  56.         if (proxyPkg == null) {  
  57.             // 如果沒有非公開的代理接口,使用com.sun.proxy作爲包名  
  58.             proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";  
  59.         }  
  60.   
  61.         /* 生成代理類名的序號*/  
  62.         long num = nextUniqueNumber.getAndIncrement();  
  63.         //生成全類名  
  64.         String proxyName = proxyPkg + proxyClassNamePrefix + num;  
  65.   
  66.         /*生成代理類字節碼 */  
  67.         byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,interfaces);  
  68.         try {  
  69.             return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);  
  70.         } catch (ClassFormatError e) {  
  71.             throw new IllegalArgumentException(e.toString());  
  72.         }  
  73.     }  
ProxyClassFactory中,可以看到產生代理類的具體邏輯,大致上是,根據傳遞的被代理類及其實現的接口生成代理類的字節碼加載到緩存中,但是加載到緩存中只是一個.java文件也不能用,所以底層還有編譯等操作。到這裏,可以大致的看清JDK中動態代理的面孔了,實現的步驟爲:

1.      創建代理類的源碼;

2.      對源碼進行編譯成字節碼;

3.      將字節碼加載到內存;

4.      實例化代理類對象並返回給調用者;

底層的代碼我們看不到,但是我們可以查看其生成的字節碼:

[java] view plain copy
  1. //獲得字節碼的測試方法  
  2.     byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy1", Car.class.getInterfaces());   
  3.         FileOutputStream out = null;   
  4.         try {   
  5.             out = new FileOutputStream(System.getProperty("user.dir") + "\\$Proxy1.class");   
  6.             out.write(classFile);   
  7.             out.flush();   
  8.         } catch (Exception e) {   
  9.             e.printStackTrace();   
  10.         } finally {   
  11.             try {   
  12.                 out.close();   
  13.             } catch (IOException e) {   
  14.                 e.printStackTrace();   
  15.             }   
  16.         }  
  17.    
  18. //生成的字節碼:  
  19. importcn.com.goser.proxy.imooc.staticproxy.Moveable;  
  20. importjava.lang.reflect.InvocationHandler;  
  21. importjava.lang.reflect.Method;  
  22. importjava.lang.reflect.Proxy;  
  23. import java.lang.reflect.UndeclaredThrowableException;  
  24.    
  25. public final class $Proxy1 extends Proxy  
  26.   implements Moveable  
  27. {  
  28.   private static Method m1;  
  29.   private static Method m3;  
  30.   private static Method m0;  
  31.   private static Method m2;  
  32.    
  33.   public $Proxy1(InvocationHandler paramInvocationHandler)  
  34.     throws  
  35.   {  
  36.     super(paramInvocationHandler);  
  37.   }  
  38.    
  39.   public final boolean equals(Object paramObject)  
  40.     throws  
  41.   {  
  42.     try  
  43.     {  
  44.       return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();  
  45.     }  
  46.     catch (RuntimeException localRuntimeException)  
  47.     {  
  48.       throw localRuntimeException;  
  49.     }  
  50.     catch (Throwable localThrowable)  
  51.     {  
  52.     }  
  53.     throw new UndeclaredThrowableException(localThrowable);  
  54.   }  
  55.    
  56.   public final void move()  
  57.     throws Exception  
  58.   {  
  59.     try  
  60.     {  
  61.       this.h.invoke(this, m3, null);  
  62.       return;  
  63.     }  
  64.     catch (Exception localException)  
  65.     {  
  66.       throw localException;  
  67.     }  
  68.     catch (Throwable localThrowable)  
  69.     {  
  70.     }  
  71.     throw new UndeclaredThrowableException(localThrowable);  
  72.   }  
  73.    
  74.   public final int hashCode()  
  75.     throws  
  76.   {  
  77.     try  
  78.     {  
  79.       return ((Integer)this.h.invoke(this, m0, null)).intValue();  
  80.     }  
  81.     catch (RuntimeException localRuntimeException)  
  82.     {  
  83.       throw localRuntimeException;  
  84.     }  
  85.     catch (Throwable localThrowable)  
  86.     {  
  87.     }  
  88.     throw new UndeclaredThrowableException(localThrowable);  
  89.   }  
  90.    
  91.   public final String toString()  
  92.     throws  
  93.   {  
  94.     try  
  95.     {  
  96.       return (String)this.h.invoke(this, m2, null);  
  97.     }  
  98.     catch (RuntimeException localRuntimeException)  
  99.     {  
  100.       throw localRuntimeException;  
  101.     }  
  102.     catch (Throwable localThrowable)  
  103.     {  
  104.     }  
  105.     throw new UndeclaredThrowableException(localThrowable);  
  106.   }  
  107.    
  108.   static  
  109.   {  
  110.     try  
  111.     {  
  112.       m1 = Class.forName("java.lang.Object").getMethod("equals"new Class[] { Class.forName("java.lang.Object") });  
  113.       m3 = Class.forName("cn.com.goser.proxy.imooc.staticproxy.Moveable").getMethod("move"new Class[0]);  
  114.       m0 = Class.forName("java.lang.Object").getMethod("hashCode"new Class[0]);  
  115.       m2 = Class.forName("java.lang.Object").getMethod("toString"new Class[0]);  
  116.       return;  
  117.     }  
  118.     catch (NoSuchMethodExceptionlocalNoSuchMethodException)  
  119.     {  
  120.       throw new NoSuchMethodError(localNoSuchMethodException.getMessage());  
  121.     }  
  122.     catch (ClassNotFoundExceptionlocalClassNotFoundException)  
  123.     {  
  124.     }  
  125.     throw new NoClassDefFoundError(localClassNotFoundException.getMessage());  
  126.   }  
  127. }  
生成的字節碼比較長,但是在字節碼中最關鍵的信息是代理類的聲明:

public final class $Proxy1 extends Proxy

可以看到生成的代理類是繼承了Proxy類的,這就是說明了爲什麼使用JDK動態代理不能實現繼承式動態代理,原因是Java不允許多繼承,而生成的代理類本身就已經繼承了Proxy類。

至此,JDK的動態代理的使用及底層原理分析完畢,揭下動態代理的神祕面紗,果然是枚美女。

至於最底層的native方法是怎麼動態生成代理類的字節碼我們也可以簡單的模擬一下,先分析下模擬的步驟:首先要生成一段代理類的源碼,然後將源碼編譯後生成代理類的實例返回給調用者。依據此步驟開始編寫我們的模擬代碼:

[java] view plain copy
  1. /** 
  2.  * JDK java.lang.reflect.Proxy的模擬 
  3.  * @author Goser    (mailto:[email protected]) 
  4.  * @Since 2016年9月7日 
  5.  */  
  6. public class Proxy {  
  7.     private static final String  RT = "\r\n";  
  8.     public static Object newProxyInstance() throws Exception{  
  9.         //聲明一段源碼  
  10.         String sourceCode =  
  11.         "packagecn.com.goser.proxy.jdk.simulate;"+ RT +  
  12.         "importcn.com.goser.proxy.imooc.staticproxy.Admin;" + RT +  
  13.         "importcn.com.goser.proxy.imooc.staticproxy.Manager;" + RT +  
  14.         "//以聚合方式實現的代理主題" + RT +  
  15.         "public class $Proxy0 implementsManager{" + RT +  
  16.         "   privateAdmin admin;" + RT +  
  17.         "   public$Proxy0(Admin admin) {" + RT +  
  18.         "       super();" + RT +  
  19.         "       this.admin= admin;" + RT +  
  20.         "   }" + RT +  
  21.         "   publicvoid doSomething() {" + RT +  
  22.         "       System.out.println(\"Log:admin操作開始\");" + RT +  
  23.         "       admin.doSomething();" + RT +  
  24.         "       System.out.println(\"Log:admin操作結束\");" + RT +  
  25.         "   }" + RT +  
  26.         "}";  
  27.         String filename = System.getProperty("user.dir") + "/src/main/java/cn/com/goser/proxy/jdk/simulate/$Proxy0.java";  
  28.         File file = new File(filename);  
  29.         //使用org.apache.commons.io.FileUtils.writeStringToFile()將源碼寫入磁盤  
  30.         //編寫到處,可以運行一下程序,可以在當前目錄中看到生成的.java文件  
  31.         FileUtils.writeStringToFile(file,sourceCode);  
  32.         //獲得當前系統中的編譯器  
  33.         JavaCompiler complier = ToolProvider.getSystemJavaCompiler();  
  34.         //獲得文件管理者  
  35.         StandardJavaFileManager fileMgr =complier.getStandardFileManager(nullnullnull);  
  36.         Iterable its =fileMgr.getJavaFileObjects(filename);  
  37.         //編譯任務  
  38.         CompilationTask task = complier.getTask(null, fileMgr, nullnullnull, its);  
  39.         //開始編譯,執行完可在當前目錄下看到.class文件  
  40.         task.call();  
  41.         fileMgr.close();  
  42.         //load到內存  
  43.         ClassLoader loader = ClassLoader.getSystemClassLoader();  
  44.         Class cls = loader.loadClass("cn.com.goser.proxy.jdk.simulate.$Proxy0");  
  45.         //生成代理類對象  
  46.         Constructor ct = cls.getConstructor(Admin.class);  
  47.         return ct.newInstance(new Admin());  
  48.     }  
  49. }  
  50. class test{  
  51.     public static void main(String[] args) throws Exception {  
  52.         Manager m = (Manager)Proxy.newProxyInstance();  
  53.         m.doSomething();  
  54.     }  
  55. }  
運行測試代碼,結果和手工編寫的結果一致,完成了JDK中動態代理的實現模擬。

cglib動態代理

    前面分析到,因爲Java只允許單繼承,而JDK生成的代理類本身就繼承了Proxy類,因此,使用JDK實現的動態代理不能完成繼承式的動態代理,但是我們可以使用cglib來實現繼承式的動態代理。

大名鼎鼎的Spring中就含有cglib動態代理,在此也以Spring中自帶的cglib完成動態代理的實現:

[java] view plain copy
  1. //1.具體主題  
  2. public class Train{  
  3.     public void move(){  
  4.         System.out.println("火車行駛中…");  
  5.     }  
  6. }  
  7. //2.生成代理  
  8. public class CGLibProxy implements MethodInterceptor {  
  9.     private Enhancer enhancer = new Enhancer();  
  10.     public Object getProxy(Class<?> clazz){  
  11.         enhancer.setSuperclass(clazz);  
  12.         enhancer.setCallback(this);  
  13.         return enhancer.create();  
  14.     }  
  15.     /** 
  16.      * 攔截所有目標類方法的調用 
  17.      * 參數: 
  18.      * obj目標實例對象 
  19.      *method 目標方法的反射對象 
  20.      * args方法的參數 
  21.      * proxy代理類的實例 
  22.      */  
  23.     public Object intercept(Object obj, Method method, Object[] args,  
  24.             MethodProxy proxy) throws Throwable {  
  25.         //代理類調用父類的方法  
  26.         System.out.println("日誌開始");  
  27.         proxy.invokeSuper(obj, args);  
  28.         System.out.println("日誌結束");  
  29.         return null;  
  30.     }  
  31. }  
  32. //3.測試  
  33. public class Test {  
  34.     public static void main(String[] args) {  
  35.         CGLibProxy proxy = new CGLibProxy();  
  36.         Train t = (Train) proxy.getProxy(Train.class);  
  37.         t.move();  
  38.     }  
  39. }  
小結

動態代理與靜態代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理。在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜態代理那樣對每一個方法或方法組合進行處理。Proxy 很美很強大,但是僅支持 interface 代理。Java 的單繼承機制註定了這些動態代理類們無法實現對 class 的動態代理。好在有cglib爲Proxy提供了彌補。class與interface的區別本來就模糊,在java8中更是增加了一些新特性,使得interface越來越接近class,當有一日,java突破了單繼承的限制,動態代理將會更加強大。


    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章