代理模式詳解:靜態代理、JDK動態代理與Cglib動態代理
阿新 • • 發佈:2021-03-09
1. 代理模式簡介分類
- 概念
代理,是為了在不修改目標物件的基礎上,增強目標方法的業務邏輯。
客戶類需要執行的是目標物件的目標方法,但是真正執行的是代理物件的代理方法,客戶類對目標物件的訪問是通過代理物件來實現的。當然,代理類與目標類需要實現同一個介面。
- 舉例
生活中遇到了官司,我們平常老百姓對法律的瞭解不全面,所以一般都會請律師處理。
目標物件:法庭上我們一般稱為當事人即目標物件
代理類:律師稱為代理律師即代理類
共同介面:都為了一個共同的目標努力贏得官司即為共同介面
目標方法:我們所做的提供證據各種努力成為目標方法
代理方法:再此過程中我們可能做不全面,律師對證據材料等進行整理收集,結合法律法規進行辯證等等,此過程稱為代理方法
- 代理分類
代理模式一般分為靜態代理與動態代理,動態代理又分為JDK動態代理與CGLIB動態代理
2. 靜態代理
- 概念
靜態代理是指,代理類在程式執行前就已經定義好,其與目標類等關係在程式執行前就已經確立。
靜態代理類似於富翁於私人律師的代理關係,並不是在發生官司之後才去請律師,而是在此之前已經確立好的代理關係。
- 實現與解析
a、定義業務介面
```java
package com.rangers.proxy.staticProxy;
/**
* @Author Rangers
* @Description
* @Date 2021-03-09
**/
public interface IAccountService {
// 轉賬業務介面
void transfer();
}
```
b、定義目標類與目標方法
```java
package com.rangers.proxy.staticProxy;
/**
* @Author Rangers
* @Description
* @Date 2021-03-09
**/
public class AccountServiceImpl implements IAccountService {
// 轉賬業務實現即目標方法
@Override
public void transfer() {
System.out.println("進行轉賬操作");
}
}
```
c、定義代理類AccountServiceImplProxy,實現IAccountService介面。在有參構造方法中傳入目標物件,將目標物件引入代理類,以便代理類呼叫目標方法,進行增強
```java
package com.rangers.proxy.staticProxy;
/**
* @Author Rangers
* @Description
* @Date 2021-03-09
**/
public class AccountServiceImplProxy implements IAccountService {
// 宣告目標介面物件
private IAccountService target;
public AccountServiceImplProxy() {
}
// 業務介面物件作為構造器,用於接收目標物件
public AccountServiceImplProxy(IAccountService target) {
this.target = target;
}
@Override
public void transfer() {
// 此處對目標方法進行增強
System.out.println("對轉賬人身份校驗。。");
target.transfer();
System.out.println("進行日誌記錄。。");
}
}
```
d、編寫測試類TransferServiceTest
```java
package com.rangers.proxy.staticProxy;
/**
* @Author Rangers
* @Description
* @Date 2021-03-09
**/
public class TransferServiceTest {
public static void main(String[] args) {
// 建立目標物件
IAccountService target = new AccountServiceImpl();
// 建立代理物件,傳入目標物件進行初始化
IAccountService proxy = new AccountServiceImplProxy(target);
// 執行代理物件的方法
proxy.transfer();
}
}
```
3. 動態代理
動態代理,程式在整個執行過程中根本就不存在目標類的代理類,目標物件的代理物件只是由代理工具在程式執行時由JVM根據反射等機制動態生成。代理物件與目標物件的代理關係在程式執行時才確立。
動態代理類似於普通人在有官司之後,再聘請律師的,即代理關係是在官司發生後確立的。
動態代理的實現方式有兩種:JDK的動態代理、CGLIB動態代理
a、JDK動態代理
- 概念
JDK動態代理是通過JDK提供的 java.lang.reflect.Proxy類實現動態大力,使用其靜態方法newProxyInstance(),對目標物件、業務介面及業務增強邏輯,自動生成一個動態代理物件。
```java
public static newProxyInstance(ClassLoader classLoader,Class> interfaces,InvocationHandler handler)
classLoader:傳入目標類的類載入器,通過目標物件的反射獲取
interfaces:目標物件實現的介面陣列,通過目標物件的反射獲取
handler:業務增強邏輯,需要具體實現
```
InvocationHandler是個介面,實現InvocationHandler介面的類用於增加目標類的業務邏輯。需要實現invoke()方法,具體的增強邏輯就是在此方法中進行實現,程式呼叫住業務邏輯時會自動呼叫invoke()方法
```java
public Object invoke(Object proxy,Method method,Object[] args)
proxy:生成的代理物件
method:目標方法
args:目標方法的引數
```
Method類物件,invoke()方法進行執行目標物件的目標方法
```java
public Object invoke(Object obj,Object args)
method.invoke(Object target,Object...args)執行目標方法
target:目標物件
args:目標方法的執行引數
```
- 實現與解析
1. 定義業務介面與實現類
```java
package com.rangers.proxy.jdkProxy;
/**
* @Author Rangers
* @Description
* @Date 2021-03-09
**/
public interface IAccountService {
// 轉賬業務介面
void transfer();
}
```
```java
package com.rangers.proxy.jdkProxy;
/**
* @Author Rangers
* @Description
* @Date 2021-03-09
**/
public class AccountServiceImpl implements IAccountService {
// 轉賬業務實現即目標方法
@Override
public void transfer() {
System.out.println("進行轉賬操作");
}
}
```
2. 定義JdkProxy類實現InvocationHandler介面,實現invoke()方法,並對業務邏輯進行增強
```java
package com.rangers.proxy.jdkProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @Author Rangers
* @Description
* @Date 2021-03-09
**/
public class JdkProxy implements InvocationHandler {
private Object target;
public JdkProxy() {
}
public JdkProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 此處對目標方法進行增強
System.out.println("對轉賬人身份校驗。。");
Object result = method.invoke(target, args);
System.out.println("進行日誌記錄。。");
return result;
}
}
```
3. 新建測試類JDKProxyTest
```java
package com.rangers.proxy.jdkProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Author Rangers
* @Description
* @Date 2021-03-09
**/
public class JDKProxyTest {
public static void main(String[] args) {
// 建立目標物件
IAccountService target = new AccountServiceImpl();
// 建立代理物件,傳入目標物件進行初始化
IAccountService proxyService =
(IAccountService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new JdkProxy(target)
);
// 亦可使用匿名類進行實現
/*(IAccountService) 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("對轉賬人身份校驗。。");
Object result = method.invoke(target, args);
System.out.println("進行日誌記錄。。");
return result;
}
});*/
// 此處執行的業務方法就是代理物件的增強過的邏輯
proxyService.transfer();
}
}
```
注:使用JDK動態代理時需要目標類目標方法必須在實現的介面中,否則不能使用此方式進行動態打擊。對於無介面的類需要實現動態代理,就要使用CGLIB方式來進行實現
b、CGLIB動態代理
- 概念
CGLIB是一個開源的第三方程式碼生成類庫,對於無介面的類,要為其建立動態代理,就要使用CGLIB進行實現。CGLIB代理的生成原理是生成目標類的子類,子類是增強過的,就是目標類的代理類。所以,使用CGLIB生成動態代理,要求目標類必須能夠被繼承,即不能是final修飾的類。
CGLIB包的底層是通過使用一個小兒快的位元組碼處理框架ASM(java位元組碼操控框架),來轉換位元組碼並生成新的類,通過對位元組碼進行增強來生成代理類。
我們靜態代理理解為私人律師,JDK動態代理成為代理律師,CGLIB動態代理可以理解為老父親的兒子。老父親是被需要增強對目標類,兒子則是用於增強父親對代理類,事先不需要約定。父親需要兒子增強什麼,兒子就增強什麼,即他們之間的關係不要介面來進行約束。
- 注意要點
使用CGLIB動態代理時,生成代理類的類需要實現MethodInterceptor介面及intercept()方法
```java
public Object intercept(Object proxy,Method method,Objectp[] args,MethodProxy methodProxy)
proxy:代理物件
method:代理物件的方法,即增強後的方法
args:方法引數
methodProxy:代理方法的物件
```
建立代理物件時使用Enhancer類
```java
// 建立增強器
Enhancer enhancer = new Enhancer();
// 初始化增強器:將目標類指定為父類
enhancer.setSuperclass(target.class);
// 初始化增強器:設定回撥至本類中的intercept()方法
enhancer.setCallback(this);
// 使用增強器建立代理物件進行返回
enhancer.create();
```
- 實現與解析
1. 引入CGLIB依賴
```xml
cglib
cglib-full
2.0.2
```
2. 建立目標類AccountService
```java
package com.rangers.proxy.cglibProxy;
/**
* @Author Rangers
* @Description
* @Date 2021-03-09
**/
public class AccountService {
// 轉賬業務 即目標方法
public void transfer() {
System.out.println("進行轉賬操作");
}
// 查詢餘額 即目標方法
public void getBalance() {
System.out.println("查詢餘額操作");
}
}
```
3. 建立代理類AccountServiceCglibProxy實現MethodInterceptor介面,完善intercept()方法進行增強,建立生成代理物件createProxy()方法
```java
package com.rangers.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;
/**
* @Author Rangers
* @Description
* @Date 2021-03-09
**/
public class AccountServiceCglibProxy implements MethodInterceptor {
// 宣告目標類的成員變數,並建立以目標類為引數的構造器,用於接收目標物件
private AccountService target;
public AccountServiceCglibProxy(AccountService accountService) {
this.target = accountService;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 此處對目標方法進行增強
Object result = new Object();
if ("transfer".equals(method.getName())){
System.out.println("對轉賬人身份校驗。。");
result = method.invoke(target, args);
System.out.println("進行日誌記錄。。");
}else{
// 直接執行目標物件的目標方法
result = methodProxy.invokeSuper(target,args);
}
return result;
}
// 建立代理物件
public AccountService createProxy(){
// 建立增強器
Enhancer enhancer = new Enhancer();
// 初始化增強器:將目標類指定為父類
enhancer.setSuperclass(AccountService.class);
// 初始化增強器:設定回撥至本類中的intercept()方法
enhancer.setCallback(this);
// 使用增強器建立代理物件
return (AccountService) enhancer.create();
}
}
```
4. 建立測試類CglibProxyTest
```java
package com.rangers.proxy.cglibProxy;
/**
* @Author Rangers
* @Description
* @Date 2021-03-09
**/
public class CglibProxyTest {
public static void main(String[] args) {
// 目標物件
AccountService target = new AccountService();
// 建立代理物件,傳入目標物件進行初始化
AccountService accountService = new AccountServiceCglibProxy(target).createProxy();
accountService.transfer();
accountService.getBalance();
}
}