大話--代理模式

10000_Hours發表於2020-07-27

代理模式

定義

代理模式就是給某一個物件提供一個代理物件,並由代理物件控制對原物件的引用。通俗講代理模式就是我們生活中常見的中介。

使用場景

遠端(Remote)代理:為一個位於不同的地址空間的物件提供一個本地的代理物件,這個不同的地址空間可以是在同一臺主機中,也可是在另一臺主機中。
虛擬(Virtual)代理:如果需要建立一個資源消耗較大的物件,先建立一個消耗相對較小的物件來表示,真實物件只在需要時才會被真正建立。
Copy-on-Write代理:它是虛擬代理的一種,把複製(克隆)操作延遲到只有在客戶端真正需要時才執行。一般來說,物件的深克隆是一個開銷較大的操作,Copy-on-Write代理可以讓這個操作延遲,只有物件被用到的時候才被克隆。
保護(Protect or Access)代理:控制對一個物件的訪問,可以給不同的使用者提供不同級別的使用許可權。
緩衝(Cache)代理:為某一個目標操作的結果提供臨時的儲存空間,以便多個客戶端可以共享這些結果。
防火牆(Firewall)代理:保護目標不讓惡意使用者接近。
同步化(Synchronization)代理:使幾個使用者能夠同時使用一個物件而沒有衝突。
智慧引用(Smart Reference)代理:當一個物件被引用時,提供一些額外的操作,如將此物件被呼叫的次數記錄下來等。

優點

在目標物件實現的基礎上,可以增強額外的功能操作,即擴充套件目標物件的功能。
代理模式主要起增強方法和許可權攔截的作用。

缺點

1.增加了代理物件可能導致請求的處理速度變慢。
2.實現代理模式需要額外工作,有些代理模式的實現非常複雜。

組成

代理模式通常分成靜態代理和動態代理。動態代理又分為JDK代理和Cglib代理

tips

1、和介面卡模式的區別:介面卡模式主要改變所考慮物件的介面,而代理模式不能改變所代理類的介面。
2、和裝飾器模式的區別:裝飾器模式為了增強功能,而代理模式是為了加以控制。
靜態代理需要自己手動編寫代理類和目標方法。
動態代理就不需要自己手動實現代理類和目標方法,但動態代理的目標類要必須實現介面!
Cglib代理的目標類可以實現介面也可以不實現,因為可以使用繼承子類的方式代理。

關鍵程式碼

實現代理類與被代理類的組合

類圖

普通代理模式

靜態代理

動態代理

Cglib代理

具體實現

靜態代理

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

靜態代理的優缺點:
優點:在不修改目標物件功能的前提下,能通過代理物件對目標功能擴充套件。
確定:需要很多代理類;一旦介面增加,目標物件和代理物件都要維護。

1.定義一個介面ITeacherDao
2.目標物件TeacherDao實現介面ITeacherDAO
3.使用靜態代理方式,需要代理物件TeacherDaoProxy也實現ITeacherDao
4.呼叫時,通過呼叫代理物件的方法來呼叫目標物件
5.代理物件和目標物件要實現相同的介面,並通過呼叫相同的方法來呼叫目標物件的方法
package Proxy.staticProxy;

public interface ITeacherDao {
    void teach();
}

package Proxy.staticProxy;

/**
 * @title: TeacherDao
 * @Author yzhengy
 * @Date: 2020/7/27 16:23
 * @Question:  目標物件
 */
public class TeacherDao implements ITeacherDao {

    @Override
    public void teach() {
        System.out.println("老師正在授課......");
    }
}

package Proxy.staticProxy;

/**
 * @title: TeacherDaoProxy
 * @Author yzhengy
 * @Date: 2020/7/27 16:24
 * @Question: 代理物件
 */
public class TeacherDaoProxy implements ITeacherDao {

    private ITeacherDao target;//目標物件,通過介面來聚合

    //構造器
    public TeacherDaoProxy(ITeacherDao target) {
        this.target = target;
    }

    @Override
    public void teach() {
        System.out.println("開始代理......操作過程進行中");
        target.teach();
        System.out.println("提交操作");
    }
}

package Proxy.staticProxy;

/**
 * @title: Client
 * @Author yzhengy
 * @Date: 2020/7/27 16:27
 * @Question: 測試類
 */
public class Client {

    public static void main(String[] args) {
        //建立目標物件
        TeacherDao teacherDao = new TeacherDao();

        //建立代理物件,同時將目標物件傳遞給代理物件
        TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);

        //通過代理物件,呼叫目標物件的方法(執行的是代理物件的方法,代理物件再去呼叫目標物件的方法)
        teacherDaoProxy.teach();

    }

}

動態代理

1.代理物件不需要實現介面,但目標物件需要實現介面。
2.代理物件的生成是利用JDK的API,動態地在記憶體中構建代理物件。
3.代理類所在的包:java.lang.reflect.Proxy
4.JDK實現代理只需要使用newProxyInstance方法。static Object newProxyInstance(Classloader loader,Class<?>[]interfaces,InvocationHandler h)

代理類是由Proxy這個類通過newProxyInstance方法動態生成的,生成物件後使用“例項呼叫方法”的方式進行方法呼叫,那麼代理類的被代理類的關係只有在執行這行程式碼的時候才會生成,因此成為動態代理。

package Proxy.dynamicProxy;

public interface ITeacherDao {

    void teacher();
    void sayHello(String name);

}

package Proxy.dynamicProxy;

/**
 * @title: TeacherDao
 * @Author yzhengy
 * @Date: 2020/7/27 17:04
 * @Question: 目標物件
 */
public class TeacherDao implements ITeacherDao {

    @Override
    public void teacher() {
        System.out.println("老師授課中......");
    }

    @Override
    public void sayHello(String name) {
        System.out.println("hello" + name);
    }
}

package Proxy.dynamicProxy;

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

/**
 * @title: ProxyFactory
 * @Author yzhengy
 * @Date: 2020/7/27 17:05
 * @Question: 代理物件工廠
 */
public class ProxyFactory {

    private Object target;//維護一個目標物件

    public ProxyFactory(Object target){//構造器,對target進行初始化
        this.target = target;
    }

    //給目標物件生成一個相應的代理物件
    public Object getProxyInstance(){
		/*說明
		 *  public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            1. ClassLoader loader : 指定當前目標物件使用的類載入器, 獲取載入器的方法固定
            2. Class<?>[] interfaces: 目標物件實現的介面型別,使用泛型方法確認型別
            3. InvocationHandler h : 事情處理,執行目標物件的方法時,會觸發事情處理器方法, 會把當前執行的目標物件方法作為引數傳入
		 */
		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("JDK動態代理開始......");
                Object returnVal = method.invoke(target,args);//利用反射機制呼叫目標物件的方法
                System.out.println("JDK動態代理提交......");
                return returnVal;
            }
        });

    }
}

package Proxy.dynamicProxy;

/**
 * @title: Client
 * @Author yzhengy
 * @Date: 2020/7/27 17:13
 * @Question: 測試類
 */
public class Client {

    public static void main(String[] args) {
        //建立目標物件
        ITeacherDao target = new TeacherDao();

        //給了目標物件,建立代理物件,需要轉成ITeacherDao
        ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();

        //proxyInstance=class com.sun.proxy.$Proxy0 記憶體中動態生成了代理物件
        System.out.println("proxyInstance=" + proxyInstance.getClass());

        //通過代理物件,呼叫目標物件的方法
        proxyInstance.teacher();
        proxyInstance.sayHello("tina");
    }
}

Cglib代理

CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。但因為採用的是繼承,所以不能對final修飾的類進行代理。
1.引用Cglib的jar包
2.在記憶體中動態地建立子類,注意代理的類不能為final,否則會出現java.lang.IllegalArgumentException錯誤。
3.目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法。

package Proxy.cglibProxy;

/**
 * @title: TeacherDao
 * @Author yzhengy
 * @Date: 2020/7/27 18:10
 * @Question: 目標物件
 */
public class TeacherDao {

    public String teach(){
        System.out.println("老師授課中  , 我是cglib代理,不需要實現介面");
        return "hello";
    }
}

package Proxy.cglibProxy;

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

import java.lang.reflect.Method;

/**
 * @title: ProxyFactory
 * @Author yzhengy
 * @Date: 2020/7/27 17:38
 * @Question: 代理物件
 */
public class ProxyFactory implements MethodInterceptor {

    //維護一個目標物件
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    //返回一個代理物件,是target目標物件的代理物件
    public Object getProxyInstance(){
        //1.建立一個工具類
        Enhancer enhancer = new Enhancer();
        //2.設定父類
        enhancer.setSuperclass(target.getClass());
        //3.設定回撥函式
        enhancer.setCallback(this);
        //4.建立子類物件,即代理物件
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib代理模式......開始");
        Object returnVal = method.invoke(target,args);
        System.out.println("cglib代理模式......結束");
        return returnVal;
    }

}

package Proxy.cglibProxy;

/**
 * @title: Client
 * @Author yzhengy
 * @Date: 2020/7/27 18:21
 * @Question: 測試類
 */
public class Client {

    public static void main(String[] args) {
        //.建立目標物件
        TeacherDao target = new TeacherDao();

        //.獲取代理物件,並將目標物件傳遞給代理物件
        TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();

        //.執行代理物件的方法,觸發intercept方法,實現對目標物件方法的呼叫
        String res = proxyInstance.teach();
        System.out.println("res=" + res);
    }

}

使用代理模式的原始碼

Spring AOP 程式設計的實現原理就是動態代理。使用的是JDK代理和cglib代理,比如Spring的事務使用的是AOP技術,當目標類沒有實現介面時候,會使用cglib代理,實現了介面預設使用JDK代理。

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

 @Override
 public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
     Class<?> targetClass = config.getTargetClass();
     if (targetClass == null) {
       throw new AopConfigException("TargetSource cannot determine target class: " +
           "Either an interface or a target is required for proxy creation.");
     }
       // 判斷目標類是否是介面或者目標類是否Proxy型別,若是則使用JDK動態代理
     if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
       return new JdkDynamicAopProxy(config);
     }
       // 配置了使用CGLIB進行動態代理或者目標類沒有介面,那麼使用CGLIB的方式建立代理物件
     return new ObjenesisCglibAopProxy(config);
   }
   else {
       // 上面的三個方法沒有一個為true,那使用JDK的提供的代理方式生成代理物件
     return new JdkDynamicAopProxy(config);
   }
 }
   //其他方法略……
}

相關文章