java动态代理_Java 动态代理和依赖注入
【注】本文譯自:https://psamsotha.github.io/jersey/2015/12/16/dynamic-proxies-dependency-injection.html
本文將討論動態(tài)代理如何在 Java 平臺中工作以及依賴注入如何利用此 Java 功能。本文的撰寫源于我在 HK2 框架(或更確切地說是 Jersey 應(yīng)用程序中的 HK2)中嘗試將請求范圍對象注入到單例對象中的的搜索。我本來打算將我的發(fā)現(xiàn)都寫在一個博客里,但是我覺得這個主題太寬泛了,不適合用兩行代碼就能解決的問題。
首先,我將快速討論代理模式,然后展示如何在 Java 語言中使用動態(tài)代理,最后介紹一個使用動態(tài)代理和自定義依賴項(xiàng)注入的示例。
代理模式
我不會過多介紹代理模式。在互聯(lián)網(wǎng)上到處都有很好的參考。我只給出一個簡短的類比,以及該模式的一些簡短代碼示例。
我敢肯定,你們大多數(shù)人都聽過“委托投票”這個說法。當(dāng)有人投票給其他人時。例如,說公司董事會成員之間有一些隨意的投票。成員 B 生病住院了,因此無法參加董事會會議。所以,成員 A 代表成員 B 進(jìn)行了投票。因此,在表決會議上,成員 A 只是成員 B 的委托。
代理模式的工作原理與此相同。這是一個類圖(來自維基百科)。
假設(shè) Member 是接口
public?interface?Member?{????void?vote();}那么你有 MemberA 和 MemberB
public?class?MemberA?implements?Member?{????public?void?vote()?{}}public?class?MemberB?implements?Member?{????public?void?vote()?{}}由于成員 B 將不存在,因此我們需要一個代理。 代理還應(yīng)實(shí)現(xiàn) Member 接口,并應(yīng)包含對 MemberB 的引用。
public?class?MemberBProxy?implements?Member?{????private?MemberB?memberB;????public?void?vote?{????????memberB.vote();????}}現(xiàn)在 MemberA 可以讓 a 成為成員 B 的代理,讓代理為成員 B 進(jìn)行代理投票。
這可能不是最好的例子,因?yàn)榇韮H對成員 B 進(jìn)行表決。但是對于真正的代理,通常情況下還會發(fā)生其他事情。例如,在使用遠(yuǎn)程代理的情況下, vote() 方法實(shí)際上可能會對遠(yuǎn)程 MemberB 進(jìn)行網(wǎng)絡(luò)調(diào)用。Java平臺中的一個示例就是 RMI(遠(yuǎn)程方法調(diào)用)。后面的示例將描述另一個用例,通常對于開發(fā)人員來說是透明的。
動態(tài)代理
在上面的示例中,我們終須手動編寫代理類。但是,在 Java 中,隨著 1.3 中引入動態(tài)代理,這不是必需的。動態(tài)代理的核心接口是 java.lang.reflect.Proxy。要使用它,我們需要組件,我們的代理接口和一個 InvocationHandler。 讓我們看一個簡單的例子,使用與上面相同的類比。
Member?memberBProxy?=?(Memeber)?Proxy.newProxyInstance(????????Memeber.class.getClassLoader(),????????new?Class[]?{?Member.class?},????????new?InvocationHandler()?{????????????@Override????????????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?{????????????????return?method.invoke(new?MemberB(),?args);????????????}????????});就是這樣。現(xiàn)在 memberBProxy 是 Proxy 的一個實(shí)例,而不是 MemberB 的實(shí)例。如果你打印出 Member 對象的類名,實(shí)際上會看到類名是 com.sun.proxy.ProxyX,而不是 MemberB。
讓我們快速地瀏覽一下。這是 Proxy#newProxyInstance 的簽名
newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)它首先需要 ClassLoader 用于定義代理,其次需要一個實(shí)現(xiàn)的接口,最后是 InvocationHandler。InvocationHandler 只有一個需要實(shí)現(xiàn)的 callback 方法。
Object invoke(Object proxy, Method method, Object[] args)第一個參數(shù)是實(shí)際的代理對象。你應(yīng)該很少使用它。第二個參數(shù)是 java.lang.reflect.Method 。如果你有 Java 反射的經(jīng)驗(yàn),那么應(yīng)該熟悉這個接口。 使用 Method 時,我們可以通過傳遞要調(diào)用該方法的對象以及任何參數(shù)(最后一行)來調(diào)用:
return method.invoke(new MemberB(), args);在這里,代理將方法和傳遞給代理之上的方法調(diào)用的參數(shù)傳遞給代理。作為 InvocationHandler 的實(shí)現(xiàn)者,我們可以使用 Method 和method 參數(shù)做我們想做的任何事情。這里我們只是簡單地,應(yīng)該在 new MemberB() 對象中調(diào)用,并傳遞參數(shù)。
為了獲得更清楚地了解情況,只需將其看作 Proxy 實(shí)例具有 Member 接口具有的所有方法。因此,當(dāng)我們調(diào)用 Proxy#vote() 時,它會調(diào)用 InvocationHandler#invoke 本身、方法和傳遞給 vote() 的參數(shù)(如果有)。通過調(diào)用 Method 對象上的 inovoke,InvocationHandler 實(shí)現(xiàn)只需調(diào)用該方法即可。然后,Method 對象將對實(shí)際的 MemberB 對象調(diào)用 vote()。
就是這樣。如你所見,動態(tài)代理很容易實(shí)現(xiàn)。
動態(tài)代理和自定義注入示例
- 獲取 GitHub 項(xiàng)目(https://github.com/psamsotha/dynamic-proxies-example)
我將在這里嘗試解釋的是在依賴項(xiàng)注入(DI)框架中如何使用動態(tài)代理。DI 中動態(tài)代理的主要用例之一是處理范圍。例如,您有一個處于單例作用域中的服務(wù)或控制器,這意味著每個應(yīng)用僅創(chuàng)建一個。該單例服務(wù)依賴于請求范圍內(nèi)的服務(wù)器,這意味著應(yīng)為每個請求創(chuàng)建一個服務(wù)器。類可能看起來像這樣(這完全是虛構(gòu)的---沒有特定的框架)。
@Controller(scope?=?"singleton")public?class?AppController?{????@Inject????SingletonService?service;}@Singletonpublic?class?SingletonService?{????@Inject????RequestScopedService?service;}@RequestScopedpublic?class?RequestScopedService?{}這里的問題是,在啟動時創(chuàng)建 SingletonService 時,需要執(zhí)行所有注入。但是在啟動時沒有請求,因此當(dāng)前應(yīng)該沒有綁定到請求的 RequestScopedService。另一個問題是我們?nèi)绾喂芾砟膫€請求獲取哪個 RequestScopedService。也許我們可以在 SingletonService 中添加一個 setter,在其中我們可以為每個請求設(shè)置一個新的 RequestScopedService。但這是行不通的,因?yàn)?SingletonService 將被并發(fā)訪問,就像一些服務(wù)器的工作方式一樣(每個請求一個線程)。
這是動態(tài)代理發(fā)揮作用的地方。This is where dynamic proxies come to the rescue. When the當(dāng)啟動創(chuàng)建 SingletonService 時我們將注入服務(wù)的 Proxy,而不是注入實(shí)際的 RequestScopedService。當(dāng)從 from inside the SingletonService, 內(nèi)部對 RequestScopedService 進(jìn)行調(diào)用時,實(shí)際上將在 Proxy 上進(jìn)行調(diào)用,該代理將調(diào)用委派給 InvocationHandler#invoke 方法,該方法實(shí)現(xiàn)對從 ThreadLocal 獲得的 RequestScopedService 的調(diào)用。每次處理一個請求時,都會在 ThreadLocal 中設(shè)置一個新的 RequestScopedService,這個請求將在一個單獨(dú)的線程中處理。如果你聽說過 “線程本地代理”一詞,那么這幾乎就是它的工作原理。
讓我們嘗試實(shí)現(xiàn)這些。我們甚至將實(shí)現(xiàn)自己的依賴注入。我們將實(shí)現(xiàn)一個簡單的服務(wù)器框架,該框架允許用戶實(shí)現(xiàn)一個自定義 RequestHandler ,它可以注入我們的 SingletonService ,而 SingletonService 又依賴于 RequestScopedService。以下是類圖。(接下來,最好從上面的鏈接中獲取 GitHub 項(xiàng)目)。
如前所述,用戶將能夠?qū)崿F(xiàn)自定義 RequestHandler 并注入我們的 SingletonService。在項(xiàng)目中,有一個默認(rèn)的實(shí)現(xiàn),它僅只返回來自 SingletonService 的消息作為 Response。
public?class?DefaultRequestHandler?implements?RequestHandler?{????????@Inject????private?SingletonService?singletonService;????@Override????public?Response?handleRequest(Request?request)?{????????return?new?Response(singletonService.getMessage());????}??}然后用戶創(chuàng)建 Server 傳入實(shí)現(xiàn)類給構(gòu)造器。
Server server = new Server(DefaultRequestHandler.class);
在服務(wù)器構(gòu)造函數(shù)中,您將看到兩件事,用戶定義的 RequestHandler 類驗(yàn)證,以及創(chuàng)建 SingletonService。驗(yàn)證在這里并不重要,這是 SingletonService 的創(chuàng)建。
private?static?SingletonService?initSingletonService()?{????Service?proxyService?=?(Service)?Proxy.newProxyInstance(????????????Service.class.getClassLoader(),?new?Class[]{Service.class},????????????new?ServiceInvocationHandler());????return?new?SingletonService(proxyService);}我們要做的第一件事是創(chuàng)建 Service 類的代理。這里是 ServiceInvocationHandler
public?class?ServiceInvocationHandler?implements?InvocationHandler?{????@Override????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?..?{????????Service?service?=?ThreadLocalService.get();????????return?method.invoke(service,?args);????}}它沒有太大作用。它只是從 ThreadLocalService 檢索 Service ,并在服務(wù)上調(diào)用代理方法。稍后我們將看到,RequestScopedService 的實(shí)例被設(shè)置為 ThreadLocal。
然后,使用 SingletonService 創(chuàng)建 Service。所以現(xiàn)在,當(dāng) SingletonService調(diào)用 Service 上的方法時,代理將查找線程本地 Service 并將調(diào)用委托給其方法。
服務(wù)器引導(dǎo)程序就是這樣。現(xiàn)在我們進(jìn)入運(yùn)行時和請求處理。下面是來自請求處理流的序列圖。
首先,Main 程序調(diào)用 Server#sendRequest(Request) 并傳入一個新的 Request 對象。 Request 對象僅具有客戶端的名稱。
當(dāng)我們在 Server 上調(diào)用 sendRequest 時,它所做的就是將請求添加到 BlockingQueue。
public?void?sendRequest(Request?request)?{????try?{????????requests.put(request);????}?catch?(InterruptedException?ex)?{????????throw?new?RuntimeException(ex);????}}服務(wù)器啟動時,它會不斷輪詢 BlockingQueue,等待新的 Request
public?void?startServer()?{????executors.submit(new?Runnable()?{????????@Override????????public?void?run()?{????????????while?(true)?{????????????????try?{????????????????????Request?request?=?requests.take();????????????????????if?(request.isShutdownTrigger())?{????????????????????????break;????????????????????}????????????????????executors.submit(new?RequestProcessor(userDefineHandler,??????????????????????????????????????????????????????????singletonService,??????????????????????????????????????????????????????????request));????????????????}?catch?(InterruptedException?ex)?{????????????????????throw?new?RuntimeException(ex);????????????????}????????????}????????????System.out.println("Server?shutdown!");????????}????});??}當(dāng)接收到 Request 時,Server 將創(chuàng)建一個新的 RequestProcessor,傳入 Request 對象、SingletonService 對象和使用定義的 RequestHandler 類。如果你查看 RequestProcessor 的 run() 方法,你將看到以下內(nèi)容
private?void?setThreadLocalService()?{????ThreadLocalService.set(new?RequestScopedService(request));}@Overridepublic?void?run()?{????setThreadLocalService();????RequestHandler?handler?=?initInjections();????Response?response?=?handler.handleRequest(request);????System.out.println(response.getMessage());}因此,處理器要做的第一件事就是將 RequestScopedService 設(shè)置為 ThreadLocalService。 然后使用一些反射實(shí)例化 RequestHandler ,如 initInjections() 方法所示
RequestHandler?initInjections()?{????try?{????????for?(Field?field?:?handlerCls.getDeclaredFields())?{????????????if?(field.isAnnotationPresent(Inject.class)????????????????????&&?field.getType()?==?SingletonService.class)?{????????????????return?createHandler(field,?handlerCls);????????????}????????}????????Constructor[]?cons?=?handlerCls.getConstructors();????????for?(Constructor?con?:?cons)?{????????????if?(con.isAnnotationPresent(Inject.class)????????????????????&&?con.getParameterCount()?==?1????????????????????&&?con.getParameterTypes()[0]?==?SingletonService.class)?{????????????????return?(RequestHandler)?con.newInstance(singletonService);????????????}????????}????}?catch?(Exception?ex)?{????????throw?new?RuntimeException(ex);????}????throw?new?RuntimeException("RequestHandler?could?not?be?created.");}該方法只是檢查我們是否應(yīng)該進(jìn)行字段注入或構(gòu)造函數(shù)注入。它確保使用 @Inject 注解字段或構(gòu)造函數(shù)。如果字段帶有注解,并且字段類型為 SingletonService,我們將使用反射使用 SingletonService 設(shè)置字段。進(jìn)程構(gòu)造函數(shù)注入也會發(fā)生類似事件。
RequestHander 做的最后一件事就是簡單地調(diào)用 RequestHandler 的 handleRequestof ,它返回一個 Response,然后處理器打印出 Response 消息。這就是處理單個請求的過程。
如果運(yùn)行 Main 類,應(yīng)該會看到類似以下內(nèi)容的信息
Message: Hello Kobe??meta-info:????service class: com.sun.proxy.$Proxy2????service id: 1????thread name: pool-1-thread-2Message: Hello Lebron??meta-info:????service class: com.sun.proxy.$Proxy2????service id: 2????thread name: pool-1-thread-3... three more您應(yīng)該注意的第一件事是,服務(wù)類確實(shí)是 Proxy 實(shí)例,而不是 RequestScopedService。只要正在處理請求,底層的 RequestScopedService 都將保持不變。因此所有在 Service 內(nèi)部的服務(wù)上所做的將總是被委托給與特定線程相關(guān)聯(lián)的實(shí)際 RequestScopedService 。
就這樣了。
總結(jié)
我們介紹了代理模式的一些基礎(chǔ)知識,并了解了它如何使用包裝器或委托模型來調(diào)用底層對象。然后,我們討論了動態(tài)代理,以及如何用 Java 語言實(shí)現(xiàn)它。最后,我們通過一個示例說明了動態(tài)代理是如何使用作用域依賴注入的。如果需要將較小作用域的對象注入到更大作用域的對象中,則需要代理較小作用域的對象,以便不同的線程能夠訪問自己的較小作用域的對象實(shí)例。
這個示例與你在現(xiàn)實(shí)生活中使用的一些東西相去甚遠(yuǎn),但我希望你能更好地理解我們討論的兩個主題(動態(tài)代理和依賴注入)的組全背后發(fā)生的事情。
總結(jié)
以上是生活随笔為你收集整理的java动态代理_Java 动态代理和依赖注入的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云丁密码锁使用说明书_郑州智能密码锁多少
- 下一篇: zemax操作数_ZEMAX与像差理论: