DesignMode_Proxy

proxyMode 在我看來比較有意思的一點是中介+反射。而爲何在設計模式中這個代理又尤爲重要的緣由,是java程序猿的春天的AOP核心思想就是這個。因此熟練掌握是必須的html

首先講一下代理模式是個什麼意思,爲何要用代理模式,代理模式有什麼好處?我主要採用問答的模式來解決問題,這也是我比較習慣的思考方式。java

 代理模式其實來源於生活的各處,就是中介,中介就是幫助委託對象來作一些過濾的事情,好比房屋中介,能夠根據委託人加的條件進行刪選房源提供,衣服店能夠幫助買衣服的人從工廠裏面各類各樣的衣服裏面刪選一些衣服給買衣服的人看,包括大家經常使用的某寶等app均可以稱爲中介,因此抽象到代碼上來就是,爲目標對象來擴展相應的繁雜事務的一種代理類。程序員

固然你可能會問,爲何不直接在目標對象接口實現功能上添加額外的功能代碼?web

其一  固然能夠,可是人所謂高級動物,就是由於人會根據麻煩的事情去尋求最簡單的處理辦法,若是你以爲你不須要房屋中介,也能夠在偌大的房源市場中尋找到一見傾心的房子,那徹底不須要中介啊,映射到代碼中也是,某一個接口的功能的實現能夠有不少種,可是上線以後客戶忽然提出要對某一接口的功能添加需求,然而苦逼的程序猿就須要爲這個需求的每一實現類都去添加增長的需求代碼,這工做量,光是想一想就已經失去了對生活的渴望了,因此,猿須要一個代理類來實現接口擴展需求的功能。spring

其二  在代碼規範中講究開閉原則,就是對擴展開放,對修改關閉,爲何這麼作的緣由是,一旦完成某個類後,或者整個軟件後,對於要求的新的功能,擴展是最好的辦法,由於擴展是以提升軟件的穩定性和靈活性爲前提的,而修改會致使代碼的重編譯,而擴展能夠編譯好後上線。設計模式

以上說了這麼多目的其實就是想讓大家接受代理這個東東,也至關於勸大家之後找房子的時候,先去看看房屋中介喲,可是被坑神馬的本人一律不負責。。。緩存

如今來具體看看代理類是如何優化咱們的操做的?畢竟大家這羣人類是要真眼看到好處纔會買帳的嘛app


代理的方式也分爲兩種,一種是靜態代理,一種是動態代理jvm

先來看看靜態代理 -- 有程序員手動建立的源代碼,也就是編譯的時候就已經將接口,被代理類,代理類等肯定下來,在程序運行以前,代理類的 Class文件已肯定ide

用一個web開發的dao層實現簡單模擬一下

接口:(鎖定的行爲)

 

package com.itamory.dao;

public interface IUserDao {
	public void save();
	public void get();
}

  

實現類(須要被代理的對象),沒被代理以前

package com.itamory.daoImp;

import org.junit.Test;

import com.itamory.dao.IUserDao;

public class UserDao implements IUserDao{

	@Override
	@Test
	public void save() {
		System.out.println("在目標對象中本身作得 : 一些繁瑣但又必須的鏈接db的操做");
		System.out.println("目標對象應要處理的事務 : 保存到了對象");
		System.out.println("在目標對象中本身作得 :一些繁瑣但又必須的關閉db鏈接的操做");
	}

	@Override
	public void get() {
		
		System.out.println("獲得了對象");
		
	}

}

  service層的測試

package com.itamory.serviceImp;

import org.junit.Test;

import com.itamory.dao.IUserDao;
import com.itamory.daoImp.ProxyForDao;
import com.itamory.daoImp.UserDao;

public class UserService{

	IUserDao dao;
	@Test
	public void save() {
		dao = new UserDao();
		dao.save();
	}

	
	public void get() {
		// TODO Auto-generated method stub
		
	}

}

  結果:console:

 

在目標對象中本身作得 : 一些繁瑣但又必須的鏈接db的操做
目標對象應要處理的事務 : 保存到了對象
在目標對象中本身作得 :一些繁瑣但又必須的關閉db鏈接的操做

 

 

 加入靜態代理類以後的UserDao有所改變

package com.itamory.daoImp;

import org.junit.Test;

import com.itamory.dao.IUserDao;

public class UserDaoWithStaticProxy implements IUserDao{

    @Override
    @Test
    public void save() {
        System.out.println("目標對象應要處理的事務 : 保存到了對象");
    }

    @Override
    public void get() {
        
        System.out.println("獲得了對象");
        
    }

}

 

這裏UserDao的實現類save的處理業務再也不繁瑣,也不會再改變,全部的其餘業務操做,所有交給代理類,以下

package com.itamory.daoImp;

import com.itamory.dao.IUserDao;

public class ProxyForDao implements IUserDao{
    
    //維護一個委託對象
    UserDaoWithStaticProxy udsp = new UserDaoWithStaticProxy();

    @Override
    public void save() {
        // 複雜的操做我來作
        System.out.println("proxy 作這些複雜的db鏈接操做");
        udsp.save();
        System.out.println("proxy 作這些複雜的db關閉操做");
    }

    @Override
    public void get() {
        
    }

}

 

可見,代理類是維護了一個委託類這個對象的,也就是目標對象,同時本身也繼承了這個接口(行爲),才能夠對這個接口進行擴展。

service中的測試

package com.itamory.serviceImp;

import org.junit.Test;

import com.itamory.dao.IUserDao;
import com.itamory.daoImp.ProxyForDao;
import com.itamory.daoImp.UserDao;

public class UserService{

    IUserDao dao;
    @Test
    public void save() {
        dao = new UserDao();
        dao.save();
    }

    @Test
    public void saveInProxy(){
        dao = new ProxyForDao();
        dao.save();
    }
    
    public void get() {
        // TODO Auto-generated method stub
        
    }

}

 

結果:Proxy作複雜其他的操做,目標對象處理主要事務。

proxy 作這些複雜的db鏈接操做
目標對象應要處理的事務 : 保存到了對象
proxy 作這些複雜的db關閉操做

 

 靜態代理就是對某一個目標對象進行其餘的操做,這些操做多是這個目標接口所要擴展的功能,但很明顯,靜態的代理類和被代理類接口一塊兒在程序運行以前就已經編譯完成,也就是擴展的功能也是寫死了的,這樣的缺點仍是比較多的,就是代理類是須要和委託類實現同樣的接口的,因此代理類會越寫越多,一旦接口又要增長功能,此時代理類又要增長或者維護

 

動態代理 -- 在運行的時候再根據程序員在代碼中的指令動態去生成的代理類,這裏相比於靜態代理寫好編譯完成的寫死的代理更加靈活一些

這裏有涉及到jvm中的編譯和運行的知識,簡單回顧一下,咱們在IDE中寫好的代碼,一旦ctr+s保存就會生成一個.Class文件放入硬盤中,這個是這個代碼的介於機器語言的中間文件,純二進制文件,這個過程就是編譯,編譯造成的二進制文件是由jvm解釋運行的,這中間會有類的加載,類的執行等等,可是jvm不會隨便的加載寫好的類文件,而是在你要用的時候纔會加載進來,且只加載一次,也就是將二進制的類文件加載到內存中這一個過程已是運行了,固然運行過程不止這一項操做,咱們如今想作的是在運行的過程當中再去建立代理類對象。

固然你會問,沒有預先生成的二進制文件,怎麼加載類,更況且生成類對象?

這就要說到,類的生成的最主要的兩種方式,第一種就是編譯生成的類,而後經過new顯式生成類對象,第二種就是經過反射,在運行的時候,經過類文件的加載到jvm內存中獲得構造器後建立對象。

動態代理主要的兩大依靠的jdk類庫是 Proxy類和InvocationHandler接口,經過這兩個能夠實現動態的生成代理類和代理類對象,如何實現以下:

首先仍是一樣的接口行爲IUserDao.java和目標對象UserDaoWithStaticProxy.java(上面有)

其次是InvocationHandler接口的實現

package com.itamory.DynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class UserDaoInvocationHandler<T> implements InvocationHandler{
    T target; // 維護的一個目標對象
    
    public UserDaoInvocationHandler(T target) { //在建立這個invocatonHandler的時候纔會傳入目標對象
        this.target = target;
    }
    
    /**
     * proxy:表明動態代理對象
     * method:表明正在執行的方法
     * args:表明調用目標方法時傳入的實參
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("proxy 執行"+method.getName()+"方法");
        System.out.println("handler這裏 執行額外的操做,如複雜的鏈接動做");
        Object res = method.invoke(target, args); // 實際是傳入的目標對象執行方法
        System.out.println("handler這裏 執行額外的操做,如複雜的鏈接關閉動做");
        return res;
    }

}

 

從這裏能夠看出,Invocation的實現類維護了target這個目標對象,具體的實現後面再說,先看動態生成的代理類的地方仍是service的裏面

package com.itamory.serviceImp;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.junit.Test;

import com.itamory.DynamicProxy.UserDaoInvocationHandler;
import com.itamory.dao.IUserDao;
import com.itamory.daoImp.ProxyForDao;
import com.itamory.daoImp.UserDao;
import com.itamory.daoImp.UserDaoWithStaticProxy;

public class UserService{

    IUserDao dao;
    @Test
    public void save() {
        dao = new UserDao();
        dao.save();
    }

    @Test
    public void saveInProxy(){
        dao = new ProxyForDao();
        dao.save();
    }
    
    @Test // 展現如何動態建立代理類對象
    public void dynamicSaveInProxy(){
        // step 1 : 建立一個目標的對象,就是須要被代理的對象
        dao = new UserDaoWithStaticProxy();
        
        // step 2 : 建立一個和被代理對象相關的invocationHandler--同時傳入了目標對象
        InvocationHandler handler = new UserDaoInvocationHandler<IUserDao>(dao);
        
        // step 3: 建立一個代理類對象,傳入類加載器和類的相關信息(代理對象的每一個執行方法都會替換執行Invocation中的invoke方法)
        IUserDao dynamicProxy = (IUserDao) Proxy.newProxyInstance(IUserDao.class.getClassLoader(), new Class<?>[]{IUserDao.class}, handler);
        System.out.println(dynamicProxy.getClass());
        
        // step 4 : 這裏指定要執行的方法
        dynamicProxy.save();
    }
    
    
    public void get() {
        // TODO Auto-generated method stub
        
    }

}

 

在step 2 中我傳進去了一個我須要實現的目標對象到handler中,讓他來維護,而後在step3中利用Proxy這個jdk類庫中的自帶類,調用的getProxyInstance方法來動態建立代理類,也便是說,這一代碼是建立動態類的關鍵,因此咱們須要看看這Proxy的源碼一探究竟

這裏我主要是貼上這個方法的源碼

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

        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, interfaces);
        }

        /*
         * 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;
            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());
        }
    }

 

紅色字體的源碼所作的事情就是,在運行時獲得interfaces的Class二進制的類文件,而後對其Class建立一個實現類的類文件,而後讓其成爲代理類的類文件,而後根據其類文件的內容,擁有的構造器建立代理類對象,完了,同時還須要注意的是,newProxyInstance方法的第一個參數是提供類加載器,用於運行時的須要加載到緩存的類文件,第二個參數,目標對象須要實現的接口們,這裏面擁有了需求擴展的全部功能方法,前兩個參數已經完成了一個代理類所須要的需求擴展的接口的屬性,而第三個參數,則是主要執行方法的擴展,在這裏面維護了目標對象,

先來看看結果:

class com.sun.proxy.$Proxy4
proxy 執行save方法
handler這裏 執行額外的操做,如複雜的鏈接動做
目標對象應要處理的事務 : 保存到了對象
handler這裏 執行額外的操做,如複雜的鏈接關閉動做

  

那爲何dynamicProxy一執行save就能夠獲得結果呢?

咱們能夠對其進行反編譯看看,由於上面說過生成了代理類的類文件的,因此加入如下代碼進行編譯

package com.itamory.serviceImp;

import java.io.FileOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.junit.Test;

import sun.misc.ProxyGenerator;

import com.itamory.DynamicProxy.UserDaoInvocationHandler;
import com.itamory.dao.IUserDao;
import com.itamory.daoImp.ProxyForDao;
import com.itamory.daoImp.UserDao;
import com.itamory.daoImp.UserDaoWithStaticProxy;

public class UserService{

    IUserDao dao;
    @Test
    public void save() {
        dao = new UserDao();
        dao.save();
    }

    @Test
    public void saveInProxy(){
        dao = new ProxyForDao();
        dao.save();
    }
    
    @Test // 展現如何動態建立代理類對象
    public void dynamicSaveInProxy(){
        // step 1 : 建立一個目標的對象,就是須要被代理的對象
        dao = new UserDaoWithStaticProxy();
        
        // step 2 : 建立一個和被代理對象相關的invocationHandler--同時傳入了目標對象
        InvocationHandler handler = new UserDaoInvocationHandler<IUserDao>(dao);
        
        // step 3: 建立一個代理類對象,傳入類加載器和類的相關信息(代理對象的每一個執行方法都會替換執行Invocation中的invoke方法)
        IUserDao dynamicProxy = (IUserDao) Proxy.newProxyInstance(IUserDao.class.getClassLoader(), new Class<?>[]{IUserDao.class}, handler);
        // 反編譯類文件代碼   ProxyGenerator 特殊類的使用須要設置
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy4", UserDaoWithStaticProxy.class.getInterfaces());
            String path = "F:/springdemo/ProxyDesign_AOP/src/com/itamory/serviceImp/dynamicProxy.class";
            try(FileOutputStream fos = new FileOutputStream(path)) {
                fos.write(classFile);
                fos.flush();
                System.out.println("代理類class文件寫入成功");
            } catch (Exception e) {
                e.printStackTrace();
               System.out.println("寫文件錯誤"); }
        System.out.println(dynamicProxy.getClass());
        
        // step 4 : 這裏指定要執行的方法
        dynamicProxy.save();
    }
    @Test
    public void dynamicSaveInProxy_(){
        dao = new UserDaoWithStaticProxy();
        IUserDao proxy = (IUserDao) Proxy.newProxyInstance(IUserDao.class.getClassLoader(),  
                new Class<?>[]{IUserDao.class}, 
                new InvocationHandler(){

                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    System.out.println("proxy 實現了"+method.getName()+" method");
                    System.out.println("before() 可動態擴展的地方");
                    Object res = method.invoke(dao, args);
                    System.out.println("after() 可動態擴展的地方");
                    return res;
                }});
        proxy.save();
    }
    
    public void get() {
        
        
    }

}

 

而後會生成一個dynamicProxy.class文件,在cmd中輸入jad -sjava dynamicProxy.class能夠獲得反編譯的.java文件。打開後以下:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 

import com.itamory.dao.IUserDao;
import java.lang.reflect.*;

public final class $Proxy4 extends Proxy
    implements IUserDao
{

// 這裏將傳入的invocationHandler進行維護
public $Proxy4(InvocationHandler invocationhandler) { super(invocationhandler); } public final void save() { try { super.h.invoke(this, m4, null); //看這裏,是將傳入的method教過handler去執行的 return; } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void get() { try { super.h.invoke(this, m3, null); return; } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)super.h.invoke(this, m2, null); } catch(Error _ex) { } catch(Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
// 這裏是method的所有信息
private static Method m4; private static Method m1; private static Method m0; private static Method m3; private static Method m2; static { try { m4 = Class.forName("com.itamory.dao.IUserDao").getMethod("save", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m3 = Class.forName("com.itamory.dao.IUserDao").getMethod("get", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); } catch(NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); } catch(ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } } }

 

因此這裏的invoke方法就是目標對象的具體method的執行方法,固然method是由代理類來觸發的,也就是dynamicProxy裏面維護了一個InvocationHandler, 而InvocationHandler裏面又維護了目標對象,因此最終就是在運行時,有代理類對象的觸發的方法來交給其維護的InvacationHandler實現類去invoke,而InvacationHandler實現類對象的invoke又是由其維護對象來執行相應的method的,動態代理就是這麼回事兒

同時有一個小的知識點,能夠看到$Proxy4這個代理類是繼承Proxy類的,實現IUserDao的接口的,因此能夠得出兩個結論,第一動態代理類之只能對接口進行代理,沒法對類進行代理,這是由java的單繼承特性決定的,第二個,若是直接打印這個代理類對象,會觸發toString的方法,獲得的就是接口的信息,若是打印的代理類對象的.getClass()方法,獲得的就是Proxy類信息.

最後再來稍微優化一下動態代理的代碼

@Test
    public void dynamicSaveInProxy_(){
        dao = new UserDaoWithStaticProxy();
        IUserDao proxy = (IUserDao) Proxy.newProxyInstance(IUserDao.class.getClassLoader(),  
                new Class<?>[]{IUserDao.class}, 
                new InvocationHandler(){

                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    System.out.println("proxy 實現了"+method.getName()+" method");
                    System.out.println("before() 可動態擴展的地方");
                    Object res = method.invoke(dao, args);
                    System.out.println("after() 可動態擴展的地方");
                    return res;
                }});
        proxy.save();
    }
    

 

 Over!

本站公眾號
   歡迎關注本站公眾號,獲取更多信息