领先一步
VMware 提供培训和认证,助您加速进步。
了解更多在感恩节周末,一篇2014年的文章《我为什么憎恨Spring》由Sam Atkinson撰写,开始在Twitter上传播。倾听批评意见总是很有趣的,可以让我们看到如何改进Spring,Spring Boot的诞生很大程度上就是源于倾听人们谈论在使用该框架时遇到的问题。
在这篇博文中,我将尝试解决Sam文章中讨论的一些顾虑,并描述我个人的Spring“最佳实践”。但在深入探讨细节之前,值得考虑一下为什么框架中仍然存在并非最优的做事方式。
在Spring上工作最令人头疼的方面之一,除了人们写文章抱怨它之外,就是功能往往具有很长的生命周期。Spring对向后兼容性有很强的承诺,这意味着一旦添加了一个功能,就很难从根本上改变它。开发一个框架会带来应用程序开发人员通常没有的独特挑战。敏捷技术,如YAGNI(你不需要它)和激进重构,更难应用。我们有SMNI(某人可能需要它)和谨慎重构。在Java出现lambda、泛型甚至注解之前就有意义的功能仍然得到支持。如今,用XML来配置Bean的连接方式不是一个好方法,这点很容易争论,但要完全移除它则要困难得多。我个人很看重这样一个事实:我可以轻松升级旧的Spring应用程序并逐步迁移到更新的技术。
同时也要记住,就像商业软件一样,开源也有有限的资源和时间的压力。有时我们作为开发者也会犯错误,并且需要一段时间来纠正它们。
Spring的本质是一个集成框架。它在许多不同的技术之上提供了一致的编程模型。公平地说,Spring可能有点庞大,但如果没有……集成大量技术,就不可能提供与其他技术的集成。虽然这可能显得压倒一切,但一旦你理解了核心概念,只挑选你需要的Spring部分就相当容易了。如果你有一些特定的目标需要实现,spring.io上的指南可能是一个很好的起点。Spring Boot starters列表或使用start.spring.io也是限制Spring在特定领域使用的一个好方法。
一个典型的Spring应用通常会依赖于任意数量的第三方库。历史上,很难确切知道不同版本的库何时能很好地协同工作。如果你今天开始一个新项目,我强烈推荐使用Spring Boot的父POM或Spring Platform。两者都提供了一组经过精心挑选的、已知可以协同工作的依赖项。
我们甚至开发了一个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应用程序最终都会有一个底层的“域”层和一个顶层的“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服务。上面讨论的分层架构在这里开始显现其优势,因为可以轻松地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。
如果你想改变你的开源软件的工作方式,可以尝试贡献代码!