Spring Boot 实践推荐
Spring Boot 是最流行的用于开发微服务的 Java 框架。在使用过程中有一些实践总结。
自定义BOM来维护第三方依赖
Spring Boot项目本身使用和集成了大量的开源项目,它帮助我们维护了这些第三方依赖。但是也有一部分在实际项目使用中并没有包括进来,这就需要我们在项目中自己维护版本。
参考 Spring Boot 项目 中 spring-boot-dependencies 的 pom 维护一个自己的 <packaging>pom</packaging>
三方依赖项目。
|
|
|
|
使用自动配置
Spring Boot 的一个主要特性是使用自动配置。使用它的最简单方法是依赖Spring Boot Starters。
|
|
通过使用以下注解属性,可以从自动配置中排除某些配置类,必要时才应该这样做。
|
|
Spring Initializr 初始化项目
Spring Initializr 提供了一个超级简单的方法来创建一个新的Spring Boot项目,并根据你的需要来加载可能使用到的依赖。
IDEA 新建项目的时候集成了一个 Spring Initializr。
创建自己的自动配置 starter 项目
如果你在一个严重依赖Spring Boot的公司或团队中工作,并且有共同的问题需要解决,那么你可以创建自己的自动配置。
这项任务涉及较多工作,因此你需要考虑何时获益是值得投入的。与多个略有不同的定制配置相比,维护单个自动配置更容易。
如果将这个提供Spring Boot配置以开源库的形式发布出去,那么将极大地简化数千个用户的配置工作。
官方
spring-boot-starter-data-redis
spring-boot-starter
在前面 非官方mybatis-spring-boot-starter
在后面。
正确设计代码目录结构
尽管允许你有很大的自由,但是有一些基本规则值得遵守来设计你的源代码结构。
首先你要依据 maven 结构组织项目。 避免使用默认包。确保所有内容(包括你的入口点)都位于一个名称很好的包中,这样就可以避免与装配和组件扫描相关的意外情况; 将Application.java(应用的入口类)保留在顶级源代码目录中。 将控制器和服务放在以功能为导向的模块中,但这是可选的。
保持@Controller的简洁和专注
- 控制器应该是无状态的!默认情况下,控制器是单例,并且任何状态都可能导致大量问题;
- 控制器不应该执行业务逻辑,而是依赖委托;
- 控制器应该处理应用程序的HTTP层,这不应该传递给服务;
- 控制器应该围绕用例/业务能力来设计。
要深入这个内容,需要进一步地了解设计REST API的最佳实践。
围绕业务功能构建 @Service
Service是Spring Boot的另一个核心概念。我发现最好围绕业务功能/领域/用例(无论你怎么称呼都行)来构建服务。
在应用中设计名称类似AccountService, UserService, PaymentService这样的服务,比起像DatabaseService、ValidationService、CalculationService这样的会更合适一些。
你可以决定使用Controler和Service之间的一对一映射,那将是理想的情况。但这并不意味着,Service之间不能互相调用!
使数据库独立于核心业务逻辑之外
你的数据库是一个“细节”,这意味着不将你的应用程序与特定数据库耦合。过去很少有人会切换数据库,我注意到,使用Spring Boot和现代微服务开发会让事情变得更快。
保持业务逻辑不受Spring Boot代码的影响
推荐使用构造函数注入
保持业务逻辑免受Spring Boot代码侵入的一种方法是使用构造函数注入。不仅是因为@Autowired注解在构造函数上是可选的,而且还可以在没有Spring的情况下轻松实例化bean。
熟悉并发模型
在Spring Boot中,Controller和Service是默认是单例。如果你不小心,这会引入可能的并发问题。你通常也在处理有限的线程池。请熟悉这些概念。
在考虑 Spring Boot 应用程序中的并发性时,值得考虑的关键领域是:
- 最大线程数—— 这是为处理对应用程序的请求而分配的最大线程数
- 共享外部资源 ——调用外部共享资源,如数据库和其他 REST 端点可能需要大量时间。
- 异步方法调用 ——这些方法调用在等待响应时将线程释放回线程池
- 共享内部资源 ——调我们通常无法控制外部资源的事情,但我们完全控制了系统的内部资源。
# 最大工作线程数,默认200。
server.tomcat.max-threads=200
# 最大连接数默认是10000
server.tomcat.max-connections=10000
# 等待队列长度,默认100。
server.tomcat.accept-count=100
# 最小工作空闲线程数,默认10。
server.tomcat.min-spare-threads=100
Tomcat有两种处理连接的模式
-
一种是BIO,一个线程只处理一个Socket连接;缺省是200
-
另一种就是NIO,一个线程处理多个Socket连接。 默认是10000
-
maxThreads是指Tomcat线程池最多能起的线程数
-
maxConnections则是Tomcat一瞬间最多能够处理的并发连接数。并发量指的是连接数
-
多开线程的代价就是增加上下文切换的时间,浪费CPU时间。另外还有就是线程数增多,每个线程分配到的时间片就变少。多开线程并不等于提高处理效率。
-
增加最大连接数,支持的并发量确实可以上去。但是在没有改变硬件条件的情况下,这种并发量的提升必定以牺牲响应时间为代价。
-
当连接数达到最大值maxConnections后,系统会继续接收连接,进行排队,但不会超过acceptCount的值。
-
当队列(acceptCount)已满时,任何的连接请求都将被拒绝。acceptCount的默认值为100。
-
线程数的经验值为:1核2G内存,线程数经验 值 200;4 核 8G 内 存 , 线 程 数 经 验 值800。
-
等待队列长度:队列做缓冲池用,但也不能无限长,消耗内存,出入队列也耗CPU。
从 @EnableAsync 注释下的 Application 类上的@SpringBootApplication 注释开始。启用后,您可以@Async 在返回的服务中使用注释 CompletableFuture<>
,这些@Async 方法将在后台线程池中运行。
Spring 服务和控制器默认是单例的。共享状态的其他潜在来源是缓存和自定义的、服务器范围的组件(通常是监控、安全等)。
加强配置管理的外部化
- 使用配置服务器,例如Spring Cloud Config;
- 将所有配置存储在环境变量中(可以基于git仓库进行配置)。
这些选项中的任何一个(第二个选项多一些)都要求你在DevOps更少工作量,但这在微服务领域是很常见的。
提供全局异常处理
Spring Boot 提供了两种主要方法:
- @RestControllerAdvice + @ExceptionHandler
- 自定义 HandlerExceptionResolver 全局异常处理策略;
- @Controller 的方法添加 @ExceptionHandler 注解,这在某些特定场景下使用可能会很有用。
- ResponseStatusException Spring Boot 5
- Handle the Access Denied in Spring Security
|
|
|
|
使用日志框架
|
|
测试你的代码
这不是Spring Boot特有的,但它需要提醒——测试你的代码!如果你没有编写测试,那么你将从一开始就编写遗留代码。
如果有其他人使用你的代码库,那边改变任何东西将会变得危险。当你有多个服务相互依赖时,这甚至可能更具风险。
由于存在Spring Boot最佳实践,因此你应该考虑将Spring Cloud Contract用于你的消费者驱动契约,它将使你与其他服务的集成更容易使用。
使用测试切片让测试更容易,并且更专注
测试切片是关于分割 ApplicationContext 为您的测试创建的。通常,如果您想使用 测试控制器 MockMvc,您肯定不想打扰数据层。相反,您可能想要模拟您的控制器使用的服务并验证所有与 Web 相关的交互是否按预期工作。
|
|
@WebMvcTest
是 Spring Boot 1.4 中的 web 测试切片。当它存在时,您指示 Spring Boot 需要一个 Web 环境,并且只应实例化指定的控制器。因为它知道测试的性质,它可以为您做出额外的明智决定(例如,自动配置MockMvc以便剩下的就是注入它)。此外,您的控制器具有依赖关系,UserVehicleService因此启动上下文会导致失败,因为ApplicationContext不知道它(请记住,只有 Web 基础架构UserVehicleController是已知的)。@MockBean这里用来注册一个UserVehicleServicemock,以便可以透明的注入到控制器中。
你也可以创建自己的切片