回 Yong9981 关于 Act-1.8.32 发布新闻中的评论

原创
2020/03/10 16:09
阅读数 1.6K

@yong9981actframework-1.8.32 发布新闻 中提出了一下问题:

我的回应是:

然后 yong 同学非常热心的贴了下面这段评论:

这里面我用红色标注出两段有趣的论点:

你提供的功能和SpringBoot/JBoot有很多重叠的部分

这个有问题吗? 还是不应该重复发明轮子?

SpringBoot的基本原理是用一个通用的IOC(AOP)工具配置一堆第三方工具,这些第三方工具原本和SpringBoot没有一毛钱关系,只是因为它们的优秀被Spring看中整合进来,你的竟争对手是这么个集成体系,而且它随时可以添加新的模块进来,因为本质上通用IOC工具的作用就是用来初始化Bean的。它们有个共同的特点:没有源码,所以比较适合用YML/XML或Java方式配置,而不是用JSR330注入。

这段话里面包含下面几段陈述:

  1. SpringBoot 就是一个通用 IOC 工具 - 如果你的 SpringBoot 包含了 SpringFramework, 嗯, 我部分认同这个观点.
  2. Spring 三方工具的共同特点: 没有源码 - 真的吗? @yong9981 同学? 请问你有没有去 https://github.com/spring-projects 看看里面的三方插件有没有源码?
  3. Spring 三方工具比较适合用 YML/XML 或者 Java 方式配置 - 您的 point 在哪里? Act 和 Act 的插件都使用 properties + Java 方式配置, 不行吗?
  4. Spring 三方工具不适用 JSR 330 注入 - Spring 开始自己的 DI 框架的时候 JSR330 还不存在, 因此 Spring 自成体系, 可是还是那句话, 您在这里到底想表达什么意思?

至于 yong 同学在上面说的建议, 我的建议是:

接下来 yong 同学讲到:

类Guice的配置不如Spring配置通用和方便

类 Guice 配置是 Java 的标准, 不如 Spring 配置通用是现状, 不如 Spring 配置方便我不认同.

需要开发插件往往是要用AOP功能的,如果不需要AOP,直接new就行了。比方说spring-data-mongodb也实现了AOP联盟标准,所以Guice也可以拿来用的,但你看Genie能不能拿来用?这是个架构问题,导致所有spring插件你都必须重新开发一个,偷懒都偷不了

@yong9981 这是我最后一次就 IOC 和 AOP 的问题回答你, 我们以前已经就此问题讨论多次: 2019-11-30 的讨论, 2019-12-25 的讨论, 2019-12-26 的讨论

  1. 我第一次接触 AOP 大概在 2001 年, 一篇讲 LoD (Law of Demeter) 的文章将我带到了 aspectj 的项目网页, 当时 aspectj 还是 Xerox Palo Alto 实验室的内部项目, AOP 联盟还不知道在什么地方. 我一开始也为 AOP 感到兴奋, 认定这是对 OOP 之后的一次革新. 在随后的几年中, 我一直关注 AOP 的发展. 当 aop 创始人自己开始和一个微软的大佬搞新的 intentional programming, 我又开始追这颗新星, 但一直到现在 intentional programming 也没有任何实际的东西出来. 我讲这些的目的是为了告诉你, 我理解 AOP, 也知道这是干什么用到, 我第一次尝试 aspectj weaver 的时候可能比你接触 AOP 还要早. 所以请你不要再用你 充满误解 的思想给我推销 AOP. 当我有时间和动机为 Act 提供 AOP 支持的时候我会去做, 但绝对不是在 Genie 里面. 我对 DI/IoC/AOP 这些概念有非常清楚的认识, 而且我相信这些认识和业界对这些概念的公识是一致的. 顺便劝告你一句, 到维基百科或者其他权威站点温习一下这三个概念.

  2. 更重要的不要再用支持了 AOP 就支持了 Spring 生态中的三方插件这种很二的观点来推销 AOP. 我已经讲过多次, Java 各个生态之间不会因为支持 AOP 或者其他任何规范就能够相互共享三方插件. 也许就像你给出的例子, 可能会有人出于学习或者技术展示目的搞出类似 https://github.com/mohitsinha/play-java-spring-data-mongodb 这样的项目, 我想说实际情况是生态之间并不共享插件. 另一方面生态之间共享规范, 也共享一些工具库, 比如 Hibernate, MyBatis 之类的, 但要将工具库集成进真正的生态, 必须提供插件, Spring 这样做, Play 这样做, Act 也这样做. 没有人因为支持 AOP 就可以原封不动的使用另一个生态的插件. 即便是上面那个 play-java-spring-data-mongodb 这样的 demo 项目, 也需要花费数百行代码, 而不是像你说的"共同的特点:没有源码". 你在回复中举的另一个例子 http://kasparov.skife.org/blog/src/java/guice-with-spring-transactions.html 是很辛苦寻找出来的吧, 一篇 2007 年的 blog, 有任何实际价值吗? 还是就像博主说的:

最后来看看你的项目:

那个 Guice/jBeanBox 实现 Spring 声明式自动回滚事务的就不多说了, Genie 没有实现 AOP, 所以我做不了. 但放在一个更大的 Context 下, Act-Db 是可以做自动事务回滚的, 这是不同的生态. 再次强调, 别让我去支持 Spring 机制, 我不会容忍在 Act 代码里面引入一大堆 Spring jar 文件这样的事情 更别让我因为要支持 Spring 机制, 所以在 Genie 中实现 AOP!

你项目这个 Question 倒可以谈谈:

我第一眼看这个代码就想问: 干嘛要先搞个

	public static class CarConfig extends BeanBox {
		{
			this.injectConstruct(car, String.class, color);
		}
	}

然后

Car car = JBEANBOX.getBean(CarConfig.class);

直接

Car car = new Car1(color);

它不香吗?

yong9981 大概要说, 没看见我的 colorcar 都标注有 "// read from file" 吗?

那我的回答是, 有多么蠢的项目才会这样来配置?

直接在配置文件里面给出实现类的情况有没有? 当然有, 比如你的数据库插件可能是 Hibernate 的, 也可能是 MongoDB 的, 所以会在配置文件中指定数据库插件实现类. 另一个例子, 假设你使用 osgl-storage 来存放上传文件, 你会在配置文件中指定存储系统是本地文件系统, 还是七牛云, 或者 Azure Blog, 这种情况, 也需要直接在配置中给出存储服务的实现类. 然而这些情况的共同特点是都是 Heavy load, 需要的配置和初始化, 绝不仅仅用一个构造函数就搞定的. 为应用完成重型对象配置和初始化工作正是插件的价值.

那 DI 注入本身有没有价值呢? 当然有, 假设你有两个数据库服务配置:

db.instances=sales,marketing

db.sales.implementation=org...HibernateService
db.sales.url=<jdbc-url to sales db>

db.marketing.implemenation=org...MorphiaService
db.marketing.url=<jdbc-url to  marketing db>

DI 的威力就在于代码可以清晰地指定需求而无需考虑如何准备这个需求, 例如:

public class XyzService {
	@Named("sales")
	@Inject
	private DataSource salesDs;
	
	@Named("marketing")
	@Inject
	private DataSource marketingDs;
}

当然我注意到 yong9981 在代码中演示的特性是 "使用外部工具时,比如说A中要注入B属性,B的构造器要注入C对象这种, 而且A,B,C全是第三方工具,拿不到源码,所以不能使用注解方式去配置。". 我觉得这里面需要考虑的一个问题是 这种没有考虑依赖注入的代码是否值得 DI 工具去适配, 或者需要适配到何种地步

jBeanbox 的 API 提供了一种比较粗糙的 API 包装来强行适配这种场景 (估计实际案例中很少会出现吧):

其中全然没有考虑一点类型安全, 在其演示代码中, 做一点改变, 让 Car1 不再继承 Car, 即下面代码中的 car 不再是 Car 的实例:

在编译时没有办法检测到这样的问题. 知道运行时才会在这里抛出错误:

这样的代码貌似简洁 (其实在所有已知条件下, 反而不如直接用 new 操作符), 但把 Java 这种类型安全语言当做动态语言来使用, 实际项目上怕是有点隐隐担忧吧.

从我看来, DI 工具为所谓三方库提供这样的适配是得不偿失的. 那 Genie 能否处理 Contructor binding 呢, 当然是可以的. 下面是 Genie 的实现代码:

这里我们看到了几个地方的不同, 首先将 Car.class 绑定到 Car1.class 的过程是类型安全的. 我们把 Car1 改写, 让其不要继承 Car, 我们发现 IDE 会有错误提示:

其次, 我们并没有直接向构造函数绑定中去写某个具体的值 e.g "red", 而是通过 @Named 注解来告诉 DI 引擎, 当你遇到名字为 color 的字串的时候, 提供 red 这个值. 这样的做法看起来有这样的问题, 如果你的构造函数参数上面没有 @Named 注解, 那就没法绑定到需要的值了. 在此我想强调的是依赖注入处理的应用程序逻辑拓扑, 并不是数据. 每个注入的对象都应该是一个特定概念, 构造函数绑定也不应该脱离这个观念. 因此你注入的对象要不应当是一个特定类型, 要不是普通数据类型假设某个 Qualifier (比如 @Named) 来限定这个概念范围.

设想你有下面的构造函数:

public class MyAwsS3ServiceAgent {
    private String awsKey;
	private String awsSecret;
	public MyService(@Named("aws.key") awsKey, @Named("aws.Secret") awsSecret) {
		this.awsKey = awsKey;
		this.awsSecret = awsSecret;
	}
}

在构造函数中清楚地地指定了参数的概念, 因此我们可以让 DI 引擎发现其中的逻辑关系并提供需要的值绑定. 看官可能要问, 如果我用的是很老的库, 的确没有 @Named 这样的机制怎么办. 我的回答是应用提供一层 Wrapper 来封装这个库, 适配到 DI 引擎. 这种做法的优势在于所有的概念都很清晰, 所有的值来源也很透明. 代码的可维护性和采用 jBeanBox.injectConstruct 这样晦涩不清的 API 相比不可同日而语.

另一方面, yong9981 的代码除了有两句注释说明 car 和 color 的值来自配置文件, 但其实根本没有演示如何把配置从文件加载进 JVM 的. 下面的代码演示 Genie 是如何处理这个过程的.

为了更好地展示 Genie 这方面的能力, 我们在 Car 中添加一个整型字段: seats (座位数):

我们在 test/resources/test.properties 文件中准备好配置数据:

然后我们需要适配 Genie 提供的 ConfigurationLoader 机制到这个配置文件中:

注意上面的适配机制每个应用只需要完成一次即可. 下面是绑定和测试代码:

注意到 Genie 的配置机制很聪明地将配置文件中的 "6" 变成需要的整型变量 6 了吗? ActFramework 中大量使用了这样的机制. 大家可以参考一下这个演示项目

总结一下: 提供工具库, 比如 Genie 这样的 DI 引擎, 我们应该仔细思索提供这个工具的目的是什么, DI 的目的到底是什么, 在什么层面上可以帮助应用程序, 使用这个工具是否有利于应用程序的代码组织, 维护, 是否鼓励更好的编码方式. 毫无选择地提供粗糙的封装 API 只能让工具沦为没有价值的玩具

最后, 文中的代码都在 https://gitee.com/greenlaw110/GuiceSpringTx 项目中, 有兴趣的朋友可以 Checkout 出来看看

展开阅读全文
打赏
0
1 收藏
分享
加载中
2001 我10岁,正在上初中,没接触过aop.
2020/03/11 10:53
回复
举报
重点在AOP这块,不是让你支持Spring,而是支持AOP联盟,我只是指出个功能缺失点,如果你认为AOP联盟没必要支持,没问题,你可以坚持(正如JFinal/Nutz所做),但相比于Spring/Guice生态,这是个扣分项。

@Named和@Qualifier注解相当于在程序里声明一个null值变量, 挖一个坑,然后在配置文件里去填这个坑。这个理念实际上并不高明,想一想现代语言出现的强制非空指针特性吧。看到@Named时,还不能用IDE跳转到配置文件上,看看到底配了个啥,这个Car car=genie.get(Car.class);也有这个问题, 定位不到配置文件上。

关于IOC工具配置问题的比较,我不想太强调,因为简洁性不是重点,这个可以让其它人来判断到底是injectConstruct好用还是bind用得舒服。一个软件比另一个软件更好用,不是一个技术问题,而是一个使用习惯问题,这个和AOP联盟功能缺失的重要性不一样的。
2020/03/10 22:18
回复
举报
"重点在AOP这块,不是让你支持Spring,而是支持AOP联盟" - 在你的整个评论(包括以前几篇博客中)你对"为什么要支持 AOP 联盟"的论据都提到可以不写代码就支持 Spring 的声明式事务. 且不说你这个论据本身是否充分 (真的不写代码吗?), 你的立论非常充分地表明了你的主张: 支持 Spring 生态. 我一直不停地在强调你这个观点的错误: 生态之间并不共享插件
2020/03/11 03:21
回复
举报
我说的是声明式事务服务本身,当然要使用它之前,你要了解这个服务的使用方式吧? 比方说DAO工具要采用AOP服务提供的工具去从数据源取和关Connection,仅此而已,但是事务部分确实是一行代码都不用写的。生态之间并不共享插件,我那个例子已经说明了,生态之间是可以共享插件的,当然,我说的插件指的是服务本身,这些服务本身有可能非常复杂,并不是你理解的适配器就当成插件。
2020/03/11 05:30
回复
举报
我在文章已经提到了, 生态之间共享工具库, 比如 hibernate, MyBatis. 但 hibernate, MyBatis 他们是插件吗? spring-data-hibernate 是插件, act-hibernate 是插件; 所谓插件, 是将某个功能插入(plug-in)到某个框架(生态). 纵观 Java 生态圈, 我并没有看到不同生态直接共享插件的**事实**. 关于你的例子: 第一, 这是个 demo, 是个玩具, 第二, 这个 demo 也花费了数百行代码. 所以请不要继续再提这个例子.
2020/03/11 06:36
回复
举报
首先,这不是个玩具,只要有一个人在项目中用了,它就不再是个玩具,你能确保以后用Guice的人不采用这种方式引入Spring的事务? 其次,这是guice的原话:"Guice supports AOP Alliance method interceptors which means you can use Spring's popular transaction interceptor."
2020/03/11 06:47
回复
举报
第一, 工具和应用是不同层面的东西, 工具追求的是重用 - 需要扩散使用者范围, 应用追求的是解决当下问题. 第二, Guice 本身不是生态, Guice 是工具. 如果有人使用 Guice 来引入 Spring 事务, 这个人还是在 Spring 生态中; 某个项目使用 Guice 来引入 Spring 事务和 Play 生态使用 Guice 来引入 Spring 事务是两个不同层面的概念. 我可以理解有项目这样做(估计不会太多), 但我不同意 Play 生态会采取这种做法.
2020/03/11 06:55
回复
举报
回过来谈是否有必要支持 AOP 联盟. 我的想法是如果要支持 AOP, 那就必须支持 AOP 联盟, 因为只是事实上的标准. 然而 AOP 是否是一个必须的功能, 我对此表示怀疑. 我在 https://my.oschina.net/greenlaw110/blog/3147470 一文中详细阐述了这个观点. 当然你说是个扣分项, 我不能反驳, 毕竟是少了一个功能. 所以我也多次谈到, 在有时间有动机的时候我会为 Act 提供 AOP 支持.
2020/03/11 03:24
回复
举报
Guice和Spring都支持AOP联盟,我觉得这个理由够了,正如你说,不是不可以挑战标准权威, 但要挑战的话应该给出逻辑完备的理由。
2020/03/11 05:33
回复
举报
不支持 AOP 联盟就叫挑战权威? 这是不是有点太可笑了. 随便可以列出一堆因为不支持 AOP 联盟 **挑战标准权威** 的 Java 圈子: JFinal, Nutz, Jooby, Jaw, Quakus, Playframework. AOP 联盟的存在不是 Java Web 框架必须提供支持的理由, 也不是 DI 引擎必须支持的理由, 否则 JSR 330 大可以把支持 AOP 联盟放进标准. 我现在有点质疑你的逻辑能力.
2020/03/11 06:42
回复
举报
"@Named和@Qualifier注解相当于在程序里声明一个null值变量, 挖一个坑,然后在配置文件里去填这个坑。" - 完全看不出你的逻辑在哪里. `@Named` 和更一般性的 `@Qualifier` 是 JSR 330 提供的工具, 其目的在于将连续的值空间分割为离散的概念范围, 更加清楚地表达注入的逻辑概念.我不是说不可以挑战标准权威, 但如果你要挑战的话应该给出逻辑完备的理由
2020/03/11 03:30
回复
举报
我不是挑战,因为我的IOC工具也随大流支持这两个注解。但前者不支持IDE定位,后者使用麻烦。数量少看不出来,当有100个不同名字的@Named或@Qualifier出现在一个项目中时就知道了。我建议的方案是例子中数据源的配置。
2020/03/11 06:25
回复
举报
100 个不同名字的 @Named? 你到底有没有理解什么是 @Named 和 @Qualifier 以及它们的适用空间?
2020/03/11 06:44
回复
举报
"我不是挑战,因为我的IOC工具也随大流支持这两个注解" 你支持和你挑战这两者没有必然联系. 你提出 "@Named和@Qualifier注解相当于在程序里声明一个null值变量, 挖一个坑" 这种(无稽)说法, 说明你不认同这两个概念 - 这是你的挑战; 尽管你不认同, 你依然 "随大流" 去支持 - 这是你不能坚定自己的信念.
2020/03/11 06:49
回复
举报
这和信念有什么关系,这叫后向兼容而已。
2020/03/11 06:54
回复
举报
该评论暂时无法显示,详情咨询 QQ 群:912889742
好了,就此打住吧,我没时间了,谢谢你花了这么多的时间认真回复,反正我的意思都说明了,我是来提意见的,决策权在你,并不需要说服我,除了AOP联盟这块外,其它的我并不觉得太重要,无非是new的n种写法而已,如果你觉得好就保持下去就可以了。
2020/03/11 08:07
回复
举报
也谢谢你的意见. 总的来说我相信道理越辩越明. 至于说服对方并不是目的, 在讨论过程中理清自己的思路和理念才是更重要的. 最后 "new 的 n 种写法" - 这个我完全不同意. DI 的价值被你这句话倒是弃得干干净净
2020/03/11 08:29
回复
举报
"看到@Named时,还不能用IDE跳转到配置文件上,看看到底配了个啥,这个Car car=genie.get(Car.class);也有这个问题, 定位不到配置文件上。" - `@Named` 和配置文件没有关系. 在我文中的第二个例子 `@Configuration` 的确和配置有一定的关系 - 通过 `ConfigurationLoader` 这个类来 indirect 的. 因为没有直接联系, 因此 IDE 的支持是个问题. 但这是个多大的问题呢?
2020/03/11 03:36
回复
举报
"因为简洁性不是重点,这个可以让其它人来判断到底是injectConstruct好用还是bind用得舒服。一个软件比另一个软件更好用,不是一个技术问题,而是一个使用习惯问题" - 这里绝不是一个舒服与否的问题. 首当其冲的就是 injectConstruct 明明在可以保障类型安全的时候丢弃变量类型声明, 让一个可以在编译时检测的错误留作运行时异常, 这不能接受;
2020/03/11 03:40
回复
举报
该评论暂时无法显示,详情咨询 QQ 群:912889742
开源需要这么认真的态度,赞
2020/03/10 21:45
回复
举报
牛皮
2020/03/10 16:50
回复
举报
😳
2020/03/10 16:38
回复
举报
更多评论
打赏
24 评论
1 收藏
0
分享
返回顶部
顶部