xml java 关系_java的那些框架曾经疯狂地使用xml,说是不用硬编码,但现在怎么感觉又“去xml化”回归硬编码了?...

从10年前Spring、Struts、Hibernate 框架的xml虐过的人来回答一下:

这些东西就是妥妥的弯路。

但是确实不可避免之弯路,历史之必然也。

可以说不光是Java的后端开发,实际上整个计算机科学技术和工程化历史上,就不断上演着重复历史的一幕。

首先说一下,xml文件确实意味着松散的耦合。当然随之而来的就是反射降低性能的代价。可以看得出来,当时的需求已经是那种变更大于性能的优先级,当然这些都是有救的,毕竟xml本身是用于表达的语言而不是用于编程的语言。那么对于当时的Java语言(1.5以前的版本)来说,可描述性、动态性、模板化是极其缺失的特性。需要澄清问题里一个概念,不使用xml其实不能叫做“硬编码”,硬编码其实和使用xml与否没有什么必然联系。

之所以使用xml,是因为xml语言无关性、及其出色的对象的表达能力、可嵌套的树结构,可以借助简单的标签语法通过组合来搭建出任意想要的对象和结构。

因此,社区里无论是开发工具也好,比如Tomcat、JSP这类容器技术或者模板技术,或者构建工具也好,比如ant、maven,或者后端开发技术,比如SSH、SSM组合,都是以大量xml作为配置文件来对一些简单对象和行为进行描述,因此才会出现前面所说的“硬编码”问题。xml文件确实可以提供一定程度的动态化,这也是社区使用xml的理由之一。

说明在这些开源社区中,xml的方式对内容的表达性和解耦性在当时还是受到广泛的认可的。包括后来的Android系统,也是一样。

要说到原因,其实这跟当年 Java 语言本身的太简练有点关系。在Java 1.5以前的是不存在注解这种东西的,因此在需要元编程的领域里,在程序中跟语言无关的“动态语言”、“模板化”和“描述语言”的支持是缺失的,这极大的限制了Java本身在协议性的开发上的简化和创想。因此,在个中权衡之后,xml毫无疑问就成为了“动态语言”、“模板化”和“描述语言”上的最佳选择。

事实上,那个时候的Java还有一条路可以走,就是Scala的路。将面向对象和函数式混合到一种语言之中,可以从另一个层面上解决“动态语言”、“模板化”和“描述语言”缺失的问题。但这样的话就会导致另一个问题:函数式编程思维事实上更难,实现细节黑箱化,语言更加语义化而不是形象化,对象内部的状态更加不可变,使用不当的话,实质上可能会增加很多工作,且有可能更加难以调试。

因此JCP一直是对函数式特性的引入持有非常非常谨慎的态度。以至于到了Oracle从Sun手里收购Java以后,函数式特性在Java 8版本中才得以引入,使得C#在函数式的支持上捷足先登。

重新回到xml的话题上。Java 1.5版本引入了注解,Java 1.6版本增强了注解和反射的功能,更加广泛的注解支持可以在函数、接口、变量、方法、注解上任意添加,使得Java在语言层面本身具有描述性。结合反射可以编写基于接口和注解的代码生成器,用于编译时生成代码。即便不使用生成代码,也提供了在运行时的代码的动态性。在这一点上使得过去需要在xml中语义描述的内容,使用注解就可以实现。注解的对象可以是具体化的,也可以是抽象的,使得语言本身的描述性、动态性和灵活性提供了极大的提升。

然而,xml的缺点在使用多年以后也逐渐显现,当xml文件中需要描述的内容越多,产生的尖括号越多,在阅读上和心理上产生了极大的障碍。人这种生物,大多是讨厌看到尖锐物或者恐惧看到尖锐物的,当阅读大量的尖括号括起来的内容时,会产生不自觉的烦躁感,阅读时也会因为尖括号数量的内容占比过高,造成过大的心智负担时,社区里就会越来越多抗拒使用xml的声音。

那么,由谁来代替呢?

统共分两个方向发展:

第1种:元编程方法。也就是注解、反射的使用。编写大量的“首次载入”程序,用于反序列化原先想要使用xml表达的内容,此时可以用注解+接口的形式来替代xml作为描述语言,提高可读性。实际上,自从Spring 从3开始,就已经支持了注解的使用。而使用xml的大多是遗留下来的陈旧系统和过于老的运行时。那些老旧的系统和运行时实际上是无法找到其他的组件或者语言作为xml的替代品的。这也是Java世界里使用时间较多较长且稳定的替代方案。对于一个设计较为复杂的注解程序来说,注解了注解的结构本身,就成为描述上的描述结构。使用简单,理解背后的原理可能会增加一些难度。当注解注解不慎,导致注解和注解之间的形成了一些语义上的交集关系,要处理起来的问题可能会更加复杂,且容易出错。因此使用注解本身没有什么问题,尽管它有着相当程度的黑箱化,对于注解功能的设计者来说要慎重。尤其对于注解注解接口来说要更加慎重。因为一旦设计不当,可能会导致使用者在用于描述类作为配置类的时候,可能会出现莫名其妙的问题。不过对于一般的设计者来说这种问题只要稍微小心一点就不会发生。这种方式对于使用者来说,可以普遍意义上的使用,只要会使用该语言,只要简单的进行注解配置就可以轻松使用框架提供的功能,且极少需要调试注解本身提供的功能。对于调试非常友好。

第2种:函数式编程方法。这种方法不同于声明式语言和命令式语言,函数式语言本身是将函数作为第一等公民的,所有让语言有可描述性的方法,注解成了可有可无的东西,而更多的工作都承载到了回调函数的身上。让lambda结构层层嵌套化,从整个框架设计本身开始就设计好内部结构,到了使用时,只需要设计好需要调用的函数与闭包内逻辑,闭包内的逻辑会被当做参数传入到函数中,将函数层层递进传入。所以从函数式语言看来,只有一个根节点结构的描述性的“脚本”,实质上只调用了一个函数而已。而剩下的逻辑就是“由外而内”的结构,层层传参了而已,传的参还都是函数。这种函数式的方法不但没有超出编程语言本身的设计,没有使用反射等消耗资源的逻辑,还增加了相当的可阅读性,节省了“设计模式”的时间,没有那么多的尖括号,只需要保留闭包作为逻辑分界的代码块的大括号即可。绕过了可描述性这个难以解决的问题。因此其实可以看到,对于相对更加完善的函数式语言来说,设计良好的DSL语言逻辑是比使用反射和元编程方法更加简练、整洁的代码之道。设计DSL会不会有问题呢?会。问题也不小。不但问题不小,而且对运行时的开发者来说,开源就成了近乎必须的选择。为什么会这样呢?基于注解的元编程方法是将语言本身可描述化。而函数式则不一样,函数式是由外向内,函数调用的结构决定了要描述的本身。因此框架在设计时,运行时的代码必须要透明才能做到调用关系明确。因此由函数式语言设计的DSL本身其实也是一种黑箱。对于熟手来说,精准无误的使用会有十分大的效率提高。但是对于生手来说,数据结构的黑箱性和调试的难度会大大拉高使用门槛。

这几种方法都各自有利有弊。历史的发展就是不断的轮回、扬弃、权衡的结果,就个人来讲更喜欢的是函数式方法,所有的东西做好后只要参照手册简单的配置就能实现很多动态化内容的变更。缺点其实也有运行时效率可能出现底下的情况。假如说对底层过度封装并且底层有着复杂的数据结构,运行时的分析过程可能会耗时过长。在这一点上并不一定比xml明确的定义结构更好。

在动态化过程中效率低下的部分,有时候可以使用生成器去生成代码来代替。这就是为什么权衡运行效率和开发效率的时候会有不同的选择。如果运行效率更重要,代码生成器生成静态代码比动态分析的代码效率更高。毕竟函数式的DSL的设计比较黑箱,谁也不知道背后运行时设计的运行效率如何。注解+反射的方式也有一定的效率损失,但是开发过程快,毕竟是运行时元编程,解析结构会有一点时间上的损耗。

总结起来说,其实在java或者说类似的语言的发展过程中,xml的方式、注解、函数式,其实更可以看做一套发展过程。

新的技术点出现 --->XML--->注解元编程--->函数式/DSL

所以其实在某个语言体系中出现某个新的技术后,基本上都遵循这个发展历程。只不过注解这个过程是可有可无的。毕竟很多语言没有注解这种东西。

越是设计简单的语言,这个过程发展的就快,最终到达函数式。越是设计的特性复杂的语言,这个过程发展的就慢,越复杂越慢。

更新:

如果有人有过编写xml的经验并且同时有用某种DSL脚本配置的经验,比方说Groovy on Grails、Gradle这类使用Groovy这种动态函数式语言做配置脚本。可以考虑对比一下XML和函数式DSL脚本,仔细观察他们的结构。

你会发现,

xml不就是函数式DSL语言么。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值