在springBoot项目中为了统一返回值的格式,一般都会用一个@ControllerAdvice,比如:
@ControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof R) {
return body;
}
return R.ok(body);
}
}
有了这个东西以后,controller的返回值就可以随心所欲的定义了,比如:
@GetMapping("/demo")
public User demo(){
return new User("1", "xjs");
}
实际的输出是这样:
{
"code":0,
"msg":"OK",
"data":{
"id":"1","name":"xjs"
}
}
但是,如果controller返回的是一个简单字符串,比如:
@GetMapping("/hello")
public String hello(){
return "hello";
}
请求却报错了,异常信息如下:
java.lang.ClassCastException:
com.github.xjs.demo.dto.R
cannot be cast to
java.lang.String
看下源码: 经过advice处理以后,原始的String类型已经 变成了R类型,当再使用StringHttpMessageConverter去输出的时候就报异常了。 解决办法1:去掉StringHttpMessageConverter
@Configuration
public class HttpConverterConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
}
}
暂时看上去没啥问题,直到遇见了actuator。 此时再去访问http://localhost:8080/actuator/health,输出结果同样被转化成R格式输出了:
{
"code":0,
"msg":"OK",
"data":{"status":"UP"}
}
所以,简单的去掉StringHttpMessageConverter太过于简单暴力,并不能解决这个文问题。
解决方法2: 对于String类型的返回值,是因为StringHttpMessageConverter只能输出String类型,不能输出其他类型,因此,提前把R转化成String就可以了,比如:
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof R) {
return body;
}else if (body instanceof String) {
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
return jsonService.toJsonString(R.ok(body));
}
return R.ok(body);
}
但是,此时还是不能正常访问/actuator接口,因为/actuator返回的并不是String, 同时我们也不想改变返回的格式,因此需要排除下:
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof R) {
return body;
}else if (body instanceof String) {
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
return jsonService.toJsonString(R.ok(body));
}else if(body instanceof HealthComponent){
return body;
}
return R.ok(body);
}
这样/actuator/health就正常了,直到遇到了/actuator/prometheus,这个端点输出的数据类型也是String类型,显然被包装成了R类型,因此这种方式也不合适了。 解决办法3: 因为/actuator接口都是以/actuator开头的,因此可以把这种类型的全排除掉,比如:
@ControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
@Autowired
private JsonService jsonService;
@Autowired
private WebEndpointProperties endpointProperties;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
String uri = request.getRequestURI();
if(!endpointProperties.getDiscovery().isEnabled()){
return true;
}
String basePath = endpointProperties.getBasePath();
if(StringUtils.isEmpty(basePath)){
basePath = "/actuator/";
}
if(uri.startsWith(basePath)){
return false;
}
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof R) {
return body;
} else if (body instanceof String) {
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
return jsonService.toJsonString(R.ok(body));
}
return R.ok(body);
}
}
done。
|