代理模式 (Proxy)
代理設計模式(Proxy Design Pattern)是一種結(jié)構(gòu)型設計模式,它為其他對象提供一個代理,以控制對這個對象的訪問。代理模式可以用于實現(xiàn)懶加載、安全訪問控制、日志記錄等功能。
在設計模式中,代理模式可以分為靜態(tài)代理和動態(tài)代理。靜態(tài)代理是指代理類在編譯時就已經(jīng)確定,而動態(tài)代理是指代理類在運行時動態(tài)生成。
1) 靜態(tài)代理
1.a) 原理解析
在不改變原始類(或叫被代理類)代碼的情況下,通過引入代理類來給原始類附加功能。
1.b) 使用場景
1.緩存代理
緩存代理通常會在內(nèi)部維護一個緩存數(shù)據(jù)結(jié)構(gòu),如 HashMap 或者 LinkedHashMap,用來存儲已經(jīng)處理過的請求及其結(jié)果。
假設有一個數(shù)據(jù)查詢接口,它從數(shù)據(jù)庫或其他數(shù)據(jù)源中檢索數(shù)據(jù)。在沒有緩存代理的情況下,每次查詢都需要訪問數(shù)據(jù)庫,這可能會導致較高的資源消耗和延遲。通過引入緩存代理,我們可以將查詢結(jié)果存儲在內(nèi)存中,從而避免重復查詢數(shù)據(jù)庫。
public interface DataQuery {
String query(String queryKey);
}
public class DatabaseDataQuery implements DataQuery {
@Override
public String query(String queryKey) {
// 使用數(shù)據(jù)源從數(shù)據(jù)庫查詢數(shù)據(jù)很慢
return "result";
}
}
創(chuàng)建一個緩存代理類,它同樣實現(xiàn)了 DataQuery 接口,并在內(nèi)部使用HashMap 作為緩存:
public class DatabaseDataQueryProxy implements DataQuery {
// 實現(xiàn)緩存,需要數(shù)據(jù)結(jié)構(gòu)
private Map<String, String> cache = new HashMap<>(256);
// 你代理誰,就要持有誰
private DatabaseDataQuery dataQuery;
public DatabaseDataQueryProxy() {
// 1.屏蔽被代理對象
this.dataQuery = new DatabaseDataQuery();
}
@Override
public String query(String queryKey) {
// 2.對被代理對象的方法做增強
// 2.1.查詢緩存,命中則返回
String result = cache.get(queryKey);
if (result != null) {
System.out.println("命中緩存,走緩存");
return result;
}
// 2.2.未命中,則查詢數(shù)據(jù)庫
result = dataQuery.query(queryKey);
// 2.2.1.如果有結(jié)果,需要將結(jié)果保存到緩存中,再返回
if (result != null) {
cache.put(queryKey, result);
}
System.out.println("未命中,走持久層");
return result;
}
}
// 測試代碼
@Test
void test() {
DataQuery dataQuery = new DatabaseDataQueryProxy();
String value = dataQuery.query("key1");
System.out.println(value);
value = dataQuery.query("key1");
System.out.println(value);
value = dataQuery.query("key2");
System.out.println(value);
}
2.安全代理
用于控制對真實主題對象的訪問。通過安全代理,可以實現(xiàn)訪問控制、權(quán)限驗證等安全相關(guān)功能。
假設我們有一個敏感數(shù)據(jù)查詢接口,只有具有特定權(quán)限的用戶才能訪問:
3.虛擬代理
在需要時延遲創(chuàng)建耗時或資源密集型對象。虛擬代理在初始訪問時才創(chuàng)建實際對象,之后將直接使用該對象。這可以避免在實際對象尚未使用的情況下就創(chuàng)建它,從而節(jié)省資源。
以下是一個虛擬代理的應用示例:
假設我們有一個大型圖片類,它從網(wǎng)絡加載圖像。由于圖像可能非常大,我們希望在需要顯示時才加載它。為了實現(xiàn)這一點,我們可以創(chuàng)建一個虛擬代理來代表大型圖片類。
4.遠程代理
用于訪問位于不同地址空間的對象。遠程代理可以為本地對象提供與遠程對象相同的接口,使得客戶端可以透明地訪問遠程對象。通常,遠程代理需要處理網(wǎng)絡通信、序列化和反序列化等細節(jié)。
1.c) 靜態(tài)代理步驟總結(jié)
通過前四個案例,我們也大致了解了靜態(tài)代理的使用方式,其大致流程如下:
- 1.創(chuàng)建一個接口,定義 代理類和被代理類 實現(xiàn)共同的接口
- 2.創(chuàng)建被代理類,實現(xiàn)這個接口,并且在其中定義實現(xiàn)方法
- 3.創(chuàng)建代理類,也要實現(xiàn)這個接口,同時在其中定義一個被代理類的對象作為成員變量
- 4.在代理類中實現(xiàn)接口中的方法,方法中調(diào)用 被代理類 中的對應方法
- 5.通過創(chuàng)建代理對象,并調(diào)用其方法,方法增強
這樣,被代理類的方法就會被代理類所覆蓋,實現(xiàn)了對被代理類的增強或修改。
2) 動態(tài)代理
靜態(tài)代理需要手動編寫代理類,代理類與被代理類實現(xiàn)相同的接口或繼承相同的父類,對被代理對象進行包裝。在程序運行前,代理類的代碼就已經(jīng)生成,并在程序運行時調(diào)用。靜態(tài)代理的優(yōu)點是簡單易懂,缺點是需要手動編寫代理類,代碼復雜度較高,且不易擴展。
動態(tài)代理是在程序運行時動態(tài)生成代理類,無需手動編寫代理類,大大降低了代碼的復雜度。動態(tài)代理一般使用 Java 提供的反射機制實現(xiàn),可以對任意實現(xiàn)了接口的類進行代理。動態(tài)代理的優(yōu)點是靈活性高,可以根據(jù)需要動態(tài)生成代理類,缺點是性能相對較低,由于使用反射機制,在運行時會產(chǎn)生額外的開銷。
2.a) 基于 JDK 的動態(tài)代理實現(xiàn)步驟
使用緩存代理的例子
public interface DataQuery {
String query(String queryKey);
}
public class DatabaseDataQuery implements DataQuery {
@Override
public String query(String queryKey) {
// 使用數(shù)據(jù)源從數(shù)據(jù)庫查詢數(shù)據(jù)很慢
return "result";
}
}
創(chuàng)建一個代理類,實現(xiàn) InvocationHandler
接口,實現(xiàn)invoke()
方法,并在其中定義一個被代理類的對象作為屬性。
public class CacheInvocationHandler implements InvocationHandler {
private Map<String, String> cache = new HashMap<>(256);
private DatabaseDataQuery databaseDataQuery;
public CacheInvocationHandler() {
this.databaseDataQuery = new DatabaseDataQuery();
}
public CacheInvocationHandler(DatabaseDataQuery databaseDataQuery) {
this.databaseDataQuery = databaseDataQuery;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1.判斷是哪一個方法 (只對query方法做緩存)
String result;
if ("query".equals(method.getName())) {
// 2.查緩存
// 2.1.命中直接返回
result = cache.get(args[0].toString());
if (result != null) {
System.out.println("從緩存拿數(shù)據(jù)");
return result;
}
// 2.2.未命中,查詢數(shù)據(jù)庫 (需要代理實例)
result = (String) method.invoke(databaseDataQuery, args);
// 3.查詢到了,進行緩存
cache.put(args[0].toString(), result);
return result;
}
// 當其他的方法被調(diào)用,不希望被干預,直接調(diào)用原生的方法
return method.invoke(databaseDataQuery, args);
}
}
主要業(yè)務邏輯 (測試代碼)
@Test
void testJdkDynamicProxy() {
// jdk提供的代理實現(xiàn),主要是使用Proxy類來實現(xiàn)
// 參數(shù)1 classLoader:被代理類的類加載器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 參數(shù)2 代理類需要實現(xiàn)的接口數(shù)組
Class[] interfaces = new Class[]{DataQuery.class};
// 參數(shù)3 InvocationHandler
InvocationHandler invocationHandler = new CacheInvocationHandler();
DataQuery dataQuery = (DataQuery) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
// 調(diào)用query方法時,實際上是調(diào)用了invoke()方法
String result = dataQuery.query("key1");
System.out.println(result);
result = dataQuery.query("key1");
System.out.println(result);
result = dataQuery.query("key2");
System.out.println(result);
System.out.println("-----------------");
result = dataQuery.queryAll();
System.out.println(result);
}
2.b) 基于 CGLIB 的動態(tài)代理實現(xiàn)步驟
基于 CGLIB 的動態(tài)代理需要使用 net.sf.cglib.proxy.Enhancer
類和 net.sf.cglib.proxy.MethodInterceptor
接口。
1.創(chuàng)建一個被代理類,定義需要被代理的方法 (以DatabaseDataQuery
為例)
public class DatabaseDataQuery {
public String query(String queryKey) {
// 使用數(shù)據(jù)源從數(shù)據(jù)庫查詢數(shù)據(jù)很慢
System.out.println("正在從數(shù)據(jù)庫中查詢數(shù)據(jù)");
return "result";
}
public String queryAll() {
System.out.println("正在從數(shù)據(jù)庫中查詢數(shù)據(jù)");
return "query All result";
}
}
2.創(chuàng)建一個方法攔截器類,實現(xiàn) MethodInterceptor
接口,并在其中定義一個被代理類的對象作為屬性。
- 在
intercept
方法中,我們可以對被代理對象的方法進行增強
public class CacheMethodInterceptor implements MethodInterceptor {
private Map<String, String> cache = new HashMap<>(256);
private DatabaseDataQuery databaseDataQuery;
public CacheMethodInterceptor() {
this.databaseDataQuery = new DatabaseDataQuery();
}
public CacheMethodInterceptor(DatabaseDataQuery databaseDataQuery) {
this.databaseDataQuery = databaseDataQuery;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 1.判斷是哪一個方法
String result;
if ("query".equals(method.getName())) {
// 2.查詢緩存,命中則直接返回
result = cache.get(args[0].toString());
if (result != null) {
System.out.println("從緩存中提取數(shù)據(jù)");
return result;
}
// 3.未命中,查詢數(shù)據(jù)庫
result = (String) method.invoke(databaseDataQuery, args);
// 4.緩存到緩存中
cache.put(args[0].toString(), result);
return result;
}
return method.invoke(databaseDataQuery, args);
}
}
3.在使用代理類時,創(chuàng)建被代理類的對象和代理類的對象,并使用 Enhancer.create
方法生成代理對象。
@Test
void testCgkibDynamicProxy() {
// cglib通過enhancer
Enhancer enhancer = new Enhancer();
// 1.設置父類
enhancer.setSuperclass(DatabaseDataQuery.class);
// 2.設置一個方法攔截器,用來攔截方法
enhancer.setCallback(new CacheMethodInterceptor());
// 3.創(chuàng)建代理類
DatabaseDataQuery databaseDataQuery = (DatabaseDataQuery) enhancer.create();
String value = databaseDataQuery.query("key1");
System.out.println(value);
value = databaseDataQuery.query("key1");
System.out.println(value);
value = databaseDataQuery.query("key2");
System.out.println(value);
}
2.c) Spring中aop的使用步驟
在 Spring 中,AOP(面向切面編程)提供了一種有效的方式來對程序中的多個模塊進行橫切關(guān)注點的處理,例如日志、事務、緩存、安全等。使用 Spring AOP,可以在程序運行時動態(tài)地將代碼織入到目標對象中,從而實現(xiàn)對目標對象的增強。
Spring AOP 的使用步驟如下:
1.引入 AOP 相關(guān)依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.3.9.RELEASE</version>
</dependency>
2.在Main
中開啟自動代理@EnableAspectJAutoProxy
@SpringBootApplication
@EnableAspectJAutoProxy
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
3.定義接口和實現(xiàn)類,并將具體實現(xiàn)注入容器 (以DatabaseDataQuery
為例)
// 接口
public interface DataQuery {
String query(String queryKey);
}
// 實現(xiàn)類
@Component
public class DatabaseDataQuery implements DataQuery {
@Override
public String query(String queryKey) {
// 使用數(shù)據(jù)源從數(shù)據(jù)庫查詢數(shù)據(jù)很慢
System.out.println("正在從數(shù)據(jù)庫中查詢數(shù)據(jù)");
return "result";
}
}
4.定義切面類,對方法做增強文章來源:http://www.zghlxwxcb.cn/news/detail-691908.html
- @Pointcut() 對某包下的某個類的某個方法做增強:
..
代表任意方法 - @Around() 定義增強
@Component
@Aspect
public class CacheAspectj {
private static Map<String,String> cache = new ConcurrentHashMap<>(256);
@Pointcut("execution(* com.dcy.structural.proxy.dynamicProxy.aop.impl.DatabaseDataQuery.query(..))")
public void pointcut() {}
@Around("pointcut()")
public String around(ProceedingJoinPoint joinPoint) {
// 1.查詢緩存
Object[] args = joinPoint.getArgs();
String key = args[0].toString();
// 1.1.命中則返回
String result = cache.get(key);
if (result != null) {
System.out.println("數(shù)據(jù)從緩存中提取");
return result;
}
// 2.未命中,查詢數(shù)據(jù)庫,實際上是調(diào)用被代理bean的方法
try {
result = joinPoint.proceed().toString();
// 如果查詢有結(jié)果,進行緩存
cache.put(key, result);
} catch (Throwable e) {
throw new RuntimeException(e);
}
return result;
}
}
5.測試用例文章來源地址http://www.zghlxwxcb.cn/news/detail-691908.html
@SpringBootTest
public class AopTest {
@Resource
private DataQuery dataQuery;
@Test
void testSpringAop() {
String result = dataQuery.query("key1");
System.out.println(result);
result = dataQuery.query("key1");
System.out.println(result);
result = dataQuery.query("key2");
System.out.println(result);
}
}
到了這里,關(guān)于設計模式-代理模式Proxy的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!