大型Java進階專題(六)設計模式之代理模式
- 2020 年 7 月 2 日
- 筆記
代理模式
前言
又開始我的專題了,又停滯了一段時間了,加油繼續吧。都知道 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 執行效率更高。