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