彻底解决Spring Boot中的404异常~

大家好,我是飘渺。今天,我们将继续探讨DDD与微服务架构的系列主题。

在前一篇文章 DailyMart13:优雅远程调用,微服务中ACL与OpenFeign的绝佳配合!,一位粉丝提到在DailyMart中,关于Feign的error解码器对404错误处理不够友好,会返回空对象而不报错。

实际上,这个问题并不是Feign解码器的问题。

众所周知,在Spring Boot中,当我们访问一个不存在的接口时,会触发404异常,接口调用会返回如下JSON格式的错误消息:

JSON 复制代码
{
  "timestamp": "2023-09-23T03:36:50.503+00:00",
  "status": 404,
  "error": "Not Found",
  "path": "/api/inventory/xxxx"
}

但是在DailyMart中,这个错误消息会被全局包装类GlobalResponseBodyAdvice处理,因此出现了以下奇怪的错误信息:code码为OK表示操作正常,但data消息体却表示404异常。由于这种情况,OpenFeign的异常解码器无法正确处理。

json 复制代码
{
    "code": "OK",
    "message": null,
    "data": {
        "timestamp": "2023-09-23T03:36:50.503+00:00",
        "status": 404,
        "error": "Not Found",
        "path": "/api/inventory/xxxx"
    },
    "timestamp": 1695440210514
}

因此,问题的关键在于解决Spring Boot中404异常处理的问题,以便能够返回正确的响应结果。

1. SpringBoot 的默认错误处理机制

Spring Boot默认为我们提供了BasicErrorController来处理全局/error请求。BasicErrorController提供两种错误响应方式,一种是针对页面请求的错误响应,另一种是针对JSON请求的错误响应。

2. 自定义错误响应

在SpringBoot中,BasicErrorController是由自动配置类ErrorMvcAutoConfiguration负责加载。

java 复制代码
@AutoConfiguration(before = WebMvcAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {
  ...

	@Bean
	@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
	public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
			ObjectProvider<ErrorViewResolver> errorViewResolvers) {
		return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
				errorViewResolvers.orderedStream().toList());
	}
}

根据上述配置,只要我们自己配置一个ErrorController,就可以覆盖掉BasicErrorController的默认行为。下面的代码示例展示了如何通过继承AbstractErrorController并重写error方法来自定义错误响应,以使其与项目整体风格一致:

java 复制代码
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class CustomErrorController extends AbstractErrorController {
	...

    @RequestMapping
    public Result<Void> error(HttpServletRequest request) {
   
        return ResultFactory.fail(String.valueOf(status),message);
    
    }

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        ...
    }
}

通过这种方式,当我们访问不存在的方法时,将会返回符合预期的JSON响应结果:

json 复制代码
{
    "code": "404 NOT_FOUND",
    "message": "No message available: /api/inventory/verify",
    "data": null,
    "timestamp": "1696598118098"
}

3. 微服务中的解决方案

上述方案在单服务中表现良好,但在微服务体系中,每个服务都需要创建一个独立的CustomErrorController,这显然违反了DRY(Don't Repeat Yourself)原则。

在DailyMart中,我们封装了一个公共模块dailymart-web-spring-boot-starter,专门处理Web请求的逻辑,包括全局响应包装和异常处理。我们可以将CustomErrorController放置在此模块中,并在配置类中加载该Controller。

JAVA 复制代码
/**
 *  注册SpringBoot默认异常处理器
 */
@Bean
public CusotmErrorController globalErrorController(ErrorAttributes errorAttributes){
  return new CusotmErrorController(errorAttributes);
}

然而,这种做法在启动服务时可能会引发以下异常:

JAVA 复制代码
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'customerErrorController' method 
com.jianzh5.dailymart.springboot.starter.web.CustomerErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
to { [/error], produces [text/html]}: There is already 'basicErrorController' bean method

这是因为Spring Boot本身已经有一个处理/error请求的控制器BasicErrorController,而我们又定义了一个CustomErrorController来处理/error请求。

3.1 解决方案

这个问题有两种解决方案。

第一种解决方案是确保系统在启动时能够优先加载自定义的CustomErrorController

根据前文提到的BasicErrorController配置的条件注解,一旦定义了ErrorController就不会再加载BasicErrorController,所以只需要在自动配置类上加上@AutoConfiguration(before = ErrorMvcAutoConfiguration.class)注解,确保CustomErrorController在BasicErrorController之前加载。

第二种解决方案是在加载CustomErrorController时修改默认的BasicErrorController的拦截路径。

如前所述,BasicErrorController的拦截路径为@RequestMapping("${server.error.path:${error.path:/error}}"),如果我们定义了error.path的值,它就会优先使用。因此,只需设置error.path的值即可解决问题。

java 复制代码
@PostConstruct
public void customizeErrorPath() {
  // 修改 server.error.path 的值为自定义值
  System.setProperty("error.path", "/deprecated/error");
}

小结

通过本文,我们探讨了Spring Boot中的404异常处理问题,以及如何通过自定义ErrorController来解决这一问题。特别是在微服务架构中,我们介绍了如何利用共享模块的方法来更有效地管理异常处理。希望这些实践经验对您有所帮助,提高了您在微服务开发中处理异常情况的能力。我们非常欢迎各位读者提出宝贵的意见和建议,以进一步完善这个系列文章。

DailyMart是一个基于领域驱动设计(DDD)和Spring Cloud Alibaba的微服务商城系统。我们将在该系统中整合博主其他专栏文章的核心内容。如果你对这两大技术栈感兴趣,可以在公众号Java日知录回复关键词 DDD 以获取相关源码。

相关推荐
我命由我1234532 分钟前
Spring Boot 项目集成 Redis 问题:RedisTemplate 多余空格问题
java·开发语言·spring boot·redis·后端·java-ee·intellij-idea
面朝大海,春不暖,花不开33 分钟前
Spring Boot消息系统开发指南
java·spring boot·后端
hshpy35 分钟前
setting up Activiti BPMN Workflow Engine with Spring Boot
数据库·spring boot·后端
jay神1 小时前
基于Springboot的宠物领养系统
java·spring boot·后端·宠物·软件设计与开发
不知几秋2 小时前
Spring Boot
java·前端·spring boot
howard20052 小时前
5.4.2 Spring Boot整合Redis
spring boot·整合redis
TracyCoder1233 小时前
接口限频算法:漏桶算法、令牌桶算法、滑动窗口算法
spring boot·spring·限流
饮长安千年月3 小时前
JavaSec-SpringBoot框架
java·spring boot·后端·计算机网络·安全·web安全·网络安全
考虑考虑4 小时前
Jpa中的@ManyToMany实现增删
spring boot·后端·spring
你不是我我4 小时前
【Java开发日记】说一说 SpringBoot 中 CommandLineRunner
java·开发语言·spring boot