2年前 (2022-06-15)  Java系列 |   抢沙发  363 
文章评分 0 次,平均分 0.0
[收起] 文章目录

曾几何时,Spring框架提供了比J2EE更轻量级和更灵活的解决方案。即使在2013年左右,我也很高兴详细了解当时的新款Spring 4。如今,7年后,当我看到春天的时候,我感到一阵恐慌。注释和@ComponentScan已经用更好的东西取代了XML,这需要一个可视化工具来理解您的系统。Spring变成了一头不断生长(和变化)的水螅。我接手并试图理解他人编写的Spring应用程序,这让我很痛苦。最后但并非最不重要的一点是,Clojure教会了我代码可以/应该是多么简单。那么,我对Spring的主要问题是什么?

(为了明确起见,当我说Spring时,我指的主要是Spring控制反转(IoC)及其依赖注入子集。)

Spring的缺点

不可理解性

系统构成的黑箱

注释和组件扫描对于让应用程序快速启动和运行来说非常棒。但当您试图理解所述应用程序时,它们是一场噩梦。由于@Configurations@bean来自您的代码库、公司库中的任何地方,并且可能来自±100MB的Spring依赖项(真实情况),因此不可能清楚地了解应用程序的结构和配置。您基本上需要一次又一次地为Spring和所有库进行RTFM,以便记住可能需要的所有内容,并通读整个代码库。诸如IntelliJ SpringBeans视图之类的工具可能会有所帮助(如果您能够正常工作的话)。

在这里,我完全同意Python的禅宗“显式优于隐式”

在一个典型的Clojure项目中,我转到核心/主名称空间,其中主函数启动服务器并为其提供一个处理函数,可能(手动)使用一些中间件包装,可能在内部使用库进行路由。我还可能阅读配置并传递它。我可以轻松地单击浏览代码,并查看代码中的具体部分以及它们是如何协同工作的。即使是在cljdoc这样的大型系统中。org有一个主功能启动服务器(这里是Integrant),并为其提供配置和“系统定义”(类似于Springbean树)。一切都是明确的和可导航的。

按注释编程

能够向方法和类添加元数据非常棒。我对@GetMapping(“/”)之类的东西没有任何反对意见。但它通常被用来绕过Java的限制,并通过诸如@Scheduled@Transactional之类的注释实现横切关注点。我曾经是AOP的坚定支持者,由于语言的限制,AOP仍然是Java开发人员不可或缺的工具,但我也意识到它的成本不容忽视。问题是,您无法轻松看到它在做什么(因为它什么都没做,只是数据)。为了给您一个透视图,替换@Transactional Person findPersonInDb(String personId) {..}在Clojure中,我只需要用一个自定义宏来包装它,例如:

(defn find-person-in-db [person-id]
  (transactional
    ...))

实质性的区别是,我可以控制单击导航到宏,并查看它在做什么,因此所有行为都在那里,供我检查和理解。祝你在Spring找到答案!

故障排除

Spring是让很多事情快速起步的好时机——直到某些事情失败或不按预期工作为止。Spring是一个松散耦合的意大利面大球,在我痛苦的经历中,解决它是非常困难的。有很多文档,但我经常找不到我需要的答案。也许官方文件太肤浅,有时依赖于大量已有的知识。搜索互联网有时提供了一个解决方案,有时至少提供了有用的指针,有时提供了误导性/旧信息,有时什么都没有。在进行故障排除时,您需要艰难地通过这个庞大复杂的类,这些类以某种方式协同工作(或应该协同工作),以(似乎)神秘的方式受到类路径上的jar和@Configurations的影响,并希望您能够偶然发现问题的原因。

例如,我花了相当长的时间来理解Spring MVC应用程序中的错误处理是如何工作的。我们有一个@ControllerAdvice(["myapp.endpoint.api"]) myapp.endpoint.api.advice.ErrorHandler调用的ErrorHandler(如果您记得抛出正确的异常类型),@Component myapp.spring.ErrorPagesCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>container.addErrorPages(new ErrorPage("/error"))错误发送到org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController,然后它将神奇地呈现我们的错误。类路径上的html。我意识到我再也不明白(如果我真的明白的话)它到底是如何工作的了。但要以所需的方式显示错误,无疑是一场斗争。

另一个难点是理解为什么Spring返回404而不是预期的文件。我对它的请求处理有很多来之不易的知识,也许有一天会成为一篇博客文章。

了解@Scheduled jobs实际上是如何调度的,并试图找出为什么一个作业没有按预期运行,以及增加线程池大小以使其不再被较慢的作业停止的神奇调用是什么,这需要几天的时间和多次失败的尝试,我甚至放弃了一次或两次。搜索互联网提供了一些帮助,但肯定远远不够。这是我的一般经验,与Spring有关。

在Spring升级后搜索突然出现的ClassNotFound运行时错误,试图找出哪个Spring JAR有这个类,哪个版本是正确的,或者如何更改配置以停止需要更改…​ 不,谢谢你。

为什么是Spring IoC?

这是一个由两部分组成的问题:为什么是Spring IoC?为什么是Spring?

许多开发人员都知道,Spring是一个成熟且流行的解决方案。它还为几乎所有问题提供了解决方案,这些解决方案通常能够很好地协同工作。您也可以快速开始。另一方面,你可能会争辩说,这是夸大其词,有遗留问题,而且——通过尝试为每个人做每件事——没有做得完美,更小、专注的解决方案可能更好(尽管你需要整合它们)。

为什么选择依赖注入和IoC?您可以从中找到许多原因:使用依赖注入和IoC容器的好处是什么?。其中一些是:

  1. 简化-您的类不需要知道如何创建它们的依赖项(以及它们需要什么)。您可以将实例化和连接类的问题分离出来。
  2. 灵活性—您现在可以提供不同的/修饰的实现。因此,在测试中,您可以提供模拟实现,在大型复杂系统中,通过交换新的实现(这部分取决于对接口的编程),您可以使用疑难解答装饰器包装依赖项,从而使其更容易逐步重构。
  3. 生命周期控制

查看维基百科上列出的优点和缺点。但它有明显的好处,这并不意味着你应该在任何地方、任何事情上都使用它。记住成本和劣势,做出有根据的决策。

我们通常在Clojure-f.ex中使用依赖注入。接下来是数据库访问库。jdbc要求您将目标数据源传递给每个调用。(这使您可以自由地创建自己的包装器来控制数据源并将其传递给库(如果您愿意的话)在Cognitect AWS API中构造AWS客户端时,可以让它创建默认的底层HTTP客户端,也可以提供自己的客户端,这样就可以覆盖默认的依赖关系。我们甚至有一些人喜欢使用的依赖注入框架,如组件和上下文,而其他经验丰富的开发人员则觉得它们不必要。

选择

当谈到Spring生态系统作为一个整体时,您应该始终应用Alex的合理库原则:不要添加库,直到没有库的痛苦如此之大,您无法忍受没有它的生活(并且在适当探索了其他选择之后)。

当您需要依赖注入时,最好手动组合系统。当前的小型微服务时代与诞生Spring的巨型应用时代截然不同。你可以自己做(为什么不?!)或者使用一种重量轻、重点突出的解决方案来解决更为手动的问题,例如Feather。尽可能喜欢编程配置。Feather仍然是由CDI的@Provides驱动的注释,但至少您在一个“module”类中声明了这些注释,您显式地向Feather注册了这些注释,并显式地向Feather请求所需的实例。在过去,我们使用Guice和手动调用来绑定每个微服务的主类。对于我这个当时经验丰富的Spring用户来说,这似乎很奇怪,也很错误,但我开始理解并欣赏它。甚至还有用于Spring Boot的(实验性)编程配置DSL JaFu(Kotlin,KoFu也有一个)。

据一些人说,Jakarta(Java EE的后代)是Spring生态系统的一个更干净、更小、更好的替代品。您还可以针对特定需求搜索单个解决方案。

我从一位受人尊敬的同事那里听到了Micronaut的好消息,它提供了低开销的DI和AOP、REST客户端/服务器、反应式、断路器等,但它仍然依赖(似乎)类路径扫描进行配置,因此保留了我在Spring中的主要问题。还有反应型Helidon SE,它具有“透明的”的开发经验;纯java应用程序开发,无注释,无依赖注入”与这两个领域相同的领域是Quarkus,但其IoC基于CDI,因此与Spring有相同的问题。Eclipse Vert.X 专注于反应式、事件驱动的应用程序有所不同,但提供了类似的功能(HTTP客户端/服务器、OpenAPI、GraphQL、DB访问、配置、断路器、安全性、度量),并具有编程配置;相反,它不提供依赖注入(但您可能不需要它(尽管总线本身也有问题))。

其他人在说什么

在为本文做研究时,我发现了一些值得分享的经验和观点。

著名的挪威软件架构师Johannes Brodwall写道(2013年,再次遥遥领先于我):

我发现DI容器给我的一些直觉让我改进了设计,但同时,我发现当我移除容器时,解决方案变小了(这很好!),更易于导航和理解,更易于测试。我发现使用容器的成本非常高,这会导致复杂性和大小的增加,以及一致性的降低。

在这场Quora讨论中,有很多好的建议:为什么大多数母语不是Java的程序员似乎对Spring框架持反对意见,他们对Spring框架的哲学有什么不喜欢的?

这方面的问题是,Spring有点破坏了我们从使用Java中获得的简单性好处。Spring谨慎地将复杂性引入到您的项目中,当它工作时,框架表面上很简单,但老实说,有多少人可以解释Spring中发生的事情?调试Spring错误通常看起来像是魔术,需要90%的猜测和模式匹配。Spring通常可以在项目开始时为您节省数周的工作时间,您可能会觉得这些好处是免费的,但事实并非如此。在某些方面,您可以将其与在项目中使用动态编程语言的早期好处进行类比。在项目的初始阶段,它会大大加快您的速度,但复杂性和技术债务会在稍后的阶段打击您。

(当然,他与动态编程语言的比较与我和其他人使用Clojure的经验相反。)

所以他们改成了基于注释的配置,被迫学习,但我还是不喜欢。它仍然不是真正的Java。许多事情都是靠魔法发生的——当它们没有发生时,它们都以同样的方式失败,什么都没有发生。因此,您编辑并重新编译,同样不会发生任何事情。无法判断您是否注释了错误的内容,或者您的注释所说的内容是否与您认为的不同,或者您是否构建了错误的测试。

如果有像样的文档,我还没有看到。我最近研究了RequestParam,它是Spring MVC中普遍存在的一部分,以了解其语义。该页面实际上是无用的。Javadoc是API通信的主要方式;即使有更好的文档存在,这个页面也不会告诉我太多。

关于现代Java应用程序:

Main方法有一行起始Spring和许多类,每个类有5个注释。它们是如何被实例化的,以什么顺序,如果出现问题,如何调试这个过程

下面是我最讨厌Spring的几件事:

它确实会减慢应用程序的启动时间

在运行应用程序之前,你不会知道你的应用程序是否正常工作——在大型商业环境中,这意味着最多需要一个小时的构建和部署时间

…然后会得到大量的stacktrace,其中一半与内部spring类有关

一旦您超越了简单的单例布线场景,Spring就会变得非常丑陋、非常脆弱和不可预测

这有助于您起步,但您走得越远,维护的噩梦就越大。

结论

那么,Spring是邪恶的,应该不惜任何代价避免吗?不需要。它使人们能够克服Java的局限性,并提供了许多库来解决实际问题。但它也是巨大、复杂的,而且维护成本很高。对引入库持怀疑态度,考虑多种解决方案,并为您的案例选择最佳解决方案,而不仅仅是Spring解决方案。即使在使用DI和IoC(Spring或其他)时,也要努力实现最大的透明度,并且更喜欢编程配置而不是类路径扫描。如果其他一切都失败了,请编写良好的(java)文档,以便您的继任者能够理解您的系统。

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2676.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册