目录

Spring Boot 配置实践

Spring Boot 2.7

Spring Boot 2.7 Release Notes

  • 自动配置注册的加载从 spring.factories 迁移到 AutoConfiguration下的imports。
  • Spring MVC 的 requestMappingHandlerMapping Bean 不再默认为主,存在多个时注入,需要指定或注入集合。
  • 引入了新的 @AutoConfiguration 注解,作用在 AutoConfiguration.imports文件中列出的自动配置类。
  • 增加了一些测试相关的注解、注解属性、注解属性源。如,@SpringBootTest,@DataCouchbaseTest,@DataElasticsearchTest。
  • 增加支持 Redis Sentinel 进行身份验证的用户名,spring.redis.sentinel.username。

自动配置

自动配置注册

如果您已经创建了自己的自动配置,您应该将注册从 org.springframework.boot.autoconfigure.EnableAutoConfiguration 下的 spring.factories 移动到名为 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 的文件中。

每行包含一个自动配置类的完全限定名称,而不是一个逗号分隔的列表。

spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration

为了向后兼容,spring.factories 中的条目仍将受到尊重。

新的 @AutoConfiguration 注解

引入了新的 @AutoConfiguration 注解。 它应该用于注释新 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中列出的顶级自动配置类,替换 @Configuration。

嵌套在 @AutoConfiguration 类中或由 @AutoConfiguration 类导入的配置类应该像以前一样继续使用 @Configuration。

为方便起见,@AutoConfiguration 还支持通过 after、afterNames、before 和 beforeNames 属性进行自动配置排序。 这可以用作@AutoConfigureAfter 和@AutoConfigureBefore 的替代品。

使用 Maven Shade 插件和 Gradle Shadow 插件构建Jars

Spring Boot 2.7 改变了发现自动配置和管理上下文类的方式。 它们现在分别在名为 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 和 META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports 的文件中声明。

Maven Shade插件的配置 如果使用 maven-shade-plugin且没有依赖 spring-boot-starter-parent,添加如 AppendingTransformer配置。

1
2
3
4
5
6
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
  <resource>META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
  <resource>META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports</resource>
</transformer>

Gradle Shadow插件配置 添加如下配置,以附加 .imports 为后缀的文件:

1
2
3
4
tasks.withType(ShadowJar).configureEach {
    append("META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports")
    append("META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports")
}

测试

单元测试:一般面向某一个简单的方法,在对应测试的方法上加上注解@Test,编写一般业务代码时,测试成本较大。

切片测试:一般面向某一个小的功能模块,在对应测试的类上加上注解@RunWith @WebMvcTest @SpringBootTesT等,用于测试某一项边界功能,介于单元测试和功能测试之间。涉及到的注解有。

功能测试:一般面向某个由多个小的功能模块组成的完整的业务功能,在对应测试的类上加上注解@RunWith @SpringBootTest等,同时也可以使用切面测试中的mock能力。

总结:实际上无论是单元测试,还是切片测试,功能测试都是实现某一项功能的测试,区别不大,只要在对应的类上加上两个功能测试模块的大注解@RunWith @SpringBootTest再配合其他的小注解即可完成所有功能模块的测试。

如果您对测试花费在设置和 Spring 应用程序上下文上的时间感兴趣,您可能需要查看 JUnit Insights,它可以包含在 Gradle 或 Maven 构建中,以生成关于 JUnit 5 如何花费时间的很好的报告。

异常处理

在 Spring Boot 中创建控制器以自定义方式处理所有错误/异常时,包括自定义异常

  • 控制器是否应该实现 Spring Boot 的 ErrorController ?
  • 控制器是否应该扩展 Spring 的 ResponseEntityExceptionHandler ?

pring Boot 应用程序具有用于错误处理的默认配置 - ErrorMvcAutoConfiguration。

如果没有提供额外的配置,它的基本作用是:

它创建默认的全局错误控制器 - BasicErrorController 它创建默认的“错误”静态视图“Whitelabel 错误页面”。 BasicErrorController默认连接到“/error”。如果应用程序中没有自定义的“错误”视图,则在任何控制器抛出异常的情况下,用户会登陆 /error whitelabel 页面,该页面由 BasicErrorController 填充信息。

Spring Boot 的默认 JSON 错误体可以通过定义我们自己的 ErrorAttributes 实现来定制或扩展。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Component
public class MyCustomErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);

        Throwable throwable = getError(requestAttributes);
        Throwable cause = throwable.getCause();
        if (cause != null) {
            Map causeErrorAttributes = new HashMap<>();
            causeErrorAttributes.put("exception", cause.getClass().getName());
            causeErrorAttributes.put("message", cause.getMessage());
            errorAttributes.put("cause", causeErrorAttributes);
        }
        return errorAttributes;
    }
} 

HandlerExceptionResolver

DispatcherServlet 在实现的应用程序上下文中声明的任何 Spring bean HandlerExceptionResolver 都将用于拦截和处理 MVC 系统中引发的任何异常,而不是由 Controller 处理。

1
2
3
4
public interface HandlerExceptionResolver {
    ModelAndView resolveException(HttpServletRequest request, 
            HttpServletResponse response, Object handler, Exception ex);
}

handler 指的是产生异常的控制器

MVC 默认创建三个这样的解析器。正是这些解析器实现了上述行为:

  1. ExceptionHandlerExceptionResolver 将未捕获的异常与@ExceptionHandler处理程序(控制器)和任何控制器通知上的合适方法匹配。 在 @ControllerAdvice 类中搜索异常处理程序(使用 @ExceptionHandler 注释的方法)。
  2. ResponseStatusExceptionResolver 检查抛出的异常是用 @ResponseStatus 注解还是从 ResponseStatusException 派生。
  3. DefaultHandlerExceptionResolver 转换标准 Spring 异常并将它们转换为 HTTP 状态代码(我没有在上面提到这一点,因为它是 Spring MVC 内部的)。 然后它通过 Spring MVC 异常的默认处理程序。
  4. 最后,如果没有找到,控件将被转发到错误页面视图,并在其后面带有全局错误处理程序。如果异常来自错误处理程序本身,则不执行此步骤。
  5. 如果没有找到错误视图(例如禁用全局错误处理程序)或跳过第 4 步,则异常由容器处理。

它们按照列出的顺序链接和处理 - 在内部 Spring 创建一个专用 bean (HandlerExceptionResolverComposite) 来执行此操作。

exception-handling-in-spring-mvc

处理 400 500 错误

# 出现错误时, 直接抛出异常
spring.mvc.throw-exception-if-no-handler-found=true
# 不要为我们工程中的资源文件建立映射
spring.resources.add-mappings=false
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestControllerAdvice
@Component
public class GlobalExceptionHandler {

    @ExceptionHandler(NoHandlerFoundException.class)
    public Result handleNoHandlerFoundException(NoHandlerFoundException e) {
        return new Result(400,"api not found",null);
    }
}
// 添加上面2个配置后,会过滤静态资源,导致某些资源找不到
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 解决静态资源无法访问的问题
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");   
        // 解决升级swagger3.0.3后,swagger无法访问的问题
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    
    }
}

可以继承 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController 重写 errorHtml 抛出异常,异常中携带必要信息,最后 @ExceptionHandler 统一拦截处理。