Spring和AspectJ的领域驱动设计

在JavaCodeGeeks主持的上一篇文章中,我们的JCG合作伙伴 Tomasz Nurkiewicz介绍了使用State设计模式进行领域驱动设计的介绍 。 在该教程的最后,他承认他省略了如何将依赖项(DAO,业务服务等)注入域对象的过程。 但是,他答应在随后的帖子中解释细节。 好了,现在该看看如何完成此工作了。 托马斯(Tomasz)继续提供非常具有启发性的教程 。

让我们看看他怎么说:

(注意:对原始帖子进行了少量编辑以提高可读性)

在开始讨论我们的主要主题之前,我希望您先想一想关于您可以想象的最佳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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/374813.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BZOJ 3143 HNOI2013 游走 高斯消元 期望

这道题是我第一次使用高斯消元解决期望类的问题&#xff0c;首发A了&#xff0c;感觉爽爽的.... 不过笔者在做完后发现了一些问题&#xff0c;在原文的后面进行了说明。 中文题目&#xff0c;就不翻大意了&#xff0c;直接给原题&#xff1a; 一个无向连通图&#xff0c;顶点从…

VS2019安全函数scanf_s问题

VS2017、VS2019等安全函数scanf_s问题&#xff1a; scanf()、gets()、fgets()、strcpy()、strcat() 等都是C语言自带的函数&#xff0c;它们都是标准函数&#xff0c;但是它们都有一个缺陷&#xff0c;就是不安全&#xff0c;可能会导致数组溢出或者缓冲区溢出&#xff0c;让黑…

eclipse启动tomcat, http://localhost:8080无法访问的解决方案

问题:&#xff1a; tomcat在eclipse里面能正常启动&#xff0c;但在浏览器中访问http://localhost:8080/不能访问tomcat管理页面&#xff0c;且报404错误。同时其他项目页面也不能访问。访问的时候出现下列页面: 现在关闭eclipse里面的tomcat&#xff0c;在tomcat安装目录下双击…

GWT EJB3 Maven JBoss 5.1集成教程

大家好&#xff0c; 在本文中&#xff0c;我们将演示如何正确集成GWT和EJB3 &#xff0c;以实现示例项目&#xff0c;使用maven进行构建并将其部署在JBoss 5.1应用服务器上。 实际上&#xff0c;您可以轻松地更改maven构建文件中的依赖关系&#xff0c;并将项目部署到您喜欢的…

VS2019注释整段代码

VS2019注释整段代码 1.注释 先CTRLK&#xff0c;然后CTRLC 2.取消注释&#xff1a; 先CTRLK&#xff0c;然后CTRLU 顺便写一下&#xff1a; VS code注释整段代码 Alt Shift A 注释 CodeBlocks&#xff1a; CtrlShiftC注释掉当前行或选中部分&#xff0c; CtrlShiftX解除注释…

linux中的开机和关机命令

与关机、重新启动相关的命令 * 将数据同步写入硬盘中的命令 sync * 惯用的关机命令 shutdown * 重新启动、关机 reboot halt poweroff sync 强制将内存中的数据写入到硬盘当中。因为linux系统中&#xff0c;数据会缓存在内存当中&#xff0c;所以为了保证数据完整保存在硬盘…

如何在不到1ms的延迟内完成100K TPS

马丁汤普森&#xff08;Martin Thompson&#xff09;和迈克尔巴克&#xff08;Michael Barker&#xff09;讨论了通过采用一种新的基础架构和软件方法来构建HPC金融系统&#xff0c;以不到1ms的延迟处理超过100K TPS的问题。 一些技巧包括&#xff1a; 了解平台 建模领域 明…

获取时间C语言-按秒数

两部分&#xff1a; 1.获取秒数 2.获取“年-月-日-时-分-秒” 1.获取秒数 #include<time.h>//包含的头文件int GetTime() {time_t t;t time(NULL);//另一种写法是//time(t);//当time&#xff08;&#xff09;内参数为空时结果直接输出&#xff0c;否则就会存储在参数…

Spring的69个知识点

目录 Spring 概述依赖注入Spring beansSpring注解Spring数据访问Spring面向切面编程&#xff08;AOP&#xff09;Spring MVCSpring 概述 1. 什么是spring? Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用&#xff0c;但是有些扩展是针对构建J2EE平台的…

python 编码问题之终极解决

结合之前遇到的坑以及下面贴的这篇文章&#xff0c; 总结几种python乱码解决方案&#xff0c;如果遇到乱码&#xff0c;不妨尝试一下&#xff1f; 1&#xff0c;必备 #encodingutf-8 2, python编程环境编码 import sys reload(sys) sys.setdefaultencoding(utf8) 3,不知道神马…

GWT 2 Spring 3 JPA 2 Hibernate 3.5教程

本分步指南将介绍如何使用开发一个简单的Web应用程序 Google的网络工具包 &#xff08;GWT&#xff09;用于富客户端&#xff0c;而Spring作为后端服务器端框架。 该示例Web应用程序将提供对数据库执行CRUD&#xff08;创建检索更新删除&#xff09;操作的功能。 对于数据访问层…

洛谷P1014 [NOIP1999 普及组] Cantor 表

现代数学的著名证明之一是 Georg Cantor 证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的&#xff1a; 代码 import java.util.*; public class Main{public static void main(String[] args){//int x1 0;int i 0;Scanner sc new Scanner(System.in);int n s…

3522: [Poi2014]Hotel( 树形dp )

枚举中点x( 即选出的三个点 a , b , c 满足 dist( x , a ) dist( x , b ) dist( x , c ) ) , 然后以 x 为 root 做 dfs , 显然两个位于 x 的同一颗子树内的点是不可能被同时选到的 . 我们对 x 的每一颗子树进行 dfs , 记录下当前子树中的点到 x 距离为 d ( 1 < d < n )…

第一冲刺阶段工作总结02

1.昨天&#xff1a; 实验简单的安卓程序&#xff0c;开始具体的设计软件界面。 2.今天&#xff1a; 继续设计软件页面&#xff0c;由于安卓虚拟机过于迟缓&#xff0c;配置真机&#xff0c;学习如何在真机上运行程序。 3.遇到的困难&#xff1a; 真机配置不知道怎样配置&#x…

JBoss 4.2.x Spring 3 JPA Hibernate教程第2部分

我们将继续有关Spring 3 &#xff0c; Hibernate &#xff0c; JPA和JBoss 4.2.x – 4.3集成的教程 。 最后一步是创建一个Spring服务&#xff0c;以向最终用户公开功能。 我们必须创建一个接口类和相关的实现类。 首先是接口类&#xff1a; package com.mycomp.myproject.se…

洛谷P1035 [NOIP2002 普及组] 级数求和

代码 import java.util.Scanner;public class Main {public static void main(String args[]){Scanner sc new Scanner(System.in);int k sc.nextInt();int n 0;double Sn 0;while(Sn<k){n;Sn Sn 1.0/n;}System.out.println(n);} }这样写while循环体这需要每次加上1/…

『Luogu OJ』『C++』Level 1-1

关卡1-1&#xff0c;3道题 洛谷的第一个任务 任务说明&#xff1a;勇敢的迈出第一步&#xff0c;了解下语言和洛谷。跟着书本和老师走&#xff0c;不会难的。 要完成这个任务&#xff0c;请将以下的题目都AC掉&#xff08;即通过这道题目&#xff09;&#xff1a; 1.AB Problem…

Java中的Google ClientLogin实用程序

Google API的身份验证和授权是当今需要与Google服务集成和信息交换的应用程序中的常见功能。 尽管大多数Google身份验证过程是针对Web应用程序量身定制的&#xff0c;但它也可用于桌面和已安装的应用程序。 对于桌面应用程序&#xff0c;Google建议使用称为ClientLogin的身份验…

九度OJ1486 /POJ 1029/2012北京大学研究生复试上机

wa到死&#xff01;wa到死&#xff01;这是一个看着简单&#xff0c;坑及其多的题&#xff01; 坑一&#xff1a;POJ上是单组输入&#xff0c;九度上是多组输入&#xff0c;妈蛋要是研究生复试遇到这种大坑肯定死掉啊&#xff01;而且对于codeforces比较习惯的 同学肯定会觉得巨…

P1046 [NOIP2005 普及组] 陶陶摘苹果

题目描述 陶陶家的院子里有一棵苹果树&#xff0c;每到秋天树上就会结出 1010 个苹果。苹果成熟的时候&#xff0c;陶陶就会跑去摘苹果。陶陶有个 3030 厘米高的板凳&#xff0c;当她不能直接用手摘到苹果的时候&#xff0c;就会踩到板凳上再试试。 现在已知 1010 个苹果到地面…