Java代理模式
參考地址:https://javaguide.cn/java/basis/proxy.html#_1-代理模式
簡單來說,就是給原本的類書寫一個代理類,我們透過代理類來操作原類。可以在不修改原目標物件的前提下,提供額外的功能操作,擴充套件目標物件的功能。
一、靜態代理
靜態代理中,我們對目標物件的每個方法的增強都是手動完成的,非常不靈活且麻煩。具體步驟:
定義一個介面及其實現類;
建立一個代理類同樣實現這個介面
將目標物件注入進代理類,然後在代理類的對應方法呼叫目標類中的對應方法。這樣的話,我們就可以透過代理類遮蔽對目標物件的訪問,並且可以在目標方法執行前後做一些自己想做的事情。
1、建立一個介面
public interface SmsService {
String send(String msg);
}
2、定義一個實現類(這就是我們被代理的物件)
public class SmsServiceImpl implements SmsService {
@Override
public String send(String msg) {
System.out.println("我傳送了訊息:" + msg);
return msg;
}
}
3、定義一個代理類
其實這裡我們可以思考一下,代理類由於和原本類實現了同一個介面,所以原類必須實現的規則代理類也必須實現。而且在此基礎上由於維護了一個原類的物件的介面屬性,可以透過有參構造方法傳遞需要代理的類並進行補充和增強。
public class SmsProxy implements SmsService {
private final SmsService smsService; // 維護一個常量物件
public SmsProxy(SmsService smsService) { //類初始化的時候賦值
this.smsService = smsService;
}
@Override
public String send(String msg) {
System.out.println("在傳送之前");
String m = this.smsService.send(msg);
System.out.println("在傳送之後");
return m;
}
}
4、測試
public class Main {
public static void main(String[] args) {
SmsService smsService = new SmsServiceImpl();
SmsProxy smsProxy = new SmsProxy(smsService);
smsProxy.send("你好世界!");
}
}
對應的輸出結果(這裡其實感覺到了一點Spring AOP的影子了是吧):
在傳送之前
我傳送了訊息:你好世界!
在傳送之後
二、動態代理
JDK動態代理機制
流程:
定義一個介面及其實現類;
自定義 InvocationHandler
並重寫invoke
方法,在 invoke
方法中我們會呼叫原生方法(被代理類的方法)並自定義一些處理邏輯;
透過 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法建立代理物件;
1、建立一個介面
public interface SmsService {
String send(String message);
}
2、定義一個實現類
public class SmsServiceImpl implements SmsService {
@Override
public String send(String message) {
System.out.println("傳送了訊息:" + message);
return message;
}
}
3、定義一個 InvocationHandler 的實現類並重寫 invoke 方法,沒有自己針對每一個需要代理的類都寫一個代理類
這裡可以想一下,我們可以對一些方法進行增強,當然也可以透過判斷一些特殊的方法進行一定的特殊處理。
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理類中的真實物件
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
/**
* 反射的感覺
* invoke() 方法: 當我們的動態代理物件呼叫原生方法的時候,
* 最終實際上呼叫到的是 invoke() 方法,
* 然後 invoke() 方法代替我們去呼叫了被代理物件的原生方法
* @param proxy 動態生成的代理類
* @param method 與代理類物件呼叫的方法相對應
* @param args 當前 method 方法的引數
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//呼叫方法之前,我們可以新增自己的操作
System.out.println("在方法呼叫之前");
Object result = method.invoke(target, args);
//呼叫方法之後,我們同樣可以新增自己的操作
System.out.println("在方法呼叫之後");
return result;
}
}
4、定義簡單工廠類獲取代理物件
這裡的Proxy類的 newProxyInstance 方法用於生成一個代理類的例項,然後會去呼叫傳入的自定義的 InvocationHandler 的實現類中的 invoke 方法。
public class JdkProxyFactory {
/**
傳入的引數是被代理的物件,返回的是其代理物件
*/
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目標類的類載入器,可以去了解一下JVM類載入過程
target.getClass().getInterfaces(), // 代理需要實現的介面,可指定多個
new DebugInvocationHandler(target) // 代理物件對應的自定義 InvocationHandler
);
}
}
5、測試
public class Main {
public static void main(String[] args) {
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");
}
}
對應輸出結果:
在方法呼叫之前
傳送了訊息:java
在方法呼叫之後
CGLIB 動態代理機制
關鍵在於,這個方式不需要被代理的類實現某一個介面。
流程:
定義一個類;
自定義 MethodInterceptor
並重寫 intercept
方法,intercept
用於攔截增強被代理類的方法,和 JDK 動態代理中的 invoke
方法類似;
透過 Enhancer
類的 create()
建立代理類;
1、建立一個maven專案
引入依賴:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2、定義一個類
public class SmsService {
public String send(String msg) {
System.out.println("傳送訊息:" + msg);
return msg;
}
}
3、定義一個 MethodInterceptor 的實現類 並重寫其中的 intercept 方法
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o 被代理的物件(需要增強的物件)
* @param method 被攔截的方法(需要增強的方法)
* @param args 方法入參
* @param methodProxy 用於呼叫原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//呼叫方法之前,我們可以新增自己的操作
System.out.println("在方法呼叫之前");
Object object = methodProxy.invokeSuper(o, args);
//呼叫方法之後,我們同樣可以新增自己的操作
System.out.println("在方法呼叫之後");
return object;
}
}
4、定義簡單工廠類獲取代理物件
這裡的Enhancer類的 create() 方法用於生成一個代理類的例項,使用前設定對應的幾個引數。
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 建立動態代理增強類
Enhancer enhancer = new Enhancer();
// 設定類載入器
enhancer.setClassLoader(clazz.getClassLoader());
// 設定被代理類
enhancer.setSuperclass(clazz);
// 設定方法攔截器,這裡是你自實現的攔截器
enhancer.setCallback(new DebugMethodInterceptor());
// 建立代理類
return enhancer.create();
}
}
5、測試
public class Main {
public static void main(String[] args) {
SmsService aliSmsService = (SmsService) CglibProxyFactory.getProxy(SmsService.class);
aliSmsService.send("你好世界!");
}
}
對應輸出結果:
在方法呼叫之前
傳送訊息:你好世界!
在方法呼叫之後