1、需求背景

       在做项目的时候你可能接到这样的需求,对于springboot项目而言,公司大佬们说默认的返回数据结构不能满足客户的需要,咱们自己必须封装出牛逼的数据结构,看起来很吊很吊的那种,这样对外提供的接口文档才牛逼,让别人看起来我司很正规,有一套自己的规范,巴拉巴拉巴拉一大堆。。。。

       其实这个情况在各个公司还是比较常见的。但具体咋实现嘞,总不能在每个方法里面都写一段代码来保证数据结构的一致性吧,这样太傻了。通过这篇博客就搞一下怎样简洁的实现此功能。具体思路是这样的:

1、自定义一个注解@ExeResponse,凡是被这个注解标记的方法或者类都会返回标准化的数据格式,其余的都返回正常的数据格式。

2、通过反射机制获取被@ExeResponse注解标记的类或者方法进而进行数据封装。

3、封装完毕的数据结构返回到调用方。

2、划知识点

1、自定义注解

注解这个东西的使用,个人比较随意的理解就是打标记。把凡是被打过标记的类或者方法使用一定的方式(比如java的反射机制)进行集中进行处理。可能不太好理解,在下面的内容详细进行叙述一下,不熟悉这块的同学也可以自行了解。

2、@ConditionalOnBean注解

这个注解为条件注解,具体的用法为@ConditionalOnBean({ A.class })。只有A类被加载以后,被@ConditionalOnBean标记的类才会加载。

3、@ConditionalOnProperty注解

spring boot中通过控制配置文件的参数属性来控制@Configuration是否生效。具体用法往下看或者自行搜索

4、WebMvcConfigurer接口

spring boot2.0以后可以通过实现WebMvcConfigurer来自定义一些拦截器、页面跳转、视图解析器、信息转换器等一些骚操作。本次就通过自定义一个拦截器进而处理被自定义注解标记的类或者方法。

5、HandlerInterceptor接口

自定义拦截器我想大家都并不陌生了,最常用的登录拦截、或是权限校验等等,这次用于反射操作类或者方法。

6、ResponseBodyAdvice接口

这个接口一般用于对请求后数据结构的封装、加密等操作。

7、反射机制

官方的解释:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

的确是这个样子,java反射机制基本上可以说是一些架构的灵魂所在了,不太明白的同志可以下去好好研究一下。

3、核心代码实现

1、自定义@ExeResponse注解

public @interface ExeResponse {
	Class<? extends Results> valus() default ExeResult.class;
}

2、自定义拦截器

@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 });
	                }
	            }
	        }
	    }

}

3、自定义实现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;
	}

}

4、用于测试的两个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";
	}
}

4、启动调用

springboot工程自定义response注解、自定义规范化返回数据结构-LMLPHP

springboot工程自定义response注解、自定义规范化返回数据结构-LMLPHP

springboot工程自定义response注解、自定义规范化返回数据结构-LMLPHP

5、总结

以上为实现该功能的大致过程,简单的request、response的流程图如下

springboot工程自定义response注解、自定义规范化返回数据结构-LMLPHP

具体的源码下载链接https://download.csdn.net/download/lw1124052197/12800131,由于个人C币已经不太够用,下载会象征性收几个币,见谅,不喜勿喷。

09-13 17:12