天天看點

java-proxy代理代理模式靜态代理動态代理glib代理

代理模式

代理(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();//執行的是代理的方法
    }
}           

靜态代理總結:

可以做到在不修改目标對象的功能前提下,對目标功能擴充.

缺點:因為代理對象需要與目标對象實作一樣的接口,是以會有很多代理類,類太多.同時,一旦接口增加方法,目标對象與代理對象都要維護.

動态代理

動态代理有以下特點:

  • 代理對象,不需要實作接口代理
  • 對象的生成,是利用JDK的API,動态的在記憶體中建構代理對象(需要我們指定建立代理對象/目标對象實作的接口的類型)
  • 動态代理也叫做:JDK代理,接口代理

JDK中生成代理對象的API

代理類所在包:java.lang.reflect.Proxy

JDK實作代理隻需要使用newProxyInstance方法,但是該方法需要接收三個參數,完整的寫法是:

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

總結:

代理對象不需要實作接口,但是目标對象一定要實作接口,否則不能用動态代理

glib代理

上面的靜态代理和動态代理模式都是要求目标對象是實作一個接口的目标對象,但是有時候目标對象隻是一個單獨的對象,并沒有實作任何的接口,這個時候就可以使用以目标對象子類的方式類實作代理,這種方法就叫做: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代理