领先一步
VMware 提供培训和认证,助你快速进步。
了解更多在感恩节周末,一篇名为 Sam Atkinson 的文章《我为何讨厌 Spring》 在 Twitter 上流传开来。倾听批评意见总是很有趣,可以看看我们能做些什么来改进 Spring,Spring Boot 的很大一部分就是听取了 人们谈论他们在框架中遇到的问题 后诞生的。
在这篇博文中,我将尝试回应 Sam 文章中讨论的一些担忧,并描述我个人的 Spring “最佳实践”。不过,在深入探讨细节之前,值得思考一下为什么框架中仍然存在一些非最优的做事方式。
在 Spring 开发中最令人头疼的方面之一(除了有人写文章说他们有多讨厌它)是,特性往往有很长的生命周期。Spring 对向后兼容性有着坚定的承诺,这意味着一旦添加了某个特性,就很难从根本上改变它。开发框架带来了一般应用程序开发者不会遇到的独特挑战。YAGNI _(You ain't gonna need it,你不会需要它)_ 和激进重构等敏捷技术更难应用。我们遵循 SMNI _(Someone might need it,某人可能需要它)_ 原则,并谨慎进行重构。在 Java 中还没有 lambda、泛型甚至注解时就存在的特性仍然受到支持。现在很容易说 XML 不是连接 bean 的好方法,但很难说应该完全删除它。我个人很看重我能够轻松升级旧的 Spring 应用程序并逐步迁移到新技术这一事实。
同样要记住,就像商业软件一样,开源软件也资源有限,面临时间压力。有时作为开发者,我们也会犯错误,最终需要与它们共存一段时间。
Spring 本质上是一个集成框架。它为许多不同的技术提供了一致的编程模型。公平地说,Spring 的确可能偏大,但如果不...集成许多技术,根本不可能提供与许多技术的集成。虽然它看起来令人不知所措,但一旦你理解了核心概念,就很容易只挑选你需要的那部分 Spring。如果你有一些特定的目标需要实现,spring.io 上的指南 可以作为有用的起点。Spring Boot starter 列表 或使用 start.spring.io 也是将 Spring 限制在特定领域的好方法。
典型的基于 Spring 的应用程序会依赖许多第三方库。过去,很难确切知道各种库的哪些版本能够良好协作。如果你今天开始一个新项目,我强烈建议使用 Spring Boot 的父 POM 或 Spring Platform。它们都提供了一组精心策划的依赖,这些依赖已知能够良好协作。
我们甚至开发了一个 Gradle 插件,让你在使用 POM 时即使没有 Maven 父依赖特性的优势也能使用托管依赖 部分。
如果你由于某种原因无法使用托管依赖,你仍然可以参考 Spring Boot 文档中 已知能够良好协作的兼容版本列表。
在你的 bean 中总是使用 基于构造函数的依赖注入。对于强制依赖,总是使用 断言。关于为什么基于字段的注入是邪恶的更多背景知识,你可以阅读 Oliver Gierke 的这篇文章。
像往常一样,这条规则有一个例外,当你 使用 SpringJUnit4ClassRunner
进行测试时,可以使用基于字段的注入。
我见过一些人认为应用程序代码绝不应该依赖于框架。也就是说,除了配置代码之外,任何地方出现 org.springframework
导入都是不好的。虽然你确实可以做到这一点(例如对 @Inject
等标准注解提供了广泛支持),但我倾向于不那么严格。我更喜欢在这里应用二八原则。我认为依赖于框架类和注解是可以的,但如果我要脱离 Spring,我不希望重写超过 20% 的代码。
我真正想要的是易于测试的应用程序代码。我应该能够手动创建我的业务服务实例并进行测试,而无需完整的 ApplicationContext
。
我个人喜欢保持我的 Spring 配置相当轻量。我喜欢使用组件扫描来查找应用程序 bean,并且喜欢将 @Configuration
用于那些偏离 Spring Boot 默认自动配置的框架特定配置。如今我完全不倾向于使用 XML 配置。
有些人认为为所有定义的 bean 进行显式配置更好,但在实践中我倾向于发现这是一种阻碍。遵循这种方法的应用程序往往有一个单一的 @Configuration
类,它最终了解系统中的所有内容,并包含大量小型 @Bean
方法。我也发现注解的自文档性质非常有用。我可以轻松搜索 @Service
并找到所有我的服务 bean。
Sam 的文章中写道:“...注解仍然像魔法。直到你运行应用程序,你才知道它是否正确连接。” 但我看不出这与一个 手动连接所有东西 的类有什么太大区别。你仍然需要在某个时候运行整个应用程序来确保它正常工作。
任何规模较大的系统都需要考虑架构分层。几乎所有我的 Spring 应用程序最终都底部有一个“领域”层,顶部有一个“Web”层。我发现最好是强制严格的分层分离,并保持依赖关系朝一个方向。用 @ResponseStatus
注解领域服务异常非常容易,但在实践中我很少发现这是一个好主意。同样,在 @Entity
上添加一些 Jackson 注解以便它可以被转换为 JSON 也很容易,但这通常会在长期运行中导致问题。
我推荐使用 Structure 101 和 SonarQube 等工具,它们可以检查你的代码是否存在包依赖混乱和设计问题。
我通常认为最好将业务逻辑放在 Spring MVC @Controller
bean 之外。我喜欢将 Spring MVC 视为一个小小的适配器层。它负责接收 HTTP 输入、验证它、将其传递给服务然后返回响应。我经常会在 web 层添加中介 @Service
bean 来协调对业务服务的调用。这通常使得测试变得容易得多,尤其是在使用 MVC 测试框架 时。
Java 中的日志记录实在不应该这么复杂。有太多的日志库可供选择,以及太多的不同组合,很容易让人陷入困境。幸运的是,对于典型的 Spring Boot 应用程序,你真的不需要担心太多。我的建议始终是:
在 Spring 中进行单元测试的黄金法则是:让 Spring 远离你的单元测试!你应该能够在不启动应用程序上下文的情况下对大部分 bean 进行单元测试。测试领域关于 mock 存在很多争论,但我通常认为明智地使用 Mockito(我喜欢 BDD Mockito)非常有帮助。
在进行集成测试时,你通常需要引入 Spring。我倾向于发现我的集成测试通常会配置应用程序的“层”。例如,领域测试可能会启动 Hibernate + Spring Data + 内存数据库。对于 Spring MVC 测试,我倾向于不使用数据库,而是注入 mock 服务。上面讨论的分层架构在这里真正开始发挥作用,因为它很容易模拟或 stub 你的“web 层”所需的内容。
一个典型的 Spring Boot REST 应用程序启动时间大约是 2.5 秒,一旦加入像 Hibernate 这样的技术,速度会变慢。Spring 的设计理念是“快速失败 & 尽早失败”,不幸的是,这确实会影响启动时间。从积极的一面看,一旦应用程序启动,它很可能处于正常运行状态。
如果你的启动时间有问题,可以考虑查看 Spring Boot Devtools,它应该能让重新启动更快。如果你有一些自己创建很慢的 bean,也应该考虑使用 @Lazy
初始化。
Spring Boot 于 2013 年底推出,专门解决 Spring 用户面临的一些常见问题。不幸的是,我经常看到这句话被转述:“Spring 现在如此复杂,以至于它拥有了自己的框架。” Spring Boot 有意与 Spring Framework 区分开来,它有不同的目标、不同的依赖和不同的发布周期。我更喜欢将 Spring Framework 视为原材料,而将 Spring Boot 视为烘烤好的蛋糕。你可以自由地以任何你认为合适的方式混合这些原料,但谁不喜欢蛋糕呢?
开源最伟大的优点之一就是它为你提供了巨大的自由。如果你想要依赖注入,但不太喜欢 Spring,可以看看 Guice。如果你想构建 REST 服务,但不喜欢 Spring MVC,可以试试 Ratpack。如果你想要一个集成良好、经过充分测试、文档齐全且有良好记录的框架,可以试试 Spring 和 Spring Boot。
如果你想改变你的开源项目的工作方式,尝试贡献吧!