彻底解决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 小时前
分布式事务实战:微服务跨服务数据一致性解决方案
java·后端·spring cloud
niaiheni14 小时前
红队实战:记一次Spring Cloud Gateway SpEL表达式注入到哥斯拉内存马(CVE-2022-22947)
web安全·spring cloud·网络安全
paopaokaka_luck15 小时前
基于springboot3+vue3的智能文库平台(AI智能搜索、AI智能汇总、实时在线状态展示、多格式文档预览与富文本编辑、Echarts图形化分析)
前端·网络·spring boot·网络协议·echarts
zzzll111116 小时前
Spring Boot 入门指南:从零开始构建 Java Web 应用
java·前端·spring boot
热心市民lcj21 小时前
Spring Boot 整合 Caffeine 本地缓存实战
spring boot·后端·缓存
zzzll111121 小时前
Spring Cloud 微服务:从入门到实践
spring·spring cloud·微服务
天丁o1 天前
Spring Boot + MyBatis Plus 考勤日报统计报表:打卡记录聚合、异常分类和明细下钻 Demo
spring boot·mybatis plus·企业数字化·考勤系统·报表统计
智_永无止境1 天前
Spring Boot 集成 OnlyOffice
java·spring boot·后端
Devin~Y1 天前
从本地生活电商到 AI RAG:互联网大厂 Java 面试场景完整实战
java·spring boot·redis·elasticsearch·spring cloud·kafka·rag
米码收割机1 天前
SA508-3钢回火焊道焊接温度场数值模拟(模拟图+论文)
spring boot·express·宠物