1. 程式人生 > >171129之Java的三種代理模式

171129之Java的三種代理模式

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

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

用圖表示如下:
這裡寫圖片描述

代理模式的關鍵點是:代理物件和目標物件。代理物件是對目標物件的擴充套件,並會呼叫目標物件。

靜態代理
靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現相同的介面或者是繼承相同父類.

下面舉個案例來解釋:
模擬儲存動作,定義一個儲存動作的介面:IUserDao.java,然後目標物件實現這個介面的方法UserDao.java,此時如果使用靜態代理方式,就需要在代理物件(UserDaoProxy.java)中也實現IUserDao介面.呼叫的時候通過呼叫代理物件的方法來呼叫目標物件.
需要注意的是,代理物件與目標物件要實現相同的介面,然後通過呼叫相同的方法來呼叫目標物件的方法

程式碼示例:
介面:IUserDao.java

/**
 * 介面
 */
public interface IUserDao { void save(); }

目標物件:UserDao.java

/**
 * 介面實現
 * 目標物件
 */
public class UserDao implements IUserDao {
    public void save() {
        System.out.println("----已經儲存資料!----");
    }
}

代理物件:UserDaoProxy.java

/**
 * 代理物件,靜態代理
 */
public class UserDaoProxy implements IUserDao
{
//接收儲存目標物件 private IUserDao target; public UserDaoProxy(IUserDao target){ this.target=target; } public void save() { System.out.println("開始事務..."); target.save();//執行目標物件的方法 System.out.println("提交事務..."); } }

注意:構造器是含參的,引數就是介面,方便呼叫

測試類:App.java

/**
 * 測試類
 */
public class App {
    public static void main(String[] args) {
        //目標物件
        UserDao target = new UserDao();
        //代理物件,把目標物件傳給代理物件,建立代理關係
        UserDaoProxy proxy = new UserDaoProxy(target);
        proxy.save();//執行的是代理的方法
    }
}

先建立目標物件,然後再建立代理物件,利用含參構造器把目標物件傳給代理物件,建立代理關係,從而呼叫代理的方法。

流程總結:建立介面,建立類實現介面,再建立另外個類實現介面,private 介面 介面名;建立含參構造器,引數就是介面名,最後在測試類中建立第一個類的物件,然後再建立第二個類物件,含參,最後再呼叫第二個類中的新方法。

靜態代理總結:
1.可以做到在不修改目標物件的功能前提下,對目標功能擴充套件.
2.缺點:
因為代理物件需要與目標物件實現一樣的介面,所以會有很多代理類,類太多.同時,一旦介面增加方法,目標物件與代理物件都要維護.

如何解決靜態代理中的缺點呢?答案是可以使用動態代理方式

動態代理
有以下特點:
1:代理物件,不需要實現介面
2:代理物件的生成,是利用了JDK的API,動態的在記憶體中構建代理物件(需要我們指定建立代理物件/目標物件實現的介面的型別)
3:動態代理也叫做:JDK代理,介面代理。

JDK生成代理物件的API
代理類所在包:java.lang.reflect.Proxy
JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收3個引數,完整的寫法是:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意該方法是在Proxy類中是靜態方法,且接收的三個引數依次為:
ClassLoader loader,:指定當前目標物件使用類載入器,獲取載入器的方法是固定的
Class<?>[] interfaces,:目標物件實現的介面的型別,使用泛型方式確認型別
InvocationHandler h:事件處理,執行目標物件的方法時,會觸發事件處理器的方法,會把當前執行目標物件的方法作為引數傳入

程式碼示例:

介面類IUserDao.java以及介面實現類,目標物件UserDao是一樣的,沒有做修改.在這個基礎上,增加一個代理工廠類(ProxyFactory.java),將代理類寫在這個地方,然後在測試類(需要使用到代理的程式碼)中先建立目標物件和代理物件的聯絡,然後代用代理物件的中同名方法

總結:動態代理就是在靜態代理的基礎上加個代理工廠類,來實現動態變化

代理工廠類:ProxyFactory.java

/**
 * 建立動態代理物件, 動態代理不需要實現介面,但是需要指定介面型別
 */
public class ProxyFactory{
    //維護一個目標物件
    private Object target;
    public ProxyFactory(Object target){
        this.target=target;
    }  上面的都是類似的

   //給目標物件生成代理物件
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("開始事務2");
                        //執行目標物件方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("提交事務2");
                        return returnValue;
                    }
                }
        );
    }
}

測試類:App.java

/**
 * 測試類
 */
public class App {
    public static void main(String[] args) {
        // 目標物件
        IUserDao target = new UserDao();
        // 【原始的型別 class cn.itcast.b_dynamic.UserDao】
        System.out.println(target.getClass());

        // 給目標物件,建立代理物件
        IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
        // class $Proxy0   記憶體中動態生成的代理物件
        System.out.println(proxy.getClass());

        // 執行方法   【代理物件】
        proxy.save();
    }
}

總結:
代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理

上面的靜態代理和動態代理模式都是要求目標物件是實現一個介面的目標物件,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以使用以目標物件子類的方式類實現代理,這種方法就叫做:Cglib代理

1.3.Cglib代理
上面的靜態代理和動態代理模式都是要求目標物件是實現一個介面的目標物件,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以使用以目標物件子類的方式類實現代理,這種方法就叫做:Cglib代理

Cglib代理,也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件.

JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用Cglib實現.
Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)
Cglib包的底層是通過使用一個小而塊的位元組碼處理框架ASM來轉換位元組碼並生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉.

Cglib子類代理實現方法:

1.需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
2.引入功能包後,就可以在記憶體中動態構建子類
3.代理的類不能為final,否則報錯
4.目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法.

程式碼示例:
目標物件類:UserDao.java

/**
 * 目標物件,沒有實現任何介面
 */
public class UserDao {
    public void save() {
        System.out.println("----已經儲存資料!----");
    }
}

Cglib代理工廠:ProxyFactory.java

/**
 * Cglib子類代理工廠
 * 對UserDao在記憶體中動態構建一個子類物件
 */
public class ProxyFactory implements MethodInterceptor{
    //維護目標物件
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    //給目標物件建立一個代理物件
    public Object getProxyInstance(){
        //1.工具類
        Enhancer en = new Enhancer();
        //2.設定父類
        en.setSuperclass(target.getClass());
        //3.設定回撥函式
        en.setCallback(this);
        //4.建立子類(代理物件)
        return en.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("開始事務...");
        //執行目標物件的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("提交事務...");
        return returnValue;
    }
}

測試類:

public class App {
    @Test
    public void test(){
        //目標物件
        UserDao target = new UserDao();
        //代理物件
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
       //執行代理物件的方法
        proxy.save();
    }
}

在Spring的AOP程式設計中:
如果加入容器的目標物件有實現介面,用JDK代理
如果目標物件沒有實現介面,用Cglib代理