领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多在感恩节周末,一篇 2014 年的文章名为 "Sam Atkinson 的《为什么我讨厌 Spring》" 在 Twitter 上开始流传。倾听批评意见,了解我们如何改进 Spring,这始终很有趣,大部分的 Spring Boot 就是源于倾听 人们谈论他们在框架中遇到的问题。
在这篇博文中,我将尝试解决 Sam 文章中讨论的一些问题,并描述我个人的 Spring “最佳实践”。不过,在我们深入细节之前,值得考虑一下为什么框架中仍然存在一些次优的做事方式。
除了人们写关于他们多么讨厌 Spring 之外,在 Spring 上工作的一个更具压力的方面是,功能往往具有非常长的尾部。Spring 非常重视向后兼容性,这意味着一旦添加了某个功能,就很难从根本上改变它。开发框架带来了应用程序开发人员通常不会遇到的独特挑战。诸如 YAGNI(你不需要它)和积极重构之类的敏捷技术更难应用。我们有 SMNI(有人可能需要它)和谨慎重构。在 Java 中存在 lambda、泛型甚至注释之前就很有意义的功能仍然得到支持。很容易争辩说,XML 如今不是连接 bean 的好方法,但争辩说它应该完全删除则要困难得多。我个人重视能够轻松升级旧的 Spring 应用程序并逐步迁移到新技术的事实。
还要记住,就像商业软件一样,开源也有有限的资源和时间压力。有时我们作为开发人员确实会犯错误,并且需要忍受一段时间。
Spring 的核心是一个集成框架。它在许多不同的技术上提供一致的编程模型。可以公平地说,Spring 的规模可能有点大,但是,如果不与大量技术集成,就不可能提供与大量技术的集成。虽然它看起来可能令人不知所措,但是一旦您理解了核心概念,就很容易只选择您需要的 Spring 部分。spring.io 上的指南 可以作为您需要实现某些特定目标的有用起点。Spring Boot 启动器的列表 或使用 start.spring.io 也可以是将 Spring 限制在特定领域的好方法。
一个典型的基于 Spring 的应用程序将依赖于任意数量的第三方库。从历史上看,很难确切地知道各种库的哪些版本可以很好地协同工作。如果您今天要启动一个新项目,我强烈建议使用 Spring Boot 的父 POM 或 Spring 平台。两者都提供了一组经过精心挑选的依赖项,这些依赖项已知可以很好地协同工作。
我们甚至开发了一个 Gradle 插件,允许您在没有 Maven 父依赖项功能的情况下使用 POM 的已管理依赖项 部分。
如果您由于某种原因无法使用已管理的依赖项,您仍然可以参考 Spring Boot 文档以获取 已知可以很好地协同工作的兼容版本列表。
始终在您的 bean 中使用 基于构造函数的依赖项注入。始终对强制依赖项使用 断言。有关为什么基于字段的注入有害的更多背景信息,您可以阅读 Oliver Gierke 的这篇文章。
与往常一样,规则有一个例外,当您 使用 SpringJUnit4ClassRunner
时,在测试中使用基于字段的注入是可以的。
我看到一些人争论说应用程序代码绝不应该依赖于任何框架。即,任何地方(配置代码除外)的 org.springframework
导入都是不好的。虽然您绝对可以这样做(对标准注释(例如 @Inject
)有广泛的支持),但我倾向于不那么武断。我更喜欢在这里应用 80/20 规则。我认为依赖于框架类和注释是可以的,但我不想在离开 Spring 时重写超过 20% 的类。
我想要的是易于测试的应用程序代码。我应该能够手动创建我的业务服务的实例并对其进行测试,而无需完整的 ApplicationContext
。
我个人喜欢保持我的 Spring 配置相当轻量级。我喜欢使用组件扫描来查找应用程序 bean,并且我喜欢将我的 @Configuration
用于偏离 Spring Boot 默认自动配置的框架特定配置。我如今根本不倾向于使用 XML 配置。
有些人认为为所有定义的 bean 提供显式配置更好,但在实践中,我发现这是一种阻碍。遵循这种方法的应用程序往往只有一个 @Configuration
类,最终会了解系统中的所有内容,并且包含一堆小的 @Bean
方法。我还发现注释的自文档特性非常有用。我可以轻松搜索 @Service
并找到我所有的服务 bean。
Sam 的文章指出“...注释仍然是魔术。在运行应用程序之前,您不知道它是否已正确连接”,但我没有真正看到这与拥有一个 手动连接所有内容的类 有什么区别。您仍然需要在某个时候运行整个应用程序才能确保它正常工作。
任何规模较大的系统都需要考虑架构分层。几乎我所有的 Spring 应用程序最终都会在底部有一个“领域(domain)”层,在顶部有一个“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 进行单元测试。在测试领域关于模拟有很多争论,但我通常发现谨慎使用Mockito(我喜欢BDD Mockito)非常有帮助。
在进行集成测试时,您通常需要涉及 Spring。我倾向于发现我的集成测试通常会配置应用程序的“一层”。例如,领域测试可能会启动 Hibernate + Spring Data + 内存数据库。对于 Spring MVC 测试,我倾向于不使用数据库,并注入模拟服务。上面讨论的分层架构在这里真正开始发挥作用,因为它可以轻松模拟或存根“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。
如果您想更改开源软件的工作方式,请尝试贡献!