Java動態代理設計模式
阿新 • • 發佈:2020-12-04
本文主要介紹`Java`中兩種常見的動態代理方式:`JDK原生動態代理`和`CGLIB動態代理`。
## 什麼是代理模式
就是為其他物件提供一種代理以控制對這個物件的訪問。代理可以在不改動目標物件的基礎上,增加其他額外的功能(擴充套件功能)。
代理模式角色分為 3 種:
- `Subject`(抽象主題角色):定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法;
- `RealSubject`(真實主題角色):真正實現業務邏輯的類;
- `Proxy`(代理主題角色):用來代理和封裝真實主題;
如果根據位元組碼的建立時機來分類,可以分為靜態代理和動態代理:
- 所謂靜態也就是在程式執行前就已經存在代理類的位元組碼檔案,代理類和真實主題角色的關係在執行前就確定了。
- 而動態代理的原始碼是在程式執行期間由JVM根據反射等機制動態的生成,所以在執行前並不存在代理類的位元組碼檔案
## 靜態代理
學習動態代理前,有必要來學習一下靜態代理。
靜態代理在使用時,需要定義介面或者父類,被代理物件(目標物件)與代理物件(Proxy)一起實現相同的介面或者是繼承相同父類。
來看一個例子,模擬小貓走路的時間。
```java
// 介面
public interface Walkable {
void walk();
}
// 實現類
public class Cat implements Walkable {
@Override
public void walk() {
System.out.println("cat is walking...");
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
```
如果我想知道走路的時間怎麼辦?可以將實現類`Cat`修改為:
```java
public class Cat implements Walkable {
@Override
public void walk() {
long start = System.currentTimeMillis();
System.out.println("cat is walking...");
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("walk time = " + (end - start));
}
}
```
這裡已經侵入了原始碼,如果原始碼是不能改動的,這樣寫顯然是不行的,這裡可以引入時間代理類`CatTimeProxy`。
```java
public class CatTimeProxy implements Walkable {
private Walkable walkable;
public CatTimeProxy(Walkable walkable) {
this.walkable = walkable;
}
@Override
public void walk() {
long start = System.currentTimeMillis();
walkable.walk();
long end = System.currentTimeMillis();
System.out.println("Walk time = " + (end - start));
}
}
```
如果這時候還要加上常見的日誌功能,我們還需要建立一個日誌代理類`CatLogProxy`。
```java
public class CatLogProxy implements Walkable {
private Walkable walkable;
public CatLogProxy(Walkable walkable) {
this.walkable = walkable;
}
@Override
public void walk() {
System.out.println("Cat walk start...");
walkable.walk();
System.out.println("Cat walk end...");
}
}
```
如果我們需要先記錄日誌,再獲取行走時間,可以在呼叫的地方這麼做:
```java
public static void main(String[] args) {
Cat cat = new Cat();
CatLogProxy p1 = new CatLogProxy(cat);
CatTimeProxy p2 = new CatTimeProxy(p1);
p2.walk();
}
```
這樣的話,計時是包括打日誌的時間的。
### 靜態代理的問題
如果我們需要計算`SDK`中100個方法的執行時間,同樣的程式碼至少需要重複100次,並且建立至少100個代理類。往小了說,如果`Cat`類有多個方法,我們需要知道其他方法的執行時間,同樣的程式碼也至少需要重複多次。因此,靜態代理至少有以下兩個侷限性問題:
- 如果同時代理多個類,依然會導致類無限制擴充套件
- 如果類中有多個方法,同樣的邏輯需要反覆實現
所以,我們需要一個通用的代理類來代理所有的類的所有方法,這就需要用到動態代理技術。
## 動態代理
學習任何一門技術,一定要問一問自己,這到底有什麼用。其實,在這篇文章的講解過程中,我們已經說出了它的主要用途。你發現沒,使用動態代理我們居然可以在不改變原始碼的情況下,直接在方法中插入自定義邏輯。這有點不太符合我們的一條線走到底的程式設計邏輯,這種程式設計模型有一個專業名稱叫`AOP`。所謂的`AOP`,就像刀一樣,抓住時機,趁機插入。
![](https://user-gold-cdn.xitu.io/2018/3/2/161e5ba2dfeb24bb?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
## Jdk動態代理
JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個引數:
```java
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new Privileg