彻底解决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 以获取相关源码。

相关推荐
码蜂窝编程官方14 分钟前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游
许苑向上1 小时前
Dubbo集成SpringBoot实现远程服务调用
spring boot·后端·dubbo
郑祎亦2 小时前
Spring Boot 项目 myblog 整理
spring boot·后端·java-ee·maven·mybatis
计算机毕设指导63 小时前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
paopaokaka_luck3 小时前
[371]基于springboot的高校实习管理系统
java·spring boot·后端
捂月5 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
一元咖啡6 小时前
SpringCloud Gateway转发请求到同一个服务的不同端口
spring·spring cloud·gateway
天天扭码7 小时前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
FIN技术铺7 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端