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

相关推荐
浅水壁虎1 小时前
任务调度——XXLJOB2(调度中心)
java·spring boot·spring
源码获取_wx:Fegn08952 小时前
计算机毕业设计|基于springboot + vue景区管理系统(源码+数据库+文档)
java·vue.js·spring boot·后端·课程设计
你这个代码我看不懂3 小时前
Spring Boot拦截Http请求设置请求头
spring boot·后端·http
你这个代码我看不懂4 小时前
SpringBoot单元测试Mock和Spy
spring boot·单元测试·log4j
Java程序员威哥5 小时前
SpringBoot2.x与3.x自动配置注册差异深度解析:从原理到迁移实战
java·大数据·开发语言·hive·hadoop·spring boot·后端
shejizuopin5 小时前
基于Spring Boot+小程序的非遗科普平台设计与实现(毕业论文)
spring boot·后端·小程序·毕业设计·论文·毕业论文·非遗科普平台设计与实现
vx_bisheyuange6 小时前
【源码免费送】计算机毕设精选项目:基于SpringBoot的汽车租赁系统的设计与实现
spring boot·汽车·毕业设计·需求分析
浅水壁虎6 小时前
任务调度——XXLJOB3(执行器)
java·服务器·前端·spring boot
小唐同学爱学习7 小时前
短链接修改之写锁
spring boot·redis·后端·mysql
zbguolei8 小时前
Springboot上传文件与物理删除
java·spring boot·后端