Java JDK動(dòng)態(tài)代理在攔截器和聲明式接口中的應(yīng)用小結(jié)
一、動(dòng)態(tài)代理概念回顧
Java動(dòng)態(tài)代理技術(shù)是基于反射機(jī)制的基礎(chǔ)。核心在于利用反射機(jī)制和接口編程在運(yùn)行時(shí)動(dòng)態(tài)生成代理類(lèi),并通過(guò)InvocationHandler
接口實(shí)現(xiàn)代理邏輯的靈活擴(kuò)展。通過(guò)動(dòng)態(tài)代理,Java程序可以在運(yùn)行時(shí)動(dòng)態(tài)地生成代理類(lèi),并控制對(duì)目標(biāo)對(duì)象的訪問(wèn),從而實(shí)現(xiàn)對(duì)目標(biāo)對(duì)象方法的攔截和增強(qiáng)。
其優(yōu)勢(shì)在于靈活性、可擴(kuò)展性、解耦、AOP支持和遠(yuǎn)程方法調(diào)用等方面.
Java動(dòng)態(tài)代理原理主要基于Java的反射機(jī)制,通過(guò)動(dòng)態(tài)地生成代理類(lèi)來(lái)實(shí)現(xiàn)對(duì)接口的動(dòng)態(tài)代理。
二、 核心組件
java.lang.reflect.Proxy
類(lèi):這是Java動(dòng)態(tài)代理的核心類(lèi),提供了創(chuàng)建動(dòng)態(tài)代理實(shí)例的靜態(tài)方法。java.lang.reflect.InvocationHandler
接口:該接口定義了一個(gè)方法invoke(Object proxy, Method method, Object[] args)
,代理實(shí)例在調(diào)用接口方法時(shí),會(huì)調(diào)用此方法。
三、工作流程
- 定義業(yè)務(wù)接口:首先,需要定義一個(gè)或多個(gè)業(yè)務(wù)接口,這些接口將被動(dòng)態(tài)代理。
- 實(shí)現(xiàn)InvocationHandler接口:創(chuàng)建一個(gè)實(shí)現(xiàn)了
InvocationHandler
接口的類(lèi),在該類(lèi)的invoke
方法中編寫(xiě)代理邏輯。invoke
方法會(huì)在代理對(duì)象調(diào)用接口方法時(shí)被自動(dòng)調(diào)用。 - 生成代理實(shí)例:通過(guò)
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法生成代理實(shí)例。這個(gè)方法接收三個(gè)參數(shù):- ClassLoader loader:類(lèi)加載器,用于加載代理類(lèi)。
- Class<?>[] interfaces:一個(gè)接口數(shù)組,代理類(lèi)將實(shí)現(xiàn)這些接口。
- InvocationHandler h:當(dāng)代理對(duì)象的方法被調(diào)用時(shí),會(huì)調(diào)用此處理器的
invoke
方法。
- 調(diào)用代理實(shí)例的方法:當(dāng)調(diào)用代理實(shí)例的方法時(shí),實(shí)際上會(huì)調(diào)用
InvocationHandler
的invoke
方法,然后可以在invoke
方法中執(zhí)行自定義邏輯,并調(diào)用實(shí)際接口實(shí)現(xiàn)的方法。 - 動(dòng)態(tài)生成字節(jié)碼:
Proxy.newProxyInstance
方法內(nèi)部會(huì)動(dòng)態(tài)生成一個(gè)實(shí)現(xiàn)了指定接口的代理類(lèi)字節(jié)碼。這個(gè)代理類(lèi)會(huì)重寫(xiě)接口中的所有方法,并在方法內(nèi)部調(diào)用InvocationHandler
的invoke
方法。JDK生成的代理類(lèi)名稱通常為com.sun.proxy.$ProxyN
,其中N
是一個(gè)遞增的數(shù)字,表示這是第幾個(gè)動(dòng)態(tài)生成的代理類(lèi)。
四、動(dòng)態(tài)代理的應(yīng)用場(chǎng)景
動(dòng)態(tài)代理的兩個(gè)最常用見(jiàn)應(yīng)用場(chǎng)景為 攔截器 和 聲明性接口 :
4.1 搭載器(AOP)
搭載器就是將目標(biāo)組件劫持,在執(zhí)行目標(biāo)組件代碼的前后,塞入一些其它代碼。比如在正式執(zhí)行業(yè)務(wù)方法前,先進(jìn)行權(quán)限校驗(yàn),如果校驗(yàn)不通過(guò),則拒絕繼續(xù)執(zhí)行。對(duì)于此類(lèi)操作,業(yè)界已經(jīng)抽象出一組通用的編程模型:面向切面編程AOP。
接口定義和實(shí)現(xiàn)
public interface Performer { void play(String subject); String introduction(); } public interface Director { List<String> getCreations(); } public class DefaultActor implements Performer { @Override public void play(String subject) { System.out.println("[DefaultActor]: 默認(rèn)男演員正在即興表演《" + subject + "》"); } @Override public String introduction() { return "李白·上李邕: 大鵬一日同風(fēng)起,扶搖直上九萬(wàn)里。假令風(fēng)歇時(shí)下來(lái),猶能顛卻滄溟水。世人見(jiàn)我恒殊調(diào),聞?dòng)啻笱越岳湫?。宣父尚能畏后生,丈夫未可輕年少。"; } } public class DefaultDirector implements Director { @Override public List<String> getCreations() { List<String> creations = new ArrayList<>(); creations.add("《霸王別姬》"); creations.add("《活著》"); return creations; } }
攔截器核心類(lèi)
攔截器核心類(lèi)實(shí)現(xiàn)了InvocationHandler
接口,并在invoke
方法中插入攔截邏輯。
package guzb.diy.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class ProxyForInterceptor implements InvocationHandler { private Object target; public ProxyForInterceptor(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置攔截邏輯:開(kāi)始執(zhí)行 " + method.getName() + " 方法"); Object result = method.invoke(target, args); System.out.println("后置攔截邏輯:完成執(zhí)行 " + method.getName() + " 方法"); return result; } }
測(cè)試
public class IntercepterTestMain { public static void main(String[] args) { // 創(chuàng)建原始對(duì)象 Performer actor = new DefaultActor(); Director director = new DefaultDirector(); // 創(chuàng)建代理對(duì)象 Performer actorProxy = (Performer) Proxy.newProxyInstance( Performer.class.getClassLoader(), new Class[]{Performer.class}, new ProxyForInterceptor(actor) ); Director directorProxy = (Director) Proxy.newProxyInstance( Director.class.getClassLoader(), new Class[]{Director.class}, new ProxyForInterceptor(director) ); // 通過(guò)代理對(duì)象調(diào)用方法 actorProxy.play("京劇"); System.out.println(actorProxy.introduction()); System.out.println("導(dǎo)演的作品集:"); directorProxy.getCreations().forEach(System.out::println); } }
4.2 聲明是接口
MyBatis中,聲明式接口(通過(guò)注解@Select
、@Insert
等)允許直接在接口方法上通過(guò)注解來(lái)定義SQL語(yǔ)句,而不需要編寫(xiě)具體的SQL實(shí)現(xiàn)類(lèi)。這種方式使得代碼更加簡(jiǎn)潔,易于維護(hù)。
使用JDK動(dòng)態(tài)代理來(lái)模擬MyBatis中的聲明式接口。
定義業(yè)務(wù)接口
先定義一個(gè)業(yè)務(wù)接口,里面包含使用注解定義的SQL操作。
public interface UserMapper { @Select("SELECT * FROM user WHERE name = #{name}") User findUserByName(String name); } // User的POJO類(lèi) class User { private Integer id; private String name; // getters and setters }
這里的@Select
注解是自定義的,很簡(jiǎn)單就不展示了。
編寫(xiě)InvocationHandler
寫(xiě)一個(gè)InvocationHandler
,它會(huì)在運(yùn)行時(shí)解析@Select這些注解,并執(zhí)行相應(yīng)的SQL操作。簡(jiǎn)化版只模擬解析和調(diào)用的過(guò)程。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyBatisInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 這里假設(shè)能夠獲取到注解并解析它 // 實(shí)際上,你需要一個(gè)SQL執(zhí)行器來(lái)執(zhí)行這里定義的SQL System.out.println("Executing method: " + method.getName()); System.out.println("Parameters: " + Arrays.toString(args)); // 模擬的SQL執(zhí)行結(jié)果 User user = new User(); user.setId(1); user.setName((String) args[0]); return user; } }
生成代理對(duì)象并調(diào)用
最后,我們生成UserMapper
接口的代理對(duì)象,并調(diào)用它的方法。
import java.lang.reflect.Proxy; public class MyBatisProxyDemo { public static void main(String[] args) { MyBatisInvocationHandler handler = new MyBatisInvocationHandler(); UserMapper mapper = (UserMapper) Proxy.newProxyInstance( UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, handler ); User user = mapper.findUserByName("Alice"); System.out.println("Found user: " + user.getName()); } }
輸出結(jié)果
Executing method: findUserByName
Parameters: [Alice]
Found user: Alice
到此這篇關(guān)于Java JDK動(dòng)態(tài)代理在攔截器和聲明式接口中的應(yīng)用小結(jié)的文章就介紹到這了,更多相關(guān)JDK動(dòng)態(tài)代理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+JWT實(shí)現(xiàn)注冊(cè)、登錄、狀態(tài)續(xù)簽流程分析
這篇文章主要介紹了SpringBoot+JWT實(shí)現(xiàn)注冊(cè)、登錄、狀態(tài)續(xù)簽【登錄保持】,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06SpringBoot配置MySQL5.7與MySQL8.0的異同點(diǎn)詳解
MySQL 是 Java 開(kāi)發(fā)中最常用的數(shù)據(jù)庫(kù)之一,而 Spring Boot 提供了便捷的配置方式,隨著 MySQL 8.0 的普及,許多開(kāi)發(fā)者需要從 MySQL 5.7 升級(jí)到 8.0,在實(shí)際開(kāi)發(fā)中,二者的配置方式既有相似之處,也有一些需要特別注意的不同點(diǎn),所以本文給大家詳細(xì)介紹了它們的異同點(diǎn)2024-12-12詳解java 三種調(diào)用機(jī)制(同步、回調(diào)、異步)
這篇文章主要介紹了java 三種調(diào)用機(jī)制(同步、回調(diào)、異步),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Spring的@Value如何從Nacos配置中心獲取值并自動(dòng)刷新
這篇文章主要介紹了Spring的@Value如何從Nacos配置中心獲取值并自動(dòng)刷新,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07Spring Boot開(kāi)啟遠(yuǎn)程調(diào)試的方法
這篇文章主要介紹了Spring Boot開(kāi)啟遠(yuǎn)程調(diào)試的方法,幫助大家更好的理解和使用Spring Boot框架,感興趣的朋友可以了解下2020-10-10Java中Stringbuilder和正則表達(dá)式示例詳解
Java語(yǔ)言為字符串連接運(yùn)算符(+)提供特殊支持,并為其他對(duì)象轉(zhuǎn)換為字符串,字符串連接是通過(guò)StringBuilder(或StringBuffer)類(lèi)及其append方法實(shí)現(xiàn)的,這篇文章主要給大家介紹了關(guān)于Java中Stringbuilder和正則表達(dá)式的相關(guān)資料,需要的朋友可以參考下2024-02-02Java commons-httpclient如果實(shí)現(xiàn)get及post請(qǐng)求
這篇文章主要介紹了Java commons-httpclient如果實(shí)現(xiàn)get及post請(qǐng)求,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09