VS-Aspectj对比SpringAop之间的差异

在我开始讨论我们的主题之前,我想让你考虑一下你可以想象的最佳Java EE应用程序设计。 不管你使用Spring还是EJB3,因为它们非常相似,你可能会建议一种类似于以下的方法。 从后端开始,您有:

  • 域对象 ,这是简单的POJO直接映射到数据库关系。 POJO非常棒,因为JavaBean风格的属性在许多框架中都很好理解。
  • 数据访问层 – 通常是无状态的服务,它隐藏了数据库访问代码(JDBC,Hibernate,JPA,iBatis或任何你想要的),从而隐藏了它的复杂性并提供了某种程度的(泄漏)抽象。 DAO非常棒,因为它们隐藏了令人讨厌和尴尬的JDBC逻辑(这就是为什么有些人在使用JPA时质疑DAO的需求),充当数据库和对象之间的或多或少的翻译器。
  • 业务服务层 – 另一组无状态服务,它们对域对象进行操作。 典型的设计引入了一个对象的图,该对象获取或返回域对象并对它们执行一些逻辑,再次,通常通过数据访问层访问数据库。 服务层非常棒,因为它专注于业务逻辑,将技术细节委托给DAO层。
  • 用户界面 – 时下,通常通过网络浏览器。 用户界面非常好,因为……事实就是如此。

美丽,不是吗? 现在睁开你的眼睛,现在是冷水淋浴的时候了。

服务和DAO都是无状态的,因为Spring和EJB3倾向于这样的类 – 所以我们学会了如何使用它。 另一方面,POJO是“无逻辑的” – 它们只包含数据,保持其状态而不对其进行操作并且不引入逻辑。 如果我们考虑将“保留”域对象引入到我们的应用程序中,我们立即想到预留POJO映射到保留数据库表,ReservationDao,ReservationService,ReservationController等。

仍然没有看到问题? 你会如何描述“对象”? 它是一些具有内部(封装)状态和一些公开操作的虚拟存储器,它们可以显式访问该状态。 基于对象编程的最基本概念是将数据和程序操作在数据上并紧密关闭。 现在看看你有史以来最好的设计,你真的需要物体吗? 这是Spring,EJB3,Hibernate和其他成熟框架的黑暗秘密。 我们所有人潜意识忘记的秘密:我们不再是OOP程序员!

POJO不是对象,它们只是数据结构,数据集合。 吸气剂和吸附剂不是真正的方法,事实上,你最后一次手工写作的时间是什么时候? 事实上,自动生成它们(以及重构,添加和删除属性更改时)的需求有时非常令人沮丧。 仅仅在默认情况下使用带有公共字段的结构会不会更简单?

另一方面,看看所有这些伟大的无状态服务。 他们没有任何国家。 虽然它们在域对象上运行,但它们不是它们的一部分,或者甚至不会聚合它们(低凝聚力)。 所有数据都通过方法参数显式传递。 它们也不是对象 – 它们只是在公共名称空间上任意聚集在一起的过程的集合,与类名相对应。 在契约中,OOP中的方法也是幕后的程序,但隐式访问这个引用指向对象实例。无论何时我们将ReservationService或ReservationDao明确提供Reservation POJO引用作为参数之一,我们实际上都会重新创建OOP并手动对其进行编码。

让我们面对现实吧,我们不是OOP程序员,因为我们需要的所有东西都是五十年前发明的结构和程序。 有多少Java程序员在日常的基础上使用继承和多态? 你最后一次写了一个没有getter / setter的私有状态的对象,只有几种方法可以访问它? 最后一次使用非默认构造函数创建对象的时间是?

幸运的是,春天带来了更多的力量。 这种力量被称为AspectJ 。

在我上一篇文章中,我创建了一个具有三种业务方法的预订实体:accept(),charge()和cancel()。 将域对象的商业方法直接放置在该对象中看起来非常好。 我们不用调用reservationService.accept(reservation),而是直接运行reservation.accept(),它更直观,噪音更小。 更好的是,写作如何:


 

而不是直接调用DAO或使用EntityManager? 我对域驱动设计知之甚少,但是我发现这个基本的重构是你进入DDD世界必须经历的第一道门槛(并回到OOP)。

保留’accept()方法最终需要将一些逻辑委托给外部服务,如会计或发送电子邮件。 当然,这个逻辑不是预留域对象的一部分,应该在别处实现(高内聚性)。 问题是如何将其他服务注入到域对象中。 当所有服务都由Spring管理时,一切都很简单。 但是当Hibernate自己创建域对象或者使用new运算符创建对象时,Spring并不知道这个实例,并且不能处理依赖注入。那么Reservation POJO如何获得Spring bean或EntityManager封装必要的逻辑?

首先,将@Configurable注释添加到您的域对象:


 

这告诉Spring,预订POJO应该由Spring管理。 但是,如上所述,Spring并不知道正在创建预订实例,所以它没有自动装入和注入依赖关系的机会。 这是AspectJ进来的地方。你需要做的就是添加:

 

到你的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-aware。 无论预订是由Hibernate,Struts2还是使用new运算符创建的都没关系。 隐藏的方面代码总是负责调用Spring ApplicationContext,并要求它将所有依赖关系注入到域对象。 让我们试试看:


 

这不是一个错误 – 我将JPA规范中的EntityManger直接注入了域对象。 我还将@Transactional注释放在persist()方法上。这在普通的Spring中是不可能的,但是由于我们使用了@Configurable注解和AspectJ LTW,下面的代码是完全有效的并且按预期工作,发出SQL并针对数据库提交事务:


 

当然,您也可以将常规依赖项(其他Spring bean)注入到您的域对象中。 您可以选择使用自动装配( @Autowire或更好的@Resource注释)或手动设置属性。 后一种方法给了你更多的控制权,但是迫使你为domain对象中的Spring bean添加setter,并定义与domain对象相对应的另一个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的依赖解决方案,可以通过添加以下依赖关系轻松实现:


 

测试代码实际上不需要JAR,但通过添加此依赖关系,我们保证在测试运行之前下载它。 然后,在surefire插件配置中进行一个简单的调整:


 

真的很简单 – 可以使用Maven存储库路径安全地构建spring-agent.jar的位置。 此外,forkMode必须设置为在每次测试执行之前重新加载类(并导致LTW发生)。 我认为配置你的应用服务器和/或启动脚本不需要任何进一步的解释。

这些都是关于Spring和AspectJ通过加载时编织进行集成的。 几乎没有简单的配置步骤和一个全新的领域驱动设计世界。 我们的领域模型不再薄弱,实​​体是“聪明的”,业务代码更直观。 最后但并非最不重要的 – 你的代码将是面向对象的,而不是程序性的。

当然,你可能不喜欢加载时编织,因为它会干扰JVM类的加载。 还有另一种称为编译时编织的方法,它在编译时编织方面而不是类加载时间。 两种方法都有优点和缺点,我会尽力在未来比较两者。

原创于 【模棱博客】

VS-Aspectj对比SpringAop之间的差异》有4个想法

评论已关闭。