再论 IoC 和 AOP - 驳 yong9981 对 "谈谈 ... (2019-12-25)" 一文的评论

原创
2019/12/26 07:54
阅读数 2.8K

谈谈我对 IoC 和 AOP 的理解 一文由 JFinal 作者波总对 IoC 与 AOP 的一句表述引起:

IOC 本质是为了实现 AOP

在文中我考察了 IoC, DIAOP 三个概念及其关系, 并得出以下结论:

  1. IoC 的本质不是为了实现 AOP.
  2. 波总的 JFinal 已经实现了 IoC 原则. 因为应用写的代码总是被 JFinal 的代码调用, 这就是控制反转.
  3. DI 也不是为了实现 AOP

@yong9981 对此文给出了以下评论:

我不赞同 yong9981 在评论中的观点, 所以在本文中一一回应.

1. "其实不能算错, 相反,只有对IOC/AOP有深切理解的人才能说出这句话"

yong9981 这句话应该是针对波总的表述 "IoC 的本质是为了实现 AOP" 而言. 我在 谈谈 一文中已经清楚地给出了维基百科中对 IoC 和 AOP 这两个概念的描述, 并由此得到结论: "IoC 的本质不是为了实现 AOP". 如果 yong9981 认为波总的表述没有错误, 那就是我的结论有误. 逻辑上讲有两个可能:

  1. 维基百科对 IoC 和 AOP 的概念描述有错误
  2. 我在文中依据概念得出结论的过程不正确, 依据维基百科的概念描述应该得到 "IoC 的本质是为了实现 AOP" 这个表述

@yong9981 回答, 你认为我上面结论的错误应该属于哪种情况? 不管是哪种情况, 请给出你的依据. 直接下断言的方式我不接受.

2. "ACT则更奇葩,框架只提供 IOC 功能 ... 从架构上来说是错误的"

依据维基百科对 IoC 的定义, 凡是提供控制反转的 (应用代码被框架调用的) 都是 IoC 的应用. 因此我大致可以说基本上 Web 框架 (包括 Servlet 在内)都是符合 IoC 原则的.

基于以上分析, 我姑且揣摩 yong9981 想说的是 "ACT 则更奇葩, 框架只提供 DI 功能".

先来看看除 Act (截止到 1.8.x) 以外还有那些框架支持 DI, 但不提供 通用 AOP:

由此看来只支持 DI 而不提供 AOP 的框架也不仅仅是 Act 了.

再来看看百度百科对 奇葩 这个词的定义是:

原意是指奇特而美丽的花朵,常用来比喻不同寻常的优秀文艺作品或非常出众的人物。比喻某人(或某事物)不落世俗,个性十足。这个词更多带有调侃,指美好出众的事物不同。也多指向一些正常人行为和思维以外的,让人难以想象的行为。

我相信 yong9981 用 奇葩 来修饰 Act, 不会是引用其原意, 而是指 Act 仅支持 DI 不支持 AOP 的设计不正常. 上面我已经列出有更多的框架采用同样的设计, 所以这个词我不接受.

3. "把AOP和声明式事务当作DAO插件一起捆绑,这从架构上来说是错误的"

这里解释一下 yong9981 上面这个论断的上下文.

ActFramework 不支持通用的 AOP, 而是采用下面的方式替代了 AOP 的部分常见用途:

  1. 拦截器机制 - 解决应用在请求处理流程中的切面编程问题
  2. Metric 机制 - 解决应用收集性能数据的切面编程问题
  3. 对声明式事务的处理
    • Ebean - 交给 Ebean 的声明式事务处理机制 (通过 javaagent 机制修改应用字节码)
    • EclipseLink 和 Hibernate - 交给 act-jpa-common 的声明式事务机制处理 (通过 Act 的类增强机制修改应用字节码)

yong9981 认为 Act 在这方面的设计是错误的, 他的理由是:

因为事务本身是可以独立于DAO存在的

我不是很明白上面这条理由. 其中的 "事务" 是指一个运行时的事务实例, 还是只框架的事务处理机制? "DAO" 是指一个运行时的 Dao 实例, 还是数据库访问组件? 如果都是指实例和正在讨论的问题不相干, 我姑且认定 yong9981 说的是"事务处理机制和数据库访问组件无关", 并基于这个理解来讨论.

对上面的理由 yong9981 继续列出了一个作证:

例如spring事务模块可以捆绑在任意支持AOP联盟标准上的DAO工具上使用的,但前提是框架要支持AOP联盟标准。

先向 yong9981 提两个问题:

  1. 什么是 "支持AOP联盟标准上的DAO工具" ?
  2. 请列举出几个 "支持AOP联盟标准上的DAO工具" 出来

就 Java 生态, 我所知道的比较著名的数据库访问组件有:

请问 yong9981 上面列出的 Java 数据库访问组件哪个是支持 AOP Alliance 的? 请列出你引用的链接. 如果你给不出反例, 我可以得出以下结论:

j1. 没有支持 AOP 联盟标准上的 DAO 工具

既然 yong9981 使用 Spring 作为作证, 下面我们就看看 Spring 中 AOP 到底是如何参与声明式事务处理的

3.1 Spring 中对声明式事务的处理

Spring Transaction Management 文中这个插图描述了 Spring 对声明式事务的处理机制:

从图中, 我们可以看到 AOP 的参与到 TransactionAdvisor 为止, 而具体某个数据库访问机制如果需要支持 Spring 的事务处理过程, 需要讲自己适配到 Spring 的事务管理机制中 - 不是适配到 AOP alliance

拿 MyBatis 为例. 在 MyBatis Spring 插件 - 事务 文档中第一句话就是:

One of the primary reasons for using MyBatis-Spring is that it allows MyBatis to participate in Spring transactions

MyBatis Spring Github 项目 我们可以看到 MyBatis Spring 集成进 Spring 事务处理的代码:

到此我们可以得出下面的结论:

j2. "spring事务模块可以捆绑在任意支持AOP联盟标准上的DAO工具上使用的" 这个断言是错误的

4. "我说这个框架有问题... 只基于一个原则,... 能不能拿出来单独使用 ..."

yong9981 评论中的最后一段论述如下:

我说这个框架有问题,那个框架有问题,往往只基于一个原则,就是它这个功能能不能拿出来单独使用,能不能被其它软件替换掉, 基于这个原则,可以很简单地判断出以下问题:

  • ACT和JFinal不支持AOP联盟标准是一个缺陷
  • SpringBoot、SpringCloud的IOC/AOP内核不能被其它软件替换掉是一个问题,它导致Spring绑死在Spring内核上,不是说springioc内核不好,相反它非常好,依赖也少,查一查MAVEN就知道了。但它的问题是太大了,源码难看,又动不动更新扰民。
  • ACT和jFinal的MVC模块不能单独使用,这是一个架构问题,造成资源浪费,比方说,我想使用以下三个优秀功能的组合是做不到的: spring-ioc内核+ACT的MVC+JFinal的事务

依据 hotframeworks 给出的统计, 2019 最常用的 Java web 框架有:

  • Spring
  • JSF
  • GWT
  • Play!
  • Struts
  • Vaadin
  • Grails

请问 yong9981: 这些框架的组件都能互换吗? 是不是这些框架都有问题? 如果每个框架都有问题, 是不是你的问题有问题?

我认为:

  1. 在一定程度上, 框架组件的解耦 (相互替换) 可以采用遵循一些标准来达到, 比如 JSR 330 依赖注入, JSR 303 数据校验, JAX-RS (RESTful Service API) 等等
  2. 框架拥有选择自己生态结构的权利. 一个框架可以选择一个全封闭的生态, 也可以选择一个相对开放的生态. 没有理由因为框架做出的这种选择认为该框架有问题甚至有缺陷.

5. 更新 - 2019-12-27

回答 @yong9981 2019-12-26 对本文的评论.

5.1 再议 AOP 与 DAO

先把术语定义一下, 因为 yong9981 使用了 DAO 工具, 我们就在这里沿用这个术语, 指各种数据库访问组件, 包括 Hibernate, MyBatis 等.

@yong9981 没有回答我在文中提出的问题, 而是举出了 Spring 的TransactionInterceptor.java:

TransactionInterceptor是一个符合AOP联盟的类,Spring为什么要引用这个接口?不是吃饱了没事干,而是设计给其它AOP工具来使用这个类的

并引用到了自己的 jBeanBox 来作证:

这体现了Spring的开放性,他把最有价值的事务模块从内核剥离出来了。具体一个配置事例可见https://gitee.com/xialinlin/jBeanBox。它没有使用spring的IOC/AOP内核,但是使用了它的事务.

yong9981 进而讲:

支持AOP联盟标准上的DAO工具 这是个笔误或简写,应该是支持AOP联盟标准的声明式事务的DAO工具。你列举的上述软件只要在Spring环境下,它们大多都是基于TransactionInterceptor进行声明式事务的

这里我需要指出几点:

  1. TransactionInterceptor 是 Spring 内部基于 Spring AOP 实现声明式事务的一种机制
  2. 这种机制和其他数据库访问组件 没有 关系 - 在本文第三节中列举的 DAO 工具中的代码, 包括其 spring 适配库 (例如 mybatis-spring) 中既看不到调用 TransactionInterceptor 的方法, 也看不到任何类继承 TransactionInterceptor. 因此没有所谓 "支持AOP联盟标准的声明式事务的DAO工具" - AOP联盟标准对于数据库访问组件来说是不可知的(agnostic).
  3. yong9981 的 jBeanBox 也根本谈不上 "使用了它 (Spring) 的事务". 除了 README, 代码中没有任何与 Transaction 相关的部分.

现在的问题是为什么 yong9981 一定要把 AOP 和 声明式事务以及 DAO 联系在一起呢? 我揣测其思维脉络可能是:

  1. Spring 支持 AOP Alliance
  2. Spring 在运行时基于 AOP 提供声明式事务的支持, 大致来说有一下几点:
    • 调用事务方法前建立事务上下文
    • 调用事务方法后提交事务
    • 调用出错时回滚事务 (依据异常类型可能有不同的处理)

到这里我们可以提个问题了, 是不是其他 DAO 工具能不做任何工作就使用 Spring 的声明式事务访问机制了呢? 通过研究 mybatis-springebean-spring-txn 这两个项目, 我们可以明确地给出否定回答: DAO 工具不会因为 Spring 采用了 AOP 来触发声明式事务机制而避免和 Spring 事务机制的集成工作.

另外 yong9981 在评论中谈到:

反之,jFinal、ACT中的事务并没有共享出来

jFinal 我不清楚, 下面我们来看看 ActFramework 的声明式事务机制, 并和 Spring 的声明式事务机制做一个比较.

5.1.1 ActFramework 的声明式事务机制

ActFramework 在 act-sql-common 插件项目 (act-1.8.9 之前是 act-jpa-common 项目) 中建立了事务处理机制. 该项目是 act-hibernateact-eclipselink 两个项目的祖父项目. 简单地说 act-hibernate 和 act-eclipselink 这两个 DAO 工具的 Act 插件无需建立自己的声明式事务处理机制. 如果今后有其他 hibernate 和 eclipselink 之外的其他支持 JPA 的 DAO 工具也可以很容易的集成进 Act-DB 框架. 这里给出两个数据, act-hibernate 的代码为 212 行, act-eclipselink 的代码为 105 行, 基本上都是为了适配 hibernate 和 eclipselink 本身的各种配置.

下面来看看具体 Act 的声明式事务机制的处理过程.

  • TxScopeEnhancer 在应用类加载的时候如果检测到公共方法上有 @Transactional 注解, 对该方法的字节码进行增强:
    1. 方法调用之前建立事务环境
    2. 方法调用之后提交事务
    3. 异常发生时回滚事务
  • 以上事务环境的操作封装在 TxContext 类中, 该类通过 ThreadLocal 来封装 TxInfo 事务状态.
  • TxInfo 的事务状态通过 TxScopeEventListener 传递给 SqlDbService
  • SqlDbService 是各个 DAO 插件必须继承并实现的类. 比如 act-ebean 的 EbeanService, act-hibernate 的 HibernateService 以及 act-eclipselink 的 EclipseLinkService.
  • SqlDbService 提供以下抽象方法用于适配 DAO 工具集成:
    protected abstract void doStartTx(Object delegate, boolean readOnly);
    protected abstract void doRollbackTx(Object delegate, Throwable cause);
    protected abstract void doEndTxIfActive(Object delegate);
    

这个处理机制和 Spring 基于 AOP 的处理机制相比:

  1. TxScopeEnhancer 替代了 基于 AOP 的 TransactionInterceptor, 运行时效率上更有优势
  2. 有明确的机制将 Act 的事务处理适配到 DAO 工具集成项目中.
  3. 该机制没有采用 Spring 的 TransactionManagerDataSource 层面上耦合的方式, 而是通过明确的 API (参见上面 SqlDbService 的三个抽象方法) 和 DAO 工具集成耦合.
  4. 目前的实现比 Spring 对事务的支持要粗糙, 比如不支持 nested transaction. 这个地方是今后版本工作的重点区域.

值得一提的是从 act-1.8.9 开始, 事务机制的实现从 act-jpa-common 提升至 act-sql-common, 也就是说对 act-ebean 的事务支持理论上可以不需要通过 ebean 自己的事务处理机制了. 我今天做了一个小试验, 将 transaction-ebean 示例项目的 Account.java 文件中的事务注解从 io.ebean.annotation.Transactional 切换到了 act.db.sql.tx.Transactional, 运行结果证明了我前面的猜测, 运行过程中事务处理从 Ebean 内置的切换到了 Act 的提供的, 没有任何问题. 有兴趣的看官可以自行 checkout 这个项目代码研究一下:

在下一个版本中事务处理机制会直接提升到 Act 核心框架中, 这样允许 Act 提供对非 SQL 数据库 DAO 工具的事务支持, 比如 MongoDB (v4+).

5.2 关于开放和封闭的选择

我认为一个框架是否开放应该考察这个框架对现有业界标准的支持. 一个框架对标准的支持越好, 这个框架就越开放. 下面列出一些 Act 支持的标准:

  1. HTTP/RESTful, - 如果有哪个框架包括 Spring 在内在这方面的支持比 Act 更好, 我很愿意了解学习.
  2. JSR 330 - 依赖注入
  3. JSR 303 - 数据校验
  4. JAX-RS - 通过 act-jax-rs 插件
  5. JPA - 通过 act-jpa-common 插件

对于 yong9981 提到的

jFinal、ACT、NutzBoot,这三者都有IOC模块,难道三个作者就不能合力统一成一个短小精悍的可以干掉Guice的IOC/AOP模块?

我只能呵呵一下了. 以下是对 yong9981 其他几个表述的回答:

集成式环境的另一个问题是增加学习难度

如果一个框架能尽可能符合标准规范行事, 可以降低学习负担. 举个例子, 但凡用习惯了 Guice (支持 JSR 330 依赖注标准), 切换到 Act 毫无违和感.

你列了一堆Java web框架,但正常人有几个一个个全去了解的

列出一堆 Java Web 框架的目的是为了佐证你这个说法不正确: "我认为一个框架是否有问题只基于一个原则: 其组件能否单独使用, 能否被其他框架组件替换". 你没有回答我的质询: "这些框架的组件都能互换吗? 是不是这些框架都有问题? 如果每个框架都有问题, 是不是你的问题有问题?"

你的ACT也许是性能最好,但依赖太多(Maven上30个依赖,还是compiled类型的,发布包2M多),这对于普通人来说是个巨大的学习负担

首先性能是 Act 关注点, 但不是最重要的地方, 功能和易用性才是. 这个地方我会在以后的博客系列中继续讨论.

依赖的数量从来不是 Act 的关注点. 如果一个框架功能丰富, 具有很好的开发时支持和相当的运行时性能, 我不在乎依赖的数量.

依赖数量和学习负担没有正比关系. 学习的范围在于应用需要用到的功能. 应用开发的复杂性是一定的, 如果框架提供了支持, 你需要学习, 如果不提供, 你需要自行研发.

Yong9981 理想中的后台环境

这个属于个人喜好, 我在这里不做评论.

展开阅读全文
打赏
7
1 收藏
分享
加载中
1. "其实不能算错..."
在原贴解释过了,对JFinal来说是原因的,但单独拿出来就错,不用再谈。下面谈谈ACT架构本身。
2. ACT则更奇葩,框架只提供 IOC功..."
用了"奇葩"这词我表示诚挚道歉! 但在技术上,我坚持我的观点,就是如果做集成框架,就必须提供符合AOP联盟的功能。
比如说,ACT如果想与DbUtils集成,并要用到声明式事务怎么办?如果不求ACT作者做插件,比较简单的一个做法就是引入Spring的AOP功能和Spring的事务配块和DbUtils模块搭配。而AOP和事务这两个功能难道不应该由框架提供? 用了ACT还必须求助于Spring不成?
ACT如果只想定位于MVC功能,那就把公开的IOC/DAO调用方法给隐掉,如果想做成大一统的生态,不依赖于Spring,就必须声明式事务这个功能模块。
比方说Struts这个项目,它不提供AOP功能,是因为它定位于MVC模块,而不是一个集成开发环境,所以Struts通常要和spring或guice答配使用。相信其它MVC项目也有类似设计架构。
3. "把AOP和声明式事务当DAO插件捆绑..."
和上面同理。
4. 事务本身可以独立于DAO存在
这是Spring中的源码:
```
import org.aopalliance.intercept.MethodInterceptor;
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {
```
TransactionInterceptor是一个符合AOP联盟的类,Spring为什么要引用这个接口?不是吃饱了没事干,而是设计给其它AOP工具来使用这个类的。这体现了Spring的开放性,他把最有价值的事务模块从内核剥离出来了。具体一个配置事例可见https://gitee.com/xialinlin/jBeanBox。它没有使用spring的IOC/AOP内核,但是使用了它的事务.
反之,jFinal、ACT中的事务并没有共享出来,比方说给Spring来用。
单独的事务模块除了Spring-tx外,目前我已知的只有自已的jTransactions项目,它也采用AOP联盟接口,支持多数据源。
(待续)
2019/12/26 21:26
回复
举报
5.支持AOP联盟标准上的DAO工具 这是个笔误或简写,应该是支持AOP联盟标准的声明式事务的DAO工具。你列举的上述软件只要在Spring环境下,它们大多都是基于TransactionInterceptor进行声明式事务的,比如纯JDBC、BeetlSQL、MyBatis,它们的共同点就是在获取连接时使用Spring的DataSourceUtils来获取,好让连接置于Spring的事务中,这个比从头开发自己的事务模块省事多了。 6.关于开放和封闭的选择 因为软件有赢者通吃规律,用户只选最流行的生态,流行的也可以向封闭式架构发展(比方说springmvc依赖于spring-ioc等多个臃肿的库,iOC内核出现JPA代码),这叫店大欺客。但对于小众的、新项目来说,开放和兼容是更重要的考虑,凭一两个人的精力,所有功能模块都做到业界最好,这几乎是不可能的事,在项目选型阶段,如果对公司负责,第一步就要排除这种不知名的封闭的小生态。而开放式架构则不用担心,万一有解决不了的Bug,可以随时切换到臃肿但可靠的Spring相应模块上去,另外对于开源新项目本身来说,采用开放式架构,只要一个模块做到极致,也是有可能逐渐在流行中占据一个模块的位置。 现在流行动不动就做成一个集成式环境这种做法,有重复开发现象。jFinal、ACT、NutzBoot,这三者都有IOC模块,难道三个作者就不能合力统一成一个短小精悍的可以干掉Guice的IOC/AOP模块? 集成式环境的另一个问题是增加学习难度,普通人没时间学这么多环境,只能挑一两个深入用下去。如果这一两个又不符合业界标准(JSR/AOP等),知识不通用,则更头痛。你列了一堆Java web框架,但正常人有几个一个个全去了解的,只能挑最流行的,因为最流行的往往也是bug最少、最好学、最好用的、或功能最全的。你的ACT也许是性能最好,但依赖太多(Maven上30个依赖,还是compiled类型的,发布包2M多),这对于普通人来说是个巨大的学习负担,而jFinal走的是平民化路线,虽然功能架构上过分精简,但好学好用,所以流行也是正常的。 我瞎说一下理想中的后台环境,又打不下了,见外链 https://my.oschina.net/drinkjava2/blog/3147890
2019/12/26 22:45
回复
举报
谢谢回复, 我更新了博文来回答你的评论, 参见第 5 节
2019/12/27 10:06
回复
举报
"TransactionInterceptor和其他数据库访问组件没有关系",这不对,TransactionInterceptor可以指定事务管理器如DataSourceTransactionManager,它们最终要求DAO工具必须使用DataSourceUtils来获取连接。在所有spring桥接工具中都会找到DataSourceUtils.getConnection这个方法,它和上述管理器是配对使用,不能说没有关系。 mybatis-spring里看不到调用TransactionInterceptor,是因为TransactionInterceptor在IOC/AOP工具配置切面时才会用到,所以当然桥接工具甚至IOC/AOP工具源码里看不到TransactionInterceptor。 "AOP联盟标准对于数据库访问组件来说是不可知的",这个对,但是针对具体的某个AOP联盟切面处理类,还是要知道DAO调用了它的哪些方法,决不能错,对于Spring来说就是DataSourceUtils的方法。 "jBeanBox谈不上使用了Spring的事务",请见原贴链接,新版的jBeanBox为了清爽起见,已删除这个演示。在github的2.4.2 release版test5目录可找到这个演示。 DAO工具不会因为Spring采用了AOP...而避免事务机制的集成工作。这句话是对的,但是,集成工作也分重侵入和轻侵入,对于最常见的DataSourceTransactionManager,要求DAO工具的connection从DataSourceUtils中取和放,改造很容易,因为所有DAO工具都有这个调用。而ACT,则要求DAO工具符合JPA之类的API接口,这侵入太重,如果要对纯JDBC、DbUtils实现声明式事务怎么处理? 框架是否开放应考察对业界标准支持。个人认为主要体现在IOC/AOP这块,因为这是插件互换的基础(比如ACT使用Spring的事务),你列的那些Web框架,有一个不能与Spring集成的吗? 依赖的数量从来不是 Act 的关注点。这点我不赞同,依赖太多,不光学习理解困难,还会频繁更新,让人有学不动的感觉。我个人倾向于jFinal这种极简模式,当然,MVC、IOC/AOP、DAO要分开来发布,这样更开放合理。
2019/12/27 15:46
回复
举报
你说的"集成工作也分重侵入和轻侵入", 这句话我赞同, 以后可以再 act-sql-common 中对那三个抽象方法提供默认实现, 直接对 Context 中的 DataSource 做操作. 其他的就见仁见智了.
2019/12/27 17:19
回复
举报
嗯,每个人心目中的理想架构不一样,不能强求,但这种小的细节上能达成共识也算是没有白花时间讨论了。共勉。
2019/12/28 00:38
回复
举报
回复 @yong9981 : 共勉!
2019/12/28 03:54
回复
举报
自已再更正一个说错的地方,iOC内核出现JPA代码这句话不对,是我记错了,为了确认,又重看了下源码,spring-core内核中没有JPA代码,只是有个IdGenerator,而且有个实现调用了UUID类,但实际上与JPA毫无关联,可能当时这个类给我造成误会。
2019/12/29 01:19
回复
举报
罗神的回复精彩且犀利,给出了逻辑严密的论证过程;等同于象棋的将军;
逐条打破了 yong9981 与某部分人 自认为清晰的认识与本质。
2019/12/26 13:24
回复
举报
IOC 和 AOP 是独立俩个不互相依赖的概念和技术实现,这一点很清楚,可惜被国内某些大佬给弄混了

另外,作为BeetlSQL作者,文章分析事物AOP是分析到位的
2019/12/26 13:02
回复
举报
新东西总是要加入作者自己的理解的
如果完全复制spring
那只是在做无用功罢了
不做也罢
2019/12/26 10:09
回复
举报
牛皮
2019/12/26 09:14
回复
举报
我断言 yong9981 这位兄弟,只懂得使用 spring ,估计连 spring 如何实现AOP都没有去了解过.
2019/12/26 08:58
回复
举报
yong9981 是多个 Java 后端组件开源项目的作者: https://gitee.com/drinkjava2
2019/12/26 09:11
回复
举报
打破0回复
2019/12/26 08:34
回复
举报
更多评论
打赏
15 评论
1 收藏
7
分享
返回顶部
顶部