javascript
Spring和AspectJ的领域驱动设计
讓我們看看他怎么說:
(注意:對(duì)原始帖子進(jìn)行了少量編輯以提高可讀性)
在開始討論我們的主要主題之前,我希望您先想一想關(guān)于您可以想象的最佳Java EE應(yīng)用程序設(shè)計(jì)。 不管使用Spring還是EJB3,都沒有關(guān)系,因?yàn)樗鼈兎浅O嗨?#xff0c;可能您會(huì)建議一種類似于以下的方法。 從后端開始,您有:
- 域?qū)ο?/strong> ,它們是直接映射到數(shù)據(jù)庫關(guān)系的簡(jiǎn)單POJO。 POJO很棒,因?yàn)樵S多框架都很好地理解了JavaBean樣式的屬性。
- 數(shù)據(jù)訪問層 –通常為無狀態(tài)服務(wù),它們包裝數(shù)據(jù)庫訪問代碼(JDBC,Hibernate,JPA,iBatis或您想要的任何東西),以隱藏其復(fù)雜性并提供一定程度的(泄漏)抽象。 DAO之所以出色,是因?yàn)樗鼈冸[藏了令人討厭且笨拙的JDBC邏輯(這就是為什么有人質(zhì)疑使用JPA時(shí)對(duì)DAO的需求),它充當(dāng)數(shù)據(jù)庫和對(duì)象之間的或多或少的翻譯器。
- 業(yè)務(wù)服務(wù)層 –在域?qū)ο笊线\(yùn)行的另一組無狀態(tài)服務(wù)。 典型的設(shè)計(jì)引入了一個(gè)對(duì)象圖,這些對(duì)象采用或返回域?qū)ο蟛?duì)其執(zhí)行一些邏輯,同樣,通常通過數(shù)據(jù)訪問層訪問數(shù)據(jù)庫。 服務(wù)層很棒,因?yàn)樗鼘W⒂跇I(yè)務(wù)邏輯,將技術(shù)細(xì)節(jié)委托給DAO層。
- 用戶界面 –如今,通常是通過網(wǎng)絡(luò)瀏覽器。 用戶界面之所以如此出色是因?yàn)椤聦?shí)如此。
美麗,不是嗎? 現(xiàn)在睜開眼睛,是時(shí)候洗個(gè)冷水了。
服務(wù)和DAO都是無狀態(tài)的,因?yàn)镾pring和EJB3偏愛此類,因此我們學(xué)會(huì)了使用它。 另一方面,POJO是“無邏輯的” –它們僅包含數(shù)據(jù),不對(duì)其進(jìn)行操作就保持其狀態(tài)并且不引入邏輯。 如果考慮將“ reservation”域?qū)ο笠胛覀兊膽?yīng)用程序,我們會(huì)立即想到映射到RESERVATIONS數(shù)據(jù)庫表,ReservationDao,ReservationService,ReservationController等的Reservation POJO。
還是看不到問題嗎? 您如何形容“對(duì)象”? 它是一些具有內(nèi)部(封裝)狀態(tài)的虛擬實(shí)體,以及一些具有對(duì)該狀態(tài)的顯式訪問權(quán)限的公共操作。 基于對(duì)象的編程的最基本概念是將數(shù)據(jù)和對(duì)該數(shù)據(jù)進(jìn)行操作的過程放在一起并緊密關(guān)閉它們。 現(xiàn)在看看您有史以來最好的設(shè)計(jì),您真的需要物體嗎? 這是Spring,EJB3,Hibernate和其他完善框架的秘密。 我們所有人下意識(shí)地試圖忘記的秘密:我們不再是OOP程序員!
POJO不是對(duì)象,它們只是數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)集合。 Getter和Setter不是真正的方法,實(shí)際上,您最后一次手工編寫它們是什么時(shí)候? 實(shí)際上,自動(dòng)生成它們(以及在屬性更改時(shí)重構(gòu),添加和刪除)的需求有時(shí)會(huì)令人沮喪。 缺省情況下,僅使用具有公共字段的結(jié)構(gòu)會(huì)不會(huì)更簡(jiǎn)單?
另一方面,請(qǐng)查看所有這些出色的無狀態(tài)服務(wù)。 他們沒有任何狀態(tài)。 盡管它們?cè)谟驅(qū)ο笊线\(yùn)行,但它們不是域?qū)ο蟮囊徊糠?#xff0c;甚至不聚集它們(低內(nèi)聚性)。 所有數(shù)據(jù)都通過方法參數(shù)顯式傳遞。 它們也不是對(duì)象,它們只是在公共名稱空間(對(duì)應(yīng)于類名稱)上任意聚集的過程的集合。 在合同中,OOP中的方法也是幕后的過程,但是具有對(duì)該引用的隱式訪問,該引用指向?qū)ο髮?shí)例。 每當(dāng)我們調(diào)用ReservationService或ReservationDao明確提供Reservation POJO引用作為參數(shù)之一時(shí),我們實(shí)際上是在重新發(fā)明OOP并手動(dòng)對(duì)其進(jìn)行編碼。
面對(duì)現(xiàn)實(shí),我們不是OOP程序員,因?yàn)槲覀冃枰囊磺卸际俏迨昵鞍l(fā)明的結(jié)構(gòu)和過程。 每天有多少Java程序員在使用繼承和多態(tài)? 上一次編寫具有私有狀態(tài)而沒有g(shù)etter / setter且只有很少方法可以訪問的對(duì)象的時(shí)間是什么時(shí)候? 上次使用非默認(rèn)構(gòu)造函數(shù)創(chuàng)建對(duì)象的時(shí)間是什么時(shí)候?
幸運(yùn)的是,Spring采取了什么措施,它帶回了更大的力量。 該功能稱為AspectJ 。
在上一篇文章中,我創(chuàng)建了一個(gè)保留實(shí)體,該實(shí)體具有三種業(yè)務(wù)方法:accept(),charge()和cancel()。 將與域?qū)ο笥嘘P(guān)的業(yè)務(wù)方法直接放置在該對(duì)象中看起來真的很好。 無需調(diào)用ReservationService.accept(reservation),我們只需運(yùn)行Reservation.accept()即可,它更加直觀且噪音小。 更好的是,編寫:
Reservation res = new Reservation() //... res.persist()而不是調(diào)用DAO或直接使用EntityManager? 我對(duì)域驅(qū)動(dòng)設(shè)計(jì)了解不多,但是我發(fā)現(xiàn)這種基本的重構(gòu)是進(jìn)入DDD世界(以及返回到OOP)必須走的第一步。
Reservations的accept()方法最終將需要將一些邏輯委托給外部服務(wù),例如記帳或發(fā)送電子郵件。 自然,此邏輯不是保留域?qū)ο蟮囊徊糠?#xff0c;應(yīng)該在其他地方實(shí)現(xiàn)(高內(nèi)聚性)。 問題是如何將其他服務(wù)注入域?qū)ο蟆?當(dāng)所有服務(wù)都由Spring管理時(shí),一切都很簡(jiǎn)單。 但是,當(dāng)Hibernate自己創(chuàng)建域?qū)ο蠡蚴褂胣ew運(yùn)算符創(chuàng)建對(duì)象時(shí),Spring對(duì)此實(shí)例一無所知,無法處理依賴項(xiàng)注入。 那么,保留POJO如何獲得封裝必要邏輯的Spring bean或EntityManager?
首先,將@Configurable批注添加到您的域?qū)ο笾?#xff1a;
@Configurable @Entity public class Reservation implements Serializable {//... }這告訴Spring保留POJO應(yīng)該由Spring管理。 但是,如上所述,Spring不了解正在創(chuàng)建的Reservation實(shí)例,因此沒有機(jī)會(huì)自動(dòng)裝配和注入依賴項(xiàng)。 這是AspectJ的用處。您需要做的就是添加:
<context:load-time-weaver/>到您的Spring XML描述符。 這個(gè)非常短的XML代碼片段告訴Spring它應(yīng)該使用AspectJ加載時(shí)編織(LTW)。 現(xiàn)在,當(dāng)您運(yùn)行應(yīng)用程序時(shí):
java.lang.IllegalStateException:ClassLoader [org.apache.catalina.loader.WebappClassLoader]不提供'addTransformer(ClassFileTransformer)'方法。 指定一個(gè)定制的LoadTimeWeaver或使用Spring的代理啟動(dòng)Java虛擬機(jī):-javaagent:spring-agent.jar
在org.springframework.context.weaving.DefaultContextLoadTimeWeaver中。
setBeanClassLoader(DefaultContextLoadTimeWeaver.java:82)
在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory中。
initializeBean(AbstractAutowireCapableBeanFactory.java:1322)
在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory中。
doCreateBean(AbstractAutowireCapableBeanFactory.java:473)
…另外59個(gè)
它將失敗……Java不是魔術(shù),因此在繼續(xù)之前,請(qǐng)先解釋一下。 在上面添加XML代碼段并不能解決任何問題。 它只是告訴Spring我們正在使用AspectJ LTW。 但是,當(dāng)應(yīng)用程序啟動(dòng)時(shí),它不會(huì)找到AspectJ代理,并且會(huì)適當(dāng)?shù)馗嬖V我們有關(guān)它的信息。 如果按照建議將-javaagent:spring-agent.jar添加到我們的JVM命令行參數(shù),會(huì)發(fā)生什么? 這個(gè)Java代理僅僅是JVM的插件,它覆蓋了每個(gè)類的加載。 首次加載Reservation類時(shí),代理會(huì)發(fā)現(xiàn)@Configurable批注并將某些特殊的AspectJ方面應(yīng)用于該類。
更精確地說:正在修改Reservation類的字節(jié)碼,從而覆蓋所有構(gòu)造函數(shù)和反序列化例程。 多虧了這種修改,每當(dāng)實(shí)例化新的Reservation類時(shí),除了正常的初始化之外,Spring提供的方面添加的那些附加例程都將執(zhí)行依賴項(xiàng)注入。 因此,從現(xiàn)在開始,增強(qiáng)的Reservation類是Spring感知的。 保留是由Hibernate,Struts2創(chuàng)建還是使用new運(yùn)算符都沒有關(guān)系。 隱藏的方面代碼始終負(fù)責(zé)調(diào)用Spring ApplicationContext,并要求其將所有依賴項(xiàng)注入域?qū)ο蟆?讓我們將其用于試駕:
@Configurable @Entity public class Reservation implements Serializable {@PersistenceContextprivate transient EntityManager em;@Transactionalpublic void persist() {em.persist(this);} //... }這不是一個(gè)錯(cuò)誤–我將JPA規(guī)范中的EntityManger直接注入域?qū)ο蟆?我還將@Transactional批注放置在persist()方法上。 在普通的Spring中這是不可能的,但是由于我們使用了@Configurable批注和AspectJ LTW,因此下面的代碼是完全有效的,并且可以按預(yù)期工作,對(duì)數(shù)據(jù)庫發(fā)出SQL并提交事務(wù):
Reservation res = new Reservation() //... res.persist()當(dāng)然,您也可以將常規(guī)依賴項(xiàng)(其他Spring Bean)注入到域?qū)ο笾小?您可以選擇使用自動(dòng)裝配( @Autowire或更好的@Resource批注)或手動(dòng)設(shè)置屬性。 后一種方法為您提供了更多控制權(quán),但是迫使您在域?qū)ο笾袨镾pring bean添加setter并定義與域?qū)ο笙鄬?duì)應(yīng)的另一個(gè)bean:
<bean class=" com.blogspot.nurkiewicz.reservations.Reservation "><!-- ... --> </bean>請(qǐng)注意,我沒有提供此bean的名稱/ ID。 如果可以的話,應(yīng)該將相同的名稱傳遞給@Configurable批注。
一切都像魅力一樣運(yùn)作,但是我們?nèi)绾卧诂F(xiàn)實(shí)生活中使用這一驚人功能? 首先,我們必須設(shè)置您的單元測(cè)試以使用Java代理。 在IntelliJ IDEA中,我只是添加了:
-javaagent:D:/my/maven/repository/org/springframework/spring-agent/2.5.6/spring-agent-2.5.6.jar
JUnit運(yùn)行配置中的“ VM參數(shù)”文本字段。 如果將其添加到默認(rèn)值(“編輯默認(rèn)值”按鈕),則此參數(shù)將應(yīng)用于您運(yùn)行的每個(gè)新單元測(cè)試。 但是配置IDE并不像配置構(gòu)建工具(希望是Maven)那么重要。 首先,您必須確保Spring Java代理已下載且可用。 感謝Maven的依賴關(guān)系解析,可以通過添加以下依賴關(guān)系輕松實(shí)現(xiàn):
<dependency><groupId>org.springframework</groupId><artifactId>spring-agent</artifactId><version>2.5.6</version><scope>test</scope> </dependency>測(cè)試代碼實(shí)際上并不需要JAR,但是通過添加此依賴項(xiàng),我們保證在測(cè)試運(yùn)行之前已下載JAR。 然后,對(duì)surefire插件配置進(jìn)行簡(jiǎn)單的調(diào)整:
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><configuration><forkMode>always</forkMode><argLine>-javaagent:${settings.localRepository}/org/springframework/spring-agent/2.5.6/spring-agent-2.5.6.jar</argLine></configuration> </plugin>真的很簡(jiǎn)單–可以使用maven存儲(chǔ)庫路徑安全地構(gòu)造spring-agent.jar的位置。 此外,還必須設(shè)置forkMode以便在執(zhí)行每個(gè)測(cè)試之前重新加載類(并導(dǎo)致LTW發(fā)生)。 我認(rèn)為配置您的應(yīng)用服務(wù)器和/或啟動(dòng)腳本不需要任何進(jìn)一步的說明。
這就是通過加載時(shí)編織進(jìn)行Spring和AspectJ集成的全部?jī)?nèi)容。 很少有簡(jiǎn)單的配置步驟和域驅(qū)動(dòng)設(shè)計(jì)的全新世界出現(xiàn)。 我們的領(lǐng)域模型不再脆弱,實(shí)體變得“智能”,業(yè)務(wù)代碼更加直觀。 最后但并非最不重要的一點(diǎn)–您的代碼將是面向?qū)ο蟮?#xff0c;而不是過程性的。
當(dāng)然,您可能不喜歡加載時(shí)編織,因?yàn)樗鼤?huì)干擾JVM類的加載。 還有另一種方法,稱為編譯時(shí)編織,它在編譯時(shí)而不是在類加載時(shí)編織方面。 兩種方法都有其優(yōu)缺點(diǎn),以后我將嘗試將它們進(jìn)行比較。
確實(shí),這是非常有趣的方法。 就是這樣,我們的JCG合作伙伴 Tomasz Nurkiewicz提供了一個(gè)緊湊指南,指導(dǎo)如何使用Spring和AspectJ的加載時(shí)間編織方式將依賴項(xiàng)注入域?qū)ο?。 如果您喜歡這個(gè),別忘了分享。 編碼愉快!
相關(guān)文章:
- 在域驅(qū)動(dòng)設(shè)計(jì)中使用狀態(tài)模式
- 使用Spring AspectJ和Maven進(jìn)行面向方面的編程
- 依賴注入–手動(dòng)方式
- JBoss 4.2.x Spring 3 JPA Hibernate教程
翻譯自: https://www.javacodegeeks.com/2011/02/domain-driven-design-spring-aspectj.html
總結(jié)
以上是生活随笔為你收集整理的Spring和AspectJ的领域驱动设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三才碗指的是什么 什么是三才碗
- 下一篇: GWT 2 Spring 3 JPA 2