java設計模式-代理模式(Proxy)

定義

代理模式是對象的結構模式。代理模式給某一個對象提供代理對象,并由代理對象控制對源對象的引用。

代理模式的結構

所謂的代理,就是一個人或者一個機構代表另外一個人或者另外一個機構采取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象中間起到中介的作用。

代理模式類圖如下:

代理模式的類圖

代理模式中的角色有:

  • 抽象對象角色(AbstractObject):聲明了目標對象和代理對象的共同接口,這樣依賴在任何可以使用目標對象的地方都可以使用代理對象。
  • 目標對象角色(RealObject):定義了代理對象所代表的目標對象。
  • 代理對象角色(ProxyObject):代理對象內部含有目標對象的引用,從而可以在任何時候操作目標對象;代理對象提供一個與目標對象相同的接口,以便可以在任何時候替代目標對象。代理對象通常在客戶端調用傳遞給目標對象之前或者之后,執行某個操作,而不是單純的將調用傳遞給目標對象。

示例代碼

抽象對象角色

public abstract class AbstractObject {
    /**
     * 定義操作
     */
    public abstract void operation();
}

目標對象角色

public class RealObject extends AbstractObject {
    public void operation() {
        System.out.println("Do Something!");
    }
}

代理對象角色

public class ProxyObject extends AbstractObject {
    RealObject realObject = new RealObject();
    public void operation() {
        //在調用目標對象之前,完成一些操作
        System.out.println("Before Do Something");
        realObject.operation();
        //在調用目標對象之后,完成一些操作
        System.out.println("After Do Something");
    }
}

客戶端

public class Client {
    public static void main(String[] args) {
        AbstractObject abstractObject = new ProxyObject();
        abstractObject.operation();
    }
}

從上面的例子可以看出代理對象將客戶端的調用委派給目標對象,在調用目標對象的方法之前跟之后都可以執行特定的操作。

這就是靜態代理的實現,靜態代理中,一個目標對象對應一個代理對象,代理類在編譯時期就已經確定了。

靜態代理方式總結

  1. 可以做到在不修改目標對象的前提下,拓展目標對象的功能。
  2. 缺點是:因為代理對象需要同目標對象實現同樣的接口,所以會有很多的代理類,造成類過多;并且,一旦接口中增加方法,目標對象同代理對象都需要進行維護。

解決這個缺點的方式就是使用動態代理。

動態代理

動態代理主要有如下特點:

  • 代理對象不需要實現目標對象的接口。
  • 代理對象的生成,使用的是Java的API,動態的在內存中構件代理對象(這需要我們指定創建代理對象/目標對象的接口的類型)。
  • 動態代理也叫做JDK代理、接口代理。

JDK中生成代理對象的API

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

JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個參數,源碼中的方法定義為:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
{
    //......
}

注意,該方法在Proxy類中是靜態方法,且接收的三個參數依次為:

  • ClassLoader loader:指定當前目標對象使用類加載器,獲取加載器的方法是固定的。
  • Class<?>[] interfaces:目標對象實現的接口類型,使用泛型方式確認類型。
  • InvocationHandler h:事件處理。執行目標對象的方法時,會觸發事件處理器的方法,會把當前執行目標對象的方法作為參數傳入。

示例代碼

目標對象接口

public interface IUserDao {
    void save();
}

目標對象類

public class UserDao implements IUserDao {
    @Override
    public void save() {
        System.out.println("---------已經保存數據----------");
    }
}

動態代理對象

/**
 * 創建動態代理對象
 * 動態代理對象不需要實現接口,但是需要指定接口類型
 */
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("Begin Transaction");
                        //執行目標對象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("Commit Transaction");
                        return returnValue;
                    }
                });
    }
}

測試類

public class TestProxyFactory {
    public static void main(String[] args) {
        //目標對象
        IUserDao userDao = new UserDao();
        //原始類型 class com.sschen.proxy.UserDao
        System.out.println(userDao.getClass());
        
        //給定目標對象,動態創建代理對象
        IUserDao proxy = (IUserDao) new ProxyFactory(userDao).getProxyInstance();
        //代理對象類型 class com.sun.proxy.$Proxy0
        System.out.println(proxy.getClass());
        
        proxy.save();
    }
}

從上面的代碼可以看出,動態代理對象不需要實現目標對象接口,但是目標對象一定要實現接口,否則不能使用動態代理。

Cglib代理

上面的靜態代理和動態代理模式都需要目標對象是一個實現了接口的目標對象,但是有的時候,目標對象可能只是一個單獨的對象,并沒有實現任何的接口,這個時候,我們就可以使用目標對象子類的方式實現代理,這種代理方式就是:Cglib代理

定義

Cglib代理,也叫做子類代理,它是在內存中構件一個子類對象,從而實現對目標對象的功能拓展。

  • JDK的動態代理有個限制,就是使用動態代理的目標對象必須實現至少一個接口,由此,沒有實現接口但是想要使用代理的目標對象,就可以使用Cglib代理。
  • Cglib是強大的高性能的代碼生成包,它可以在運行期間拓展Java類與實現Java接口。它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)。
  • Cglib包的底層是通過使用一個小而快的字節碼處理框架ASM來轉換字節碼并生成新的類,不鼓勵直接只使用ASM,因為它要求你必須對JVM內部結構,包括class文件的格式和指令集都很熟悉。

Cglib子類代理的實現方法

  1. 需要引入Cglib的jar文件,在Maven中可以直接在POM.xml中添加下列引用即可。
        <!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>
  1. 引入包后,就可以在內存中動態構建子類。
  2. 代理的對象不能為final的,否則會報錯。
  3. 目標對象的方法如果為final/static修飾的,那么就不會被攔截,即不會執行目標對象額外的方法。

代碼示例

目標對象類

public class UserDao {
    public void save() {
        System.out.println("--------已經保存數據--------");
    }
}

Cglib代理工廠類

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 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(this.target.getClass());
        //3. 設置回調函數
        en.setCallback(this);
        //4. 創建子類,也就是代理對象
        return en.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Begin Transaction");

        //執行目標對象的方法
        Object returnValue = method.invoke(target, objects);

        System.out.println("End Transaction");

        return returnValue;
    }
}

測試類

public class TestProxyFactory {
    public static void main (String[] args) {
        //目標對象
        UserDao userDao = new UserDao();
        //生成代理對象
        UserDao userDaoProxy = (UserDao) new ProxyFactory(userDao).getProxyInstance();
        //調用對象方法
        userDaoProxy.save();
    }
}

在Spring的AOP編程中:

  • 如果加入容器的目標對象有實現接口,就使用JDK代理
  • 如果目標對象沒有實現接口,就使用Cglib代理。

參考

《JAVA與模式》之代理模式
Java的三種代理模式

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容