SpringMVC统一封装响应结果接口返回String类型时的异常问题分析

1.问题

一个项目里利用ResponseAdvisor对Controller的响应结果做了统一封装,代码如下;

@RestControllerAdvice(basePackages = "com.restkeeper")
public class ResponseAdvisor implements ResponseBodyAdvice<Object> {

    /**
     * 判断哪些需要拦截
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    /**
     * 返回结果包装
     *
     * @param body                  controller返回的数据
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        Object result = null;

        if (body instanceof Result) {
            result = body;
        }else if (body instanceof Boolean) {
            result = new BaseResponse<Boolean>((boolean) body);
        }else if (body instanceof PageVO) {
            result = new BaseResponse<>(body);
        }else if (body instanceof ExceptionResponse) {
            result = new BaseResponse<>(400, ((ExceptionResponse) body).getMsg());
        }else {
            result = new BaseResponse<>(body);
        }

        return result;
    }
}

有位同事写了一个接口,返回的数据是String,按照一般情况前端接受到的应该是data属性为接口返回值的BaseResponse对象,但实际情况是前端接收到异常的报错的信息。

{
    "success": false,
    "message": "com.restkeeper.response.BaseResponse cannot be cast to java.lang.String",
    "code": 400
}

2.分析

先看了眼日志,可以看到接口返回的数据确实先被封装成统一的BaseResponse,但在后续执行writing方法时报了类型转换错误。

image-20220626011855837

注意看这两行,一个是 Using ‘application/json’,另一个是Using ‘text/html’,所以一开始以为是这个地方出了问题。

image-20220626013244653

开始debug,研究这个地方为什么不一样,相关源码在类AbstractMessageConverterMethodProcessor的writeWithMessageConverters方法中,看名字就知道是转换返回到前端的数据类型的,主要看这几行代码

HttpServletRequest request = inputMessage.getServletRequest();
//获取接口发起端能接受的数据类型
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);

而getAcceptableMediaTypes方法最终会调用resolveMediaTypes方法,

	public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
		for (ContentNegotiationStrategy strategy : this.strategies) {
			List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
			if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
				continue;
			}
			return mediaTypes;
		}
		return MEDIA_TYPE_ALL_LIST;
	}

这里最终获取的是接口调用时设置的accept请求头,我是在浏览器上直接访问的,所以获取的也就是默认值。

image-20220626014310227

可以看到accept里有很多个媒体数据类型,然后执行下面的代码根据各种类型的q值等条件选择其中一个。

for (MediaType mediaType : mediaTypesToUse) {
    if (mediaType.isConcrete()) {
        selectedMediaType = mediaType;
        break;
    }
    else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
        selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
        break;
    }
}

可以看到最后选的是text/html
image-20220626014856418

既然是accept请求头的问题,那么很简单,在发起请求的时候手动设置下即可,然后就用postman重新测试,selectMediaType已经变为application/json。

image-20220626020133020

然而依然还是报类型转换错误

image-20220626015042747

只能继续往下调试,如图

image-20220626021707667

遍历SpringMVC提供了类型转换器,执行canWrite方法判断是否能处理接口返回值,如果可以继续往下执行

image-20220626022107317

valueType的值为String,ByteArrayHttpMessageConverter不能处理,遍历到第二个转换器StringHttpMessageConverter,往下执行,下面这段代码实际上就是执行我们实现的ResponseAdvisor的beforeBodyWrite方法。

image-20220626022831981

继续向下执行,执行converter.write方法的时候报了类型转换异常,“com.restkeeper.response.BaseResponse cannot be cast to java.lang.String”。

image-20220626023124770

3.解决

调试到这其实问题已经很明显了,就是因为接口的返回值是String类型,所以在向前端输出响应结果是选择了StringHttpMessageConverter转换器,然后由于项目做了统一的结果封装,执行到这的时候body值已经是BaseResponse对象,然后在类型转换时抛出异常。

解决办法也很简单,既然这里需要String类型的body,那么在封装响应结果时再多加一个判断,在beforeBodyWrite方法最后加上下面一端代码即可。

 if (body instanceof String) {
    //这里注意是将result转换为json字符串
    result = JSON.toJSONString(result);
 }

重新调试,响应结果就正常了。

{
    "code": 200,
    "data": "Hello Nacos Discovery sayhi, i am from port 8083",
    "message": "success",
    "success": true
}

在网上看到有人直接把MessageConverters里的StringHttpMessageConverter删除了,看自己的选择。


@Configuration
public class MyWebmvcConfiguration implements WebMvcConfigurer {

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //删除springboot默认的StringHttpMessageConverter解析器
        converters.removeIf(x -> x instanceof StringHttpMessageConverter);
    }
}

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在使用Spring MVC框架的项目中,可以通过不同的方式返回不同的响应码。以下是几种常见的方法: 1. 使用`@ResponseStatus`注解:可以在控制器方法上使用`@ResponseStatus`注解来指定响应码。例如,`@ResponseStatus(HttpStatus.OK)`表示返回200 OK响应码,`@ResponseStatus(HttpStatus.NOT_FOUND)`表示返回404 Not Found响应码。 ```java @Controller public class MyController { @GetMapping("/example") @ResponseStatus(HttpStatus.OK) public String example() { // 处理逻辑 return "example"; } } ``` 2. 使用`ResponseEntity`:`ResponseEntity`是Spring MVC中的一个类,可以用于封装具体的响应数据以及响应头和响应码。通过创建一个`ResponseEntity`对象,并设置对应的响应码,然后将其作为方法的返回值即可。 ```java @Controller public class MyController { @GetMapping("/example") public ResponseEntity<String> example() { // 处理逻辑 return ResponseEntity.status(HttpStatus.OK).body("Hello World"); } } ``` 3. 使用`HttpServletResponse`:可以在控制器方法中通过`HttpServletResponse`对象来设置响应码。可以使用`setStatus()`方法设置响应码,如`response.setStatus(HttpServletResponse.SC_NOT_FOUND)`表示返回404 Not Found响应码。 ```java @Controller public class MyController { @GetMapping("/example") public void example(HttpServletResponse response) throws IOException { // 处理逻辑 response.setStatus(HttpServletResponse.SC_OK); response.getWriter().write("Hello World"); } } ``` 通过以上方法,您可以根据具体的业务需求返回不同的响应码。请根据您的项目需求选择合适的方式。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值