Spring系列开发常见问题
Spring系列开发常见问题
Web应用程序开发
1.解决Spring MVC中的HttpMediaTypeNotAcceptableException异常
在使用Springboot构建项目,基于SpringMVC开发web应用程序可能会遇到HttpMediaTypeNotAcceptableException异常:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
这个异常通常是在处理Restful API请求的时候出现,表示服务器无法找到适合客户端请求的可接受的表示形式(媒体类型)。这种情况一般有两种排查思路,一是服务器无法提供客户端所需的媒体类型;二是客户端请求中的Accept
字段不正确或不匹配服务器的响应类型。在排查的时候按照上述思路进行分析,一步步排查。
异常复现&排查过程
在自定义全局异常处理器的时候,返回一个自定义的实体时发现提示上述错误。全局异常处理器定义如下所示:
@RestControllerAdvice
public class GlobalException {
/**
* @ExceptionHandler:限定对何种异常进行处理
* @ResponseBody:处理返回的格式(SpringMVC会响应一个json格式信息)
*/
@ExceptionHandler(value = BusinessException.class)
public String handler(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("发生异常的处理器:" + handler + "- 具体异常信息:" + ex.getMessage());
// 返回结果
return "{\n" +
" \"code\":\"-1\",\n" +
" \"msg\":\"服务器出现异常,请联系管理员处理...\",\n" +
" \"data\":null\n" +
"}";
}
// 异常处理器:针对参数校验设定相应的handler
/**
* 方法参数校验
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public RspDTO handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return new RspDTO(Constant.PARAM_FAIL_ERROR, e.getBindingResult().getFieldError().getDefaultMessage());
}
/**
* ValidationException
*/
@ExceptionHandler(ValidationException.class)
public RspDTO handleValidationException(ValidationException e) {
return new RspDTO(Constant.PARAM_FAIL_ERROR, e.getCause().getMessage());
}
/**
* ConstraintViolationException
*/
@ExceptionHandler(ConstraintViolationException.class)
public RspDTO handleConstraintViolationException(ConstraintViolationException e) {
return new RspDTO(Constant.PARAM_FAIL_ERROR, e.getMessage());
}
// ---------- 其他异常处理 ---------
@ExceptionHandler(NoHandlerFoundException.class)
public RspDTO handlerNoFoundException(Exception e) {
return new RspDTO(404, "路径不存在,请检查路径是否正确");
}
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public RspDTO handlerHttpMediaTypeNotAcceptableException(Exception e) {
return new RspDTO(500, "客户端请求中的Accept字段不正确或不匹配服务器的响应类型");
}
@ExceptionHandler(Exception.class)
public ResponseEntity<RspDTO> handleException(Exception e) {
return new ResponseEntity<>(new RspDTO(500, "系统繁忙,请稍后再试"), HttpStatus.OK);
}
}
异常情况说明:当在controller层提供一个通过Validator校验的接口,访问时如果传入参数不满足校验条件则会抛出MethodArgumentNotValidException异常,此时会受到全局异常拦截器进行拦截处理,按照上述全局异常处理器的逻辑分析,正常情况下会抛出RspDTO响应实体(这点也符合全局异常处理器可支持的返回类型Model,这个Model可以是任意实体)
但是在测试接口的时候却发现,异常可以正常捕获,却在内部抛出HttpMediaTypeNotAcceptableException,且这个异常并没有再次被全局异常处理器拦截,而是直接在内部抛出,抛出提示org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
,基于抛出的异常提示跟踪解决思路,一开始初步考虑是客户端或者服务端没有定义适配的接收或者返回类型(这点在Restful接口交互中特别常见),基于这个方向进一步进行排查:
【1】确认接口请求的accept参数(apifox、浏览器)
确认accept参数,可以看到apifox、浏览器接收的类型都符合目前服务器支持的类型
【2】确认服务器端的适配
服务器无法提供所需的媒体类型:当客户端请求的媒体类型在服务器端不可用时,就会触发HttpMediaTypeNotAcceptableException
异常。这通常是由于服务器端没有配置适当的媒体类型转换器或缺少相应的依赖库导致的。为了解决这个问题,需要确保服务器端正确配置了适当的媒体类型转换器。Spring MVC通过ContentNegotiationConfigurer
类提供了配置媒体类型转换器的方式。
正常情况下为了适配前端的accept请求,会自定义WebConfig去自定义适配
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
}
但是上述配置确认之后,返回类型还是渲染失败导致错误。
【3】确认渲染逻辑
在全局异常处理器中,如果返回类型是String,则会被ViewResolver接管(例如如果程序中引入了thymeleaf,则会根据指定逻辑渲染对应的模板页面)
此处返回类型设定为RspDTO,正常逻辑希望它被渲染为相应的JSON数据并响应(通过@RestControllerAdvice(@ControllerAdvice + @ResponseBody)来进行限制),但实际上却抛出了上述错误。
一开始思考是否是因为返回的类型指定错误,因此通过设定返回类型为ResponseEntity<RspDTO>
进行校验,发现还是存在上述问题。为了进一步定位问题(排查是否因全局异常处理器配置问题导致的错误),在Controller层定义接口进行校验,确认Controller是否可以正常渲染响应,做了如下调整:
- 全局异常处理器:新增handler专门对HttpMediaTypeNotAcceptableException进行拦截处理
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public ResponseEntity<String> handlerHttpMediaTypeNotAcceptableException(Exception e) {
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body("客户端请求中的Accept字段不正确或不匹配服务器的响应类型");
}
- Controller:接口测试
@GetMapping("/testExc")
public void testExc() throws HttpMediaTypeNotAcceptableException {
throw new HttpMediaTypeNotAcceptableException("参数异常....");
}
@GetMapping("/testUser")
public User testUser(){
return new User("noob");
}
@GetMapping("/testStr")
public ResponseEntity<String> test1(){
return ResponseEntity.ok("success");
}
@GetMapping("/testRspDTO")
public RspDTO testRspDTO(){
return new RspDTO(1,"响应成功");
}
依次访问测试接口:
http://localhost:8080/user/testExc
客户端请求中的Accept字段不正确或不匹配服务器的响应类型
http://localhost:8080/user/testUser
{"id":0,"username":"noob","password":null,"email":null,"phone":null,"createTime":null}
http://localhost:8080/user/testStr
success
http://localhost:8080/user/testRspDTO
客户端请求中的Accept字段不正确或不匹配服务器的响应类型
结合上述结果分析,响应都是符合预期的,但是从testRspDTO接口请求来看,直接返回RspDTO就触发了异常,说明Springboot在对RspDTO处理的时候出现异常,此时控制变量进行对比(因为正常情况下返回RspDTO和返回User都是返回对象,因此可以进一步确认这两个实体定义有什么不同)
RspDTO和User作为网络传输对象,需要通过序列化完成Java对象和二进制字节流的转化
最终定位问题出现在@Data注解,RspDTO并没有提供@Data注解,因此实体对象没有没有正常被处理(根本原因在于没有给RspDTO提供getter构造器,导致在渲染处理的时候没有办法将RspDTO正常转化),进而导致服务器将其归类为没有适配的响应类型,抛出相应的异常,最终解决方案是为RspDTO添加@Data注解,或者为其指定构造器
总结:@Data在某些层面上简化了开发,但某些情况下如果不注意使用,理所当然的忽略掉基本的内容,就会导致程序出现意想不到的问题,而问题的排查根据需要一步步定位,例如此处是先排查是否因为前后端配置问题导致的交互不匹配、是否是全局异常处理器配置问题、其次排查接口是否可以正常渲染自定义实体(转为JSON响应)、然后找到正常转化的实体,确认代码定义的差异,最终一步步定位到对应的实体定义的构造器问题(这个问题甚至没有从异常的堆栈信息中有明显的提示,只能一步步排查)
排查了上述问题,为RspDTO构建正常的@Data配置,再次访问测试,异常被正常拦截处理
Springboot导入Jackson依赖启动报错
springboot版本 2.7.6 ,导入如下版本jackson依赖,导致启动报错
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
跟踪最下面的错误提示,发现适合jackson相关的:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.filter.OrderedFormContentFilter]: Factory method 'formContentFilter' threw exception; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.fasterxml.jackson.datatype.jsr310.JavaTimeModule]: Constructor threw exception; nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ser/std/ToStringSerializerBase
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.24.jar:5.3.24]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.24.jar:5.3.24]
... 56 common frames omitted
Caused by: java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ser/std/ToStringSerializerBase
at com.fasterxml.jackson.datatype.jsr310.JavaTimeModule.<init>(JavaTimeModule.java:158) ~[jackson-datatype-jsr310-2.13.4.jar:2.13.4]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_412]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_412]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_412]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_412]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:211) ~[spring-beans-5.3.24.jar:5.3.24]
... 71 common frames omitted
解决方案1:如项目中无引用,则取消掉Jackson依赖,重新刷新Maven再次启动确认
解决方案2:引入关联的依赖 jackson-datatype-jsr310
根据上述日志提示可以看到这个错误表明Spring框架在尝试自动配置Jackson数据类型的Java 8日期和时间模块(com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
)时失败了。这通常是因为缺少相关的依赖或者依赖没有正确配置。因此需要关联引入Jackson的jackson-datatype-jsr310
模块的依赖,参考如下所示(jackson版本要一致)
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.9</version>
</dependency>