《小緹娜的奇幻之地》職業組合名稱一覽
阿新 • • 發佈:2022-04-10
代理模式
我們使用代理物件來代替對真實物件(real object)的訪問,這樣就可以在不修改原目標物件的前提下,提供額外的功能操作,擴充套件目標物件的功能。 代理模式的主要作用是擴充套件目標物件的功能,比如說在目標物件的某個方法執行前後你可以增加一些自定義的操作。
靜態代理
實現步驟:
- 定義一個介面及其實現類;
public interface SmsService {//定義介面 String send(String message); } public class SmsServiceImpl implements SmsService {//實現介面 public String send(String message) { System.out.println("send message:" + message); return message; } }
- 建立一個代理類同樣實現這個介面;
public class SmsProxy implements SmsService {//代理類同樣實現介面 private final SmsService smsService; public SmsProxy(SmsService smsService) { this.smsService = smsService; } @Override public String send(String message) {//加入前後處理,實現AOP //呼叫方法之前,我們可以新增自己的操作 System.out.println("before method send()"); smsService.send(message); //呼叫方法之後,我們同樣可以新增自己的操作 System.out.println("after method send()"); return null; } }
- 將目標物件注入進代理類,然後在代理類的對應方法呼叫目標類中的對應方法。
public class Main {
public static void main(String[] args) {
SmsService smsService = new SmsServiceImpl();
SmsProxy smsProxy = new SmsProxy(smsService);
smsProxy.send("java");
}
}
動態代理
動態代理更加靈活,不需要針對每個目標類單獨建立一個代理類,也不需要必須實現介面。
常用的兩種動態代理:JDK 動態代理,CGLIB 動態代理
JDK 動態代理
要實現動態代理的話,必須需要實現 InvocationHandler 來自定義處理邏輯。 當我們的動態代理物件呼叫一個方法時,這個方法的呼叫就會被轉發到實現 InvocationHandler 介面類的 invoke 方法來呼叫。
使用步驟:
- 定義一個介面及其實現類;
public interface SmsService {//定義介面
String send(String message);
}
public class SmsServiceImpl implements SmsService {//實現介面
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
- 自定義 InvocationHandler 並重寫 invoke 方法,在 invoke 方法中我們會呼叫原生方法(被代理類的方法)並自定義一些處理邏輯;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author shuang.kou
* @createTime 2020年05月11日 11:23:00
*/
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理類中的真實物件
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//呼叫方法之前,我們可以新增自己的操作
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
//呼叫方法之後,我們同樣可以新增自己的操作
System.out.println("after method " + method.getName());
return result;
}
}
- 通過 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法建立代理物件;
public class JdkProxyFactory {//獲取代理類物件的工廠類
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目標類的類載入
target.getClass().getInterfaces(), // 代理需要實現的介面,可指定多個
new DebugInvocationHandler(target) // 代理物件對應的自定義 InvocationHandler
);
}
}
- 實際使用
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");
CGLIB 動態代理
JDK 動態代理有個關鍵問題是隻能代理實現了介面的類。 Spring 中的 AOP 模組中:如果目標物件實現了介面,則預設採用 JDK 動態代理,否則採用 CGLIB 動態代理。
實現步驟:
- 定義一個類
package github.javaguide.dynamicProxy.cglibDynamicProxy;
public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
- 自定義 MethodInterceptor(方法攔截器)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定義MethodInterceptor
*/
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("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//呼叫方法之後,我們同樣可以新增自己的操作
System.out.println("after method " + method.getName());
return object;
}
}
- 通過 Enhancer 類的 create()建立代理類;
import net.sf.cglib.proxy.Enhancer;
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();
}
}
- 實際使用
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");
兩者區別
- JDK 動態代理只能代理實現了介面的類或者直接代理介面,而 CGLIB 可以代理未實現任何介面的類。
- 效率:大部分情況都是 JDK 動態代理更優秀,隨著 JDK 版本的升級,這個優勢更加明顯。
靜態、動態代理區別
- 靈活性:動態代理更加靈活,可以不需要針對每個目標類都建立一個代理類。靜態代理中,介面一旦新增加方法,目標物件和代理物件都要進行修改。
- 靜態代理在編譯時就將介面、實現類、代理類這些都變成了一個個實際的位元組碼(class)檔案;動態代理是在執行時動態生成類位元組碼,並載入到 JVM 中的。