Spring陷阱:代理

作為Spring框架的用戶和發燒友多年,我遇到了一些關于此堆棧的誤解和問題。 另外,在某些地方抽象非常可怕地泄漏,以便有效,安全地利用開發人員需要意識到的所有功能。 這就是為什么我開始Spring陷阱系列的原因。 在第一部分中,我們將仔細研究代理的工作原理。

Bean代理是Spring提供的基本功能,也是最重要的基礎結構功能之一。 它是如此重要和低級,以至于在大多數情況下我們甚至都沒有意識到它的存在。 但是,如果沒有交易,面向方面的編程,高級作用域, @ Async支持以及其他各種國內用例,將無法實現。 那么什么是代理 ?

這是一個示例:將DAO注入服務時,Spring將獲取DAO實例并將其直接注入。 而已。 但是有時候,Spring需要了解服務(以及任何其他bean)對DAO的每一次調用。 例如,如果DAO被標記為事務性的,則它需要在調用并提交之前啟動事務,或者在之后回滾。 當然,您可以手動執行此操作,但是這很繁瑣,容易出錯,并且混雜了許多問題。 這就是為什么我們首先使用聲明式事務的原因。

那么Spring如何實現這種攔截機制呢? 從最簡單到最高級的三種方法。 我不會討論它們的優缺點,我們將在一個具體示例中很快看到它們。

Java動態代理

最簡單的解決方案。 如果DAO實現了任何接口,Spring將創建一個Java 動態代理來實現該接口,并注入它而不是真實的類。 真正的代理仍然存在,并且代理引用了它,但是對于外部世界–代理就是bean。 現在,每次您在DAO上調用方法時,Spring都可以攔截它們,添加一些AOP魔術并調用原始方法。

CGLIB生成的類

Java動態代理的缺點是要求Bean至少實現一個接口。 CGLIB通過動態子類化原始bean并通過覆蓋每種可能的方法直接添加攔截邏輯來解決此限制。 可以將其視為原始類的子類并在其中調用超級版本:

class DAO {def findBy(id: Int) = //...
}class DAO$EnhancerByCGLIB extends DAO {override def findBy(id: Int) = {startTransactiontry {val result = super.findBy(id)commitTransaction()result} catch {case e =>rollbackTransaction()throw e}}
}

但是,此偽代碼并未說明其在現實中的工作方式-引入了另一個問題,請繼續關注。

AspectJ編織

從開發人員的角度來看,這是最具侵入性但也是最可靠和直觀的解決方案。 在這種模式下,偵聽直接應用于您的類字節碼,這意味著您的JVM運行的類與您編寫的類不同。 在構建–編譯時編織(CTW)或在加載類–加載時間編織(LTW)期間,AspectJ weaver通過直接修改類的字節碼來添加攔截邏輯。

如果您對如何在后臺實現AspectJ魔術感到好奇,則可以使用預先通過AspectJ編織編譯的,經過反編譯和簡化的.class文件:

public void inInterfaceTransactional()
{try{AnnotationTransactionAspect.aspectOf().ajc$before$1$2a73e96c(this, ajc$tjp_2);throwIfNotInTransaction();}catch(Throwable throwable){AnnotationTransactionAspect.aspectOf().ajc$afterThrowing$2$2a73e96c(this, throwable);throw throwable;}AnnotationTransactionAspect.aspectOf().ajc$afterReturning$3$2a73e96c(this);
}

使用加載時編織,在加載類時,將在運行時發生相同的轉換。 如您所見,這里沒有任何干擾,實際上,這正是您手動編程事務的方式。 旁注:您還記得病毒在操作系統將可執行文件加載后將其代碼附加到可執行文件中或動態注入自身的時候嗎?

了解代理技術對于了解代理如何工作以及如何影響代碼非常重要。 讓我們堅持聲明式事務劃分示例,這是我們的戰場:

trait FooService {def inInterfaceTransactional()def inInterfaceNotTransactional();
}@Service
class DefaultFooService extends FooService {private def throwIfNotInTransaction() {assume(TransactionSynchronizationManager.isActualTransactionActive)}def publicNotInInterfaceAndNotTransactional() {inInterfaceTransactional()publicNotInInterfaceButTransactional()privateMethod();}@Transactionaldef publicNotInInterfaceButTransactional() {throwIfNotInTransaction()}@Transactionalprivate def privateMethod() {throwIfNotInTransaction()}@Transactionaloverride def inInterfaceTransactional() {throwIfNotInTransaction()}override def inInterfaceNotTransactional() {inInterfaceTransactional()publicNotInInterfaceButTransactional()privateMethod();}
}

方便的throwIfNotInTransaction()方法…如果未在事務中調用,則引發異常。 誰曾想到? 從不同的地方和不同的配置調用此方法。 如果您仔細檢查方法的調用方式,那么所有方法都應該起作用。 但是,我們的開發人員的生活往往是殘酷的。 第一個障礙是意外的: ScalaTest不支持通過專用運行器進行 Spring集成測試 。 幸運的是,這可以通過簡單的特征輕松地移植(處理依賴注入以測試用例和應用程序上下文緩存):

trait SpringRule extends AbstractSuite { this: Suite =>abstract override def run(testName: Option[String], reporter: Reporter, stopper: Stopper, filter: Filter, configMap: Map[String, Any], distributor: Option[Distributor], tracker: Tracker) {new TestContextManager(this.getClass).prepareTestInstance(this)super.run(testName, reporter, stopper, filter, configMap, distributor, tracker)}}

請注意,我們不會像原始測試框架那樣開始和回退事務。 不僅因為它會干擾我們的演示,而且因為我發現事務測試有害–將來還會對此產生更多影響。 回到我們的示例,這是一個煙霧測試。 完整的源代碼可以在這里從proxy-problem分支下載。 不要抱怨缺少斷言–在這里,我們僅測試未引發異常:

@RunWith(classOf[JUnitRunner])
@ContextConfiguration
class DefaultFooServiceTest extends FunSuite with ShouldMatchers with SpringRule{@Resourceprivate val fooService: FooService = nulltest("calling method from interface should apply transactional aspect") {fooService.inInterfaceTransactional()}test("calling non-transactional method from interface should start transaction for all called methods") {fooService.inInterfaceNotTransactional()}}

令人驚訝的是,測試失敗。 好吧,如果您閱讀我的文章已有一段時間了,您應該不會感到驚訝: Spring AOP謎語和Spring AOP謎語揭開了神秘面紗 。 實際上,Spring參考文檔對此進行了詳細解釋,也請查看此SO問題 。 簡而言之,非事務方法調用事務方法,但繞過事務代理。 即使似乎很明顯,當inInterfaceNotTransactional()調用inInterfaceTransactional()時,事務也應該開始,但事實并非如此。 抽象泄漏。 順便說一句,還請查看引人入勝的交易策略:了解交易陷阱的更多信息。

還記得我們展示CGLIB工作原理的例子嗎? 也知道多態性是如何工作的,似乎使用基于類的代理應該會有所幫助。 現在,inInterfaceNotTransactional()調用被CGLIB / Spring覆蓋的inInterfaceTransactional(),后者依次調用原始類。 沒有機會! 這是偽代碼的真正實現:

class DAO$EnhancerByCGLIB extends DAO {val target: DAO = ...override def findBy(id: Int) = {startTransactiontry {val result = target.findBy(id)commitTransaction()result} catch {case e =>rollbackTransaction()throw e}}
}

Spring首先創建原始bean,然后創建一個將原始bean(某種Decorator模式)包裝在一個后處理器中的子類,而不是對其進行實例化和實例化。 再次,這意味著bean內部的self調用會繞過我們的類的AOP代理。 當然,使用CGLIB會改變bean的行為方式。 例如,我們現在可以注入具體的類而不是接口,實際上甚至不需要接口,并且在這種情況下需要CGLIB代理。 還有一些缺點–不再可以進行構造函數注入,請參閱SPR-3150 ,這是一個遺憾 。 那么,一些更徹底的測試呢?

@RunWith(classOf[JUnitRunner])
@ContextConfiguration
class DefaultFooServiceTest extends FunSuite with ShouldMatchers with SpringRule {@Resourceprivate val fooService: DefaultFooService = nulltest("calling method from interface should apply transactional aspect") {fooService.inInterfaceTransactional()}test("calling non-transactional method from interface should start transaction for all called methods") {fooService.inInterfaceNotTransactional()}test("calling transactional method not belonging to interface should start transaction for all called methods") {fooService.publicNotInInterfaceButTransactional()}test("calling non-transactional method not belonging to interface should start transaction for all called methods") {fooService.publicNotInInterfaceAndNotTransactional()}}

請選擇將失敗的測試(準確選擇兩個)。 你能解釋為什么嗎? 同樣,常識表明一切都應該通過,但事實并非如此。 您可以自己玩耍,請參閱基于類的代理分支。

我們不是在這里揭露問題,而是要克服它們。 不幸的是,我們糾纏不清的服務等級只能使用重型火炮來解決-真正的AspectJ編織。 編譯和加載時編織均使測試通過。 相應地請參見aspectj-ctw和aspectj-ltw分支。

您現在應該問自己幾個問題。 我應該采取哪種方法(或:我真的需要使用AspectJ?),為什么還要打擾? - 在其他人中。 我會說–在大多數情況下,簡單的Spring代理就足夠了。 但是,您絕對必須知道傳播是如何工作的,何時不起作用。 否則會發生壞事。 提交和回滾發生在意外的地方,跨越了意外的數據量,ORM 臟檢查不起作用,看不見的記錄–相信,這種事情是瘋狂發生的。 請記住,我們在這里介紹的主題不僅適用于交易,還適用于AOP的所有方面。

參考: Spring陷阱: NoBlogDefFound博客上的 JCG合作伙伴 Tomasz Nurkiewicz的代理 。

相關文章 :
  • Spring聲明式事務示例
  • Spring依賴注入技術的發展
  • Spring和AspectJ的領域驅動設計
  • Spring 3使用JUnit 4進行測試– ContextConfiguration和AbstractTransactionalJUnit4SpringContextTests
  • 使用Spring AOP進行面向方面的編程
  • Java教程和Android教程列表

翻譯自: https://www.javacodegeeks.com/2011/11/spring-pitfalls-proxying.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/373867.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/373867.shtml
英文地址,請注明出處:http://en.pswp.cn/news/373867.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

UVa11925 Generating Premutations

留坑(p.254) 1 #include<cstdio>2 #include<cstring>3 #include<cstdlib>4 #include<algorithm>5 #include<iostream>6 7 using namespace std;8 9 void setIO(const string& s) { 10 freopen((s ".in").c_str(), "r&qu…

xamarin UWP中MessageDialog與ContentDialog的區別

MessageDialog與ContentDialog的異同點解析&#xff1a; 相同點一&#xff1a;都是uwp應用上的一個彈窗控件。都能做為彈出應用。 相異點一&#xff1a;所在命名空間不同&#xff0c;MessageDialog在Windows.UI.Popups.MessageDialog下&#xff0c;而ContentDialog在Windows.UI…

python篩選大量數據_python(數據篩選)

在Python3中&#xff1a;(1)xrange的功能合并到range里面&#xff0c;xrange已經不存在 -> range和xrange用法(2)filter已經不能返回一個list&#xff0c;而是只能返回一個迭代對象&#xff0c;需要套在一個list()里面&#xff0c;且&#xff0c;需要注意的是&#xff0c;fi…

ORA-12514: TNS: 監聽程序當前無法識別連接描述符中請求的服務

不指定數據庫可以正常連接&#xff1a; 指定數據庫和使用PL/SQL Developer都出現錯誤&#xff1a; 在此說明一下我的環境&#xff1a;Oralce裝的是64位的在使用PL/SQL Developer時曾出現過初始化錯誤&#xff0c;解決辦法就是下載oracle 32位客戶端并相應的配置。 解決方案一&a…

Devoxx 2011印象

Devoxx 2011結束了&#xff0c;它很棒。 最終&#xff0c;在不得不與妻子和孩子度過周末之后&#xff08;上個星期我很少見過&#xff09;&#xff0c;我找到了寫下一些東西的時間。 對我來說&#xff0c;這是第六個Devoxx&#xff0c;我的第一個是2006年-那時我還是一個學生&a…

Ubuntu14.04.3,apt-get出現dpkg: error processing package xxx (--configure)和cups-daemon錯誤的解決方案...

Ubuntu14.04.3&#xff0c;使用apt-get安裝軟件的時候&#xff0c;報個莫名其妙的錯誤&#xff1a; dpkg: error processing package xxx (--configure): balabala...Errors were encountered while processing: cups-daemon cups-core-drivers cups E: Sub-process /usr/bin/d…

實驗三 類的繼承和多態性

實驗三 類的繼承和多態性 1.(1)編寫一個接口ShapePara&#xff0c;要求&#xff1a; 接口中的方法&#xff1a; int getArea()&#xff1a;獲得圖形的面積。int getCircumference()&#xff1a;獲得圖形的周長 (2)編寫一個圓類Circle&#xff0c;要求&#xff1a;圓類Circle實現…

ORA-01843:無效的月份

Oracle數據庫默認情況下&#xff0c;會以DD-MON-YY的形式顯示日期&#xff0c;其中DD是天數&#xff0c;MON是月份的前三個字母&#xff08;大寫&#xff09;&#xff0c;而YY是年份的最后兩位。數據庫實際上會為年份存儲4位數字&#xff0c;但是默認情況下只會顯示最后兩位。 …

貪心策略取得最優解的條件_什么是貪心算法?

一、什么是貪心算法貪心算法是指&#xff0c;在對問題求解時&#xff0c;總是做出在當前看來是最好的選擇。(局部最優解&#xff0c;而不是整體最優解)貪心算法沒有固定的算法框架&#xff0c;算法設計的關鍵是貪心策略的選擇。必須注意的是&#xff0c;貪心算法不是對所有問題…

Devoxx第1天

參加Devoxx給我帶來了足夠的動力來發布我的第一篇博客文章。 我是第一次來這里&#xff0c;它的組織方式給我留下了深刻的印象。 目前有記錄的最高發言人。 對我來說&#xff0c;選擇演示文稿來參加是一個問題。 但是感謝組織者&#xff0c;所有活動都將在12月下旬在parleys.co…

Oracle 事務的開始與結束

事務是用來分割數據庫活動的邏輯工作單元&#xff0c;事務即有起點&#xff0c;也有終點&#xff1b; 事物的處理就是保證數據操作的完整性&#xff0c;所有的操作要么成功要么同時失敗。當下列事件之一發生時&#xff0c;事務就開始了&#xff1a;連接到數據庫上&#xff0c;并…

http tcp聯系區別

術語TCP/IP代表傳輸控制協議/網際協議&#xff0c;指的是一系列協議。“IP”代表網際協議&#xff0c;TCP和UDP使用該協議從一個網絡傳送數據包到另一個網絡。把IP想像成一種高速公路&#xff0c;它允許其它協議在上面行駛并找到到其它電腦的出口。TCP和UDP是高速公路上的“卡車…

python控件隨窗口變化而適配_Tkinter窗口/控件比例調整

我目前正在為一個編程類開發一個pythongui版本的Reversi。我已經對游戲邏輯進行了編程&#xff0c;目前我正在嘗試使用Tkinter實現GUI。我有一些問題&#xff0c;調整游戲板(根窗口)和它的一切(畫布和形狀)成比例。這款游戲目前還不錯&#xff0c;但我試圖讓棋盤正確調整大小的…

Java遞歸基礎

對于那些不知道遞歸是什么的人&#xff08;并且像個笑聲一樣&#xff09;&#xff0c;請單擊以下鏈接&#xff1a;Google搜索&#xff1a;遞歸&#xff0c;然后單擊“您的意思是……”項。 希望您終于弄清楚了遞歸是指其自身的任何內容&#xff08;如果不是&#xff0c;那么您可…

我是最棒的,我一定會成功!

有人曾經做過這樣一個實驗&#xff1a;他往一個玻璃杯里放進一只跳蚤&#xff0c;發現跳蚤立即輕易地跳了出來。再重復幾遍&#xff0c;結果還是一樣。根據測試&#xff0c;跳蚤跳的高度一般可達它身體的400倍左右&#xff0c;所以說跳蚤可以稱得上是動物界的跳高冠軍。     …

頭部ct能檢查出什么_【安全用藥】做CT檢查時應注意什么?

點擊藍字 關注我們安安徽徽&#xff0c;你知道做CT檢查時應注意什么&#xff1f;上腹部CT檢查前患者至少禁食6小時、檢查前15分鐘喝溫開水充盈胃部、CT檢查時&#xff0c;患者會受到一定量X射線輻射&#xff0c;應避免過度掃描......本期安全用藥&#xff0c;大家一起來了解了解…

JAXB,SAX,DOM性能

這篇文章探討了使用多種不同方法將XML文檔編組為Java對象的性能。 XML文檔非常簡單。 它包含一個Person實體的集合。 <?xml version"1.0" encoding"UTF-8" standalone"yes"?> <persons><person><id>person0</id>…

虛擬機Linux圖形界面配置NAT-橋接

點開“虛擬機->設置->橋接模式&#xff08;勾選復制物理網絡連接狀態&#xff09;->確認” 點擊“右上角扇形網絡圖標->Edit Connections->Wired->選中->Delete->Add->IPv4 Settings->Method(Manual)->Add->輸入IP&#xff0c;子網掩碼&am…

年輕人應該謹記的十點

有個朋友的孩子今年大學畢業&#xff0c;托我幫他找個“好工作”&#xff0c;而且再三強調&#xff0c;這關系到孩子的前途命運&#xff0c;要我一定要全力以赴。他&#xff0c;一個非名牌大學的計算機網絡專業應屆畢業生&#xff0c;沒有工作經驗&#xff0c;他能找一個什么樣…

python自動化構建工具_Python自動化構建工具scons使用入門筆記

這段時間用到了scons&#xff0c;這里總結下&#xff0c;也方便我以后查閱。一、安裝sconsLinux環境(以CentOS為例)1、yum安裝yum install scons2、源碼安裝下載scons&#xff1a;http://http://jaist.dl.sourceforge.net/project/scons/scons/2.3.0/scons-2.3.0.zip安裝scons&…