大家发现没有,前面我们在实现UserController
时,API的方法签名返回值用的是统一的Response
,在返回时,都要自己调一下Response.ok(...)
。我们可不可以就按照service的返回结果,来和controller的返回值实现统一呢?这一节我们就来探究下。
API示例
现在我们抛开之前的UserAPI
,写一个TestAPI
来做试验:
package com.xiaojuan.boot.web.api;
import ...
@RequestMapping("test")
public interface TestAPI {
@GetMapping("msg")
String msg();
@PostMapping("post")
void post(HttpServletResponse response);
@PostMapping("post2")
void post2();
}
TestController
实现非常简单,这里就不贴出来了。
再写一个单元测试:
package com.xiaojuan.boot.web.controller;
import ...
public class TestControllerTest extends WebTestBase {
@Test
public void test() {
get("/test/msg", String.class);
postForm("/test/post", Void.class, null);
postForm("/test/post2", Void.class, null);
}
}
实现ResponseBodyAdvice接口
为了达到全局统一response的目的,我们可以实现一个ResponseBodyAdvice
接口,看下代码:
package com.xiaojuan.boot.common.web.support;
import ...
@Slf4j
@RestControllerAdvice
public class RestBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 不过滤,对所有RestController都应用
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
log.info("======= url = {}, body = {}", request.getURI(), body);
return null;
}
}
运行单元测试,看打印的日志:
很显然,我们发现当我们设计的API中有注入HttpServletResponse
类型的响应对象时,这种响应体的拦截机制不管用了。这是我们在使用这种方式处理统一响应要特别注意的地方!这种情况下,我们可以将返回值改为String
返回""
即可。
@Override
public String post(HttpServletResponse response) {
return SysConst.EMPTY_STRING;
}
然后,我们完善下解析方法:
@Override
public Object beforeBodyWrite(...) {
if (body instanceof String) {
// 字符串需要手动序列化为json
response.getHeaders().set("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE);
return "{\"status\":0, \"data\": \"" + body + "\"}";
}
return Response.ok(body);
}
改造UserAPI
返回值
现在再来看下UserAPI
的返回值的调整:
UserController
中相应的调整这里就不贴出来了。我们满怀期待的跑下UserControllerTest
,发现第一个单元测试就不通过,通过断点调试,我们发现在经过全局异常处理后我们已经包装成了Response
,此时在全局响应处理方法中,我们应当直接返回,不应该再包装一层:
调整为:
@Override
public Object beforeBodyWrite(...) {
if (body instanceof String) {
...
} else if (body instanceof Response) {
return body;
}
return Response.ok(body);
}
最后我们再跑一次UserControllerTest
,ok!搞定!
我们的改造又一次经受住了考验,圆满完成任务!