一、需求背景
在作项目的时候你可能接到这样的需求,对于springboot项目而言,公司大佬们说默认的返回数据结构不能知足客户的须要,我们本身必须封装出牛逼的数据结构,看起来很吊很吊的那种,这样对外提供的接口文档才牛逼,让别人看起来我司很正规,有一套本身的规范,巴拉巴拉巴拉一大堆。。。。java
其实这个状况在各个公司仍是比较常见的。但具体咋实现嘞,总不能在每一个方法里面都写一段代码来保证数据结构的一致性吧,这样太傻了。经过这篇博客就搞一下怎样简洁的实现此功能。具体思路是这样的:spring
一、自定义一个注解@ExeResponse,凡是被这个注解标记的方法或者类都会返回标准化的数据格式,其他的都返回正常的数据格式。springboot
二、经过反射机制获取被@ExeResponse注解标记的类或者方法进而进行数据封装。数据结构
三、封装完毕的数据结构返回到调用方。架构
二、划知识点
一、自定义注解app
注解这个东西的使用,我的比较随意的理解就是打标记。把凡是被打过标记的类或者方法使用必定的方式(好比java的反射机制)进行集中进行处理。可能不太好理解,在下面的内容详细进行叙述一下,不熟悉这块的同窗也能够自行了解。ide
二、@ConditionalOnBean注解post
这个注解为条件注解,具体的用法为@ConditionalOnBean({ A.class })。只有A类被加载之后,被@ConditionalOnBean标记的类才会加载。测试
三、@ConditionalOnProperty注解this
spring boot中经过控制配置文件的参数属性来控制@Configuration是否生效。具体用法往下看或者自行搜索
四、WebMvcConfigurer接口
spring boot2.0之后能够经过实现WebMvcConfigurer来自定义一些拦截器、页面跳转、视图解析器、信息转换器等一些骚操做。本次就经过自定义一个拦截器进而处理被自定义注解标记的类或者方法。
五、HandlerInterceptor接口
自定义拦截器我想你们都并不陌生了,最经常使用的登陆拦截、或是权限校验等等,此次用于反射操做类或者方法。
六、ResponseBodyAdvice接口
这个接口通常用于对请求后数据结构的封装、加密等操做。
七、反射机制
官方的解释:JAVA反射机制是在运行状态中,对于任意一个类,都可以知道这个类的全部属性和方法;对于任意一个对象,都可以调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
的确是这个样子,java反射机制基本上能够说是一些架构的灵魂所在了,不太明白的同志能够下去好好研究一下。
三、核心代码实现
一、自定义@ExeResponse注解
public @interface ExeResponse { Class<? extends Results> valus() default ExeResult.class; }
二、自定义拦截器
@Component //检查在配置文件中是否有exe-response.enabled参数,有并设置为true,注入ResponseResultInterceptor @ConditionalOnProperty(name = { "exe-response.enabled" }, havingValue = "true", matchIfMissing = true) public class ResponseResultInterceptor implements HandlerInterceptor{ public static final String RESPONSE_RESULT = "RESPONSE_RESULT"; public static final String REQUEST_ID = "request_Id"; private static final String REQUEST_TIME = "REQUEST_TIME"; private static final Logger LOGGER = LoggerFactory.getLogger(ResponseResultInterceptor.class); public ResponseResultInterceptor() { LOGGER.info("exe-response.enabled use default value: true"); } /** * preHandle方法在业务处理器处理请求以前被调用,进行预处理 * 第一步根据访问的class或method判断是否被注解标记,若是是则放入HttpServletRequest中 */ public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) { //当handle为HandlerMethod类或者子类建立的对象时 if (handler instanceof HandlerMethod) { //handler转化为HandlerMethod final HandlerMethod handlerMethod = (HandlerMethod)handler; //获取这次访问的controller的class类 final Class<?> clazz = (Class<?>)handlerMethod.getBeanType(); //获取这次访问的method方法 final Method method = handlerMethod.getMethod(); //判断method是否被IdcResponse注解标注 if (method.isAnnotationPresent(ExeResponse.class)) { //若是method被IdcResponse注解标记,取出放入HttpServletRequest中 request.setAttribute(RESPONSE_RESULT, (Object)method.getAnnotation(ExeResponse.class)); //记录时间戳 request.setAttribute(REQUEST_TIME, (Object)System.currentTimeMillis()); } else if (clazz.isAnnotationPresent(ExeResponse.class)) { //若是class被IdcResponse注解标记,取出放入HttpServletRequest中 request.setAttribute(RESPONSE_RESULT, (Object)clazz.getAnnotation(ExeResponse.class)); request.setAttribute(REQUEST_TIME, (Object)System.currentTimeMillis()); } } return true; } /** * postHandle方法在业务处理器处理请求执行完成后,生成视图以前执行 * 最后一步,打印这次访问的信息 */ public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView modelAndView) { } /** * 在DispatcherServlet彻底处理完请求后被调用,可用于数据返回处理 * 最后一步,打印这次访问的信息 */ public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) { if (handler instanceof HandlerMethod) { final HandlerMethod handlerMethod = (HandlerMethod)handler; final Class<?> clazz = (Class<?>)handlerMethod.getBeanType(); final Method method = handlerMethod.getMethod(); if (method.isAnnotationPresent(ExeResponse.class) || clazz.isAnnotationPresent(ExeResponse.class)) { final String requestID = request.getHeader(REQUEST_ID); final int statusCode = response.getStatus(); final long requestTime = (long)request.getAttribute(REQUEST_TIME); if (statusCode != HttpStatus.OK.value()) { LOGGER.error("RequestID: {}, Method: {}, Response Time: {}ms, HttpStatus: {}", new Object[] { requestID, method.getName(), System.currentTimeMillis() - requestTime, statusCode, ex }); } else { LOGGER.info("RequestID: {}, Method: {}, Response Time: {}ms", new Object[] { requestID, method.getName(), System.currentTimeMillis() - requestTime }); } } } } }
三、自定义实现ResponseBodyAdvice接口的功能类
@ControllerAdvice //ResponseResultInterceptor存在的状况下才会把ResponseResultHandler注入 @ConditionalOnBean({ ResponseResultInterceptor.class }) public class ResponseResultHandler implements ResponseBodyAdvice<Object>{ public static final String RESPONSE_RESULT = "RESPONSE_RESULT"; public static final String REQUEST_ID = "request_Id"; private static final Logger LOGGER = LoggerFactory.getLogger(ResponseResultHandler.class); /** * 第二步判断HttpServletRequest中是否有注解对象 */ @Override public boolean supports(MethodParameter returnType, Class converterType) { //生成HttpServletRequest HttpServletRequest hsr = HttpServletUtils.getRequest(); //取出HttpServletRequest中的注解 Object obj = hsr.getAttribute(RESPONSE_RESULT); //转换成IdcResponse注解 final ExeResponse responseResultAnn = (ExeResponse)obj; //不等于空返回true return responseResultAnn != null; } /** * 在数据返回以前更改格式 */ @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { //获取HttpServletRequest对象 final HttpServletRequest httpRequest = HttpServletUtils.getRequest(); //取出IdcResponse注解类 final ExeResponse idcResponse = (ExeResponse)httpRequest.getAttribute(RESPONSE_RESULT); //取出IdcResponse的valus,Results或者其子类 final Class<? extends Results> resultClazz = idcResponse.valus(); Object objBuffer = null; try { //ServerHttpResponse赋值,在ServerHttpRequest中取 HttpServletUtils.getResponse().setHeader(REQUEST_ID, httpRequest.getHeader(REQUEST_ID)); //resultClazz实例化 final Results result = (Results)resultClazz.newInstance(); //给Results赋值,body为查询到的数据 result.setCode(ExeResultCode.SUCCESS.code()); result.setMsg(ExeResultCode.SUCCESS.message()); result.setCompany(ExeResultCode.SUCCESS.company()); result.setData(body); if (body instanceof String || selectedConverterType.isAssignableFrom(StringHttpMessageConverter.class)) { objBuffer = result.toJson(); } else if (body instanceof Results) { objBuffer = body; } else { objBuffer = result; } } catch (InstantiationException | IllegalAccessException ex2) { final ReflectiveOperationException ex = null; final ReflectiveOperationException e = ex; objBuffer = new ExeResult(ExeResultCode.SYSTEM_INNER_ERROR.code(), e.getMessage()); ResponseResultHandler.LOGGER.error("BeforeBodyWrite append erro!", (Throwable)e); } return objBuffer; } }
四、用于测试的两个controller
/** * 测试标记类 * @author Administrator * */ @RestController @ExeResponse @RequestMapping(value = "/testclass") public class ExeResponseClassController { @RequestMapping(value = "/testResponseClass",method = RequestMethod.GET) public String testResponseMethed() { return "this is a test class"; } }
@RestController @RequestMapping(value = "/testmethod") public class ExeResponseMethodController { /** * 测试标记方法 * @return */ @ExeResponse @RequestMapping(value = "/testResponseMethod",method = RequestMethod.GET) public String testResponseMethed() { return "this is a test method"; } /** * 未标记的方法 * @return */ @RequestMapping(value = "/noResponseMethod",method = RequestMethod.GET) public String noResponseMethod() { return "this is a no response method"; } }
四、启动调用
五、总结
以上为实现该功能的大体过程,简单的request、response的流程图以下
具体的源码下载连接https://download.csdn.net/download/lw1124052197/12800131,因为我的C币已经不太够用,下载会象征性收几个币,见谅,不喜勿喷。