大型Java進階專題(六)設計模式之代理模式

代理模式

前言

又開始我的專題了,又停滯了一段時間了,加油繼續吧。都知道 SpringAOP 是用代理模式實現,到底是怎麼實現的?我們來一探究竟,並且自己模擬手寫還原部分細節。

代理模式的應用

在生活中,我們經常見到這樣的場景,如:租房中介、售票黃牛、婚介、經紀人、快遞、 事務代理、非侵入式日誌監聽等,這些都是代理模式的實際體現。代理模式(Proxy Pattern)的定義也非常簡單,是指為其他對象提供一種代理,以控制對這個對象的訪問。 代理對象在客服端和目標對象之間起到中介作用,代理模式屬於結構型設計模式。使用 代理模式主要有兩個目的:一保護目標對象,二增強目標象。下面我們來看一下代理 模式的類結構圖:

Subject 是頂層介面,RealSubject 是真實對象(被代理對象),Proxy 是代理對象,代 理對象持有被代理對象的引用,客戶端調用代理對象方法,同時也調用被代理對象的方 法,但是在代理對象前後增加一些處理。在代碼中,我們想到代理,就會理解為是代碼 增強,其實就是在原本邏輯前後增加一些邏輯,而調用者無感知。代理模式屬於結構型 模式,有靜態代理和動態代理。

靜態代理

舉個例子:人到了適婚年齡,父母總是迫不及待希望早點抱孫子。而現在社會的人在各 種壓力之下,都選擇晚婚晚育。於是著急的父母就開始到處為自己的子女相親,比子女 自己還著急。這個相親的過程,就是一種我們人人都有份的代理。來看代碼實現: 

/**
 * 人很多行為,要談戀愛
 */
public interface Person {
    void findLove();
}


/**
 * 兒子需要找對象
 */
public class Son implements Person {

    @Override
    public void findLove() {
        System.out.println("工作沒時間!");
    }
}

/**
 * 父親代理兒子 先幫物色對象
 */
public class Father{

    private Son son;

    //代理對象持有 被代理對象的應用 但沒辦法擴展
    private Father(Son son) {
        this.son = son;
    }

    private void findLove() {
        //before
        System.out.println("父母幫物色對象");
        son.findLove();
        //after
        System.out.println("雙方同意交往!");
    }

    //測試代碼
    public static void main(String[] args) {
        Son son = new Son();
        Father father = new Father(son);
        father.findLove();
    }
}

動態代理

動態代理和靜態對比基本思路是一致的,只不過動態代理功能更加強大,隨著業務的擴 展適應性更強。如果還以找對象為例,使用動態代理相當於是能夠適應複雜的業務場景。 不僅僅只是父親給兒子找對象,如果找對象這項業務發展成了一個產業,進而出現了媒 婆、婚介所等這樣的形式。那麼,此時用靜態代理成本就更大了,需要一個更加通用的 解決方案,要滿足任何單身人士找對象的需求。我們升級一下代碼,先來看 JDK 實現方式:

JDK 實現方式

創建媒婆(婚介)JDKMeipo 類:

package com.study;

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


public class JdkMeipo implements InvocationHandler {
  	//持有被代理對象的引用
    Object target;

    public Object getInstance(Object target){
        this.target =target;
        Class<?> aClass = target.getClass();
        return Proxy.newProxyInstance(aClass.getClassLoader(),aClass.getInterfaces(),this);
    }
  
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object object = method.invoke(this.target,args);
        after();
        return object;
    }

    private void before() {
        System.out.println("我是婚介,幫你物色對象");
    }

    private void after() {
        System.out.println("已找到,如果合適就開始");
    }
}

創建單身客戶

package com.study;

public class Customer implements Person{

    @Override
    public void findLove() {
        System.out.println("我要找白富美");
    }
}

測試代碼

package com.study;

public class DemoTest {
    public static void main(String[] args) {
        Person person = (Person) new JdkMeipo().getInstance(new Customer());
        person.findLove();
    }
}

執行效果

Jdk代理的原理

不僅知其然,還得知其所以然。既然 JDK Proxy 功能如此強大,那麼它是如何實現的呢? 我們現在來探究一下原理。 我們都知道 JDK Proxy 採用位元組重組,重新生的對象來替代原始的對象以達到動態代理 的目的。JDK Proxy 生成對象的步驟如下:

1.拿到代理對象的應用,並獲取它的所有介面,反射獲取。

2.通過JDK proxy 類重新生成一個新的類,同時新的類要實現被代理類所有實現的介面。

3.動態生成Java代碼,把新加的業務邏輯方法由一定的邏輯代碼去調用(在代碼中體現)。

4.編譯重新生成Java代碼.class

5.再重新載入的JVM中
以上這個過程就叫位元組重組。

CGLib實現方式

簡單看一下 CGLib 代理的使用,還是以媒婆為例,創建 CglibMeipo 類:

package com.study;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGlibMeipo implements MethodInterceptor {
    Object target;
    public Object getInstance(Class<?> aClass){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(aClass);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object invokeSuper = methodProxy.invokeSuper(o, objects);
        after();
        return invokeSuper;
    }

    private void after() {
        System.out.println("已找到,如果合適就開始");
    }

    private void before() {
        System.out.println("我是婚介,幫你物色對象");
    }
}

測試調用

package com.study;

public class DemoTest {
    public static void main(String[] args) {
        //Person person = (Person) new JdkMeipo().getInstance(new Customer());
        Person person = (Person) new CGlibMeipo().getInstance(Customer.class);
        person.findLove();
    }
}

執行結果:(CGLib代理的對象是不需要實現任何介面的,他是通過動態繼承目標對象實現的動態代理。)

CGLib 動態代理執行代理方法效率之所以比 JDK 的高是因為 Cglib 採用了 FastClass 機 制,它的原理簡單來說就是:為代理類和被代理類各生成一個 Class,這個 Class 會為代 理類或被代理類的方法分配一個 index(int 類型)。這個 index 當做一個入參,FastClass 就可以直接定位要調用的方法直接進行調用,這樣省去了反射調用,所以調用效率比 JDK 動態代理通過反射調用高。

CGLib 和 JDK 動態代理對比

1.JDK 動態代理是實現了被代理對象的介面,CGLib 是繼承了被代理對象。

2.JDK 和 CGLib 都是在運行期生成位元組碼,JDK 是直接寫 Class 位元組碼,CGLib 使用 ASM 框架寫 Class 位元組碼,Cglib 代理實現更複雜,生成代理類比 JDK 效率低。

3.JDK 調用代理方法,是通過反射機制調用,CGLib 是通過 FastClass 機制直接調用方法, CGLib 執行效率更高。