1 整体说明
上一篇博客《搭建公共服务实现统一后端接口的返回数据格式》介绍了如何搭建公共服务完成统一后端接口的返回数据格式,并在业务服务中引入公共服务并使用。公共服务引入并配置生效后,默认对所有的Rest接口生效。为了提供更多的灵活性,我们对公共服务进行改造,提供自定义注解用于代替@RestController
,只有引用了自定义注解的controller接口统一封装返回数据格式,对@RestController
注解标识的controller接口不做处理。
在上一篇我们提到通过@RestControllerAdvice
来拦截所有的@RestController
,@RestControllerAdvice
注解搭配ResponseBodyAdvice
接口可以实现在接口调用正常时的封装返回数据。ResponseBodyAdvice
接口的support
方法返回值决定是否对当前controller接口应用ResponseBodyAdvice
接口的beforeBodyWrite
方法,`ResponseBodyAdvice
接口的也是我们封装返回数据格式的逻辑所在。
为了实现开篇提到的以只拦截封装用自定义注解修饰的controller接口的返回结果,我们可以在ResponseBodyAdvice#support
中判断当前controller是否带自定义注解,如果是那么返回true,否则返回false不再经过ResponseBodyAdvice#beforeBodyWrite
方法去封装。
那现在的问题变成了如何在ResponseBodyAdvice#support
中知道controller接口的注解信息,网上搜一把发现可以通过WebMvcConfigurer
和HandlerInterceptorAdapter
接口拿到controller的注解信息。那我们可以把拿到的注解信息作为请求属性塞到HttpServletRequest
实例request
中,然后在ResponseBodyAdvice#support
直接获取requst
的对应属性值即可。
基本逻辑梳理完成,开干。当然WebMvcConfigurer
和HandlerInterceptorAdapter
接口的工作原理、HttpServletRequest
的生命周期还需要进一步梳理,不过不影响目前使用。
参考资料:
SpringMVC拦截器中获得Controller方法名和注解信息(用于验证权限)
精通SpringBoot——第三篇:详解WebMvcConfigurer接口
2 公共服务改造
2.1 注解定义
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
@RestController
@RequestMapping
public @interface ComRestController {
@AliasFor(annotation = RequestMapping.class) String[] value() default {};
@AliasFor(annotation = RequestMapping.class) String[] path() default {};
@AliasFor(annotation = RequestMapping.class) String[] produces() default {"application/json;charset=UTF-8"};
@AliasFor(annotation = RequestMapping.class) String[] consumes() default {};
}
复制代码
2.2 WebMvcConfigurer接口实现
@Configuration //必须加这个注解, Spring才能统一管理当前的拦截器实例
public class ComRestControllerConfigure implements WebMvcConfigurer {
/* 在应用启动阶段就执行 */
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ComRestControllerInterceptor());
}
}
复制代码
2.3 HandlerInterceptor接口实现
public class ComRestControllerInterceptor implements HandlerInterceptor {
public static final String REST_CONTROLLER_ANNO = "REST_CONTROLLER_ANNO";
/* 在rest接口前执行该方法 */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Class<?> clazz = handlerMethod.getBeanType();
request.setAttribute(REST_CONTROLLER_ANNO, clazz.isAnnotationPresent(ComRestController.class));
}
// 返回true时, 执行拦截器链上下一个拦截器, 直到所有拦截器的preHandle方法执行完成后, 再执行被拦截的请求
// 返回false时, 不再执行拦截器链后续的preHandle方法以及被拦截的请求
return true;
}
}
复制代码
2.4 封装接口异常的返回结果
只需要修改support
方法,间接判断当前controller是否带有ComRestController
注解即可。
@RestControllerAdvice //拦截Controller方法的返回值, 统一处理返回值/响应体, 一般用于统一返回格式、加解密、签名等等
public class RestResponseAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private ObjectMapper objectMapper;
/*
* 是否支持advice功能
* true支持, false不支持
*/
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 带有自定义的ComRestController注解则启用封装返回结果数据, 否则不启用封装逻辑
if(requestAttributes instanceof ServletRequestAttributes) {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
return Boolean.parseBoolean(String.valueOf(servletRequestAttributes.getRequest().
getAttribute(ComRestControllerInterceptor.REST_CONTROLLER_ANNO)));
}
return true;
}
/*
* 处理返回的结果数据
*/
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if(o instanceof String){
return objectMapper.writeValueAsString(ResultData.success(o));
}
if (o instanceof ResultData) {
return o;
}
return ResultData.success(o);
}
}
复制代码
2.5 项目结构
3 业务服务引用
公共服务的安装、业务服务引入公共服务及配置参考上一篇博客,这里不再赘述。
3.1 接口验证
(1)使用SpringBoot的@RestController
注解
@RestController
@RequestMapping("/api/original")
public class RestOriginal {
@GetMapping("/success")
public String GetSuccess() {
return "hello, don!";
}
@GetMapping("/error")
public int GetError() {
return 6/0;
}
}
复制代码
接口调用结果没有进行封装处理,直接返回字符串。
(2)使用自定义的的@ComRestController
注解
@ComRestController //自定义注解
@RequestMapping("/api/anno")
public class RestAnno {
@GetMapping("/success")
public String GetSuccess() {
return "hello, don!";
}
@GetMapping("/error")
public int GetError() {
return 6/0;
}
}
复制代码
接口调用结果经过统一的封装处理
近期评论