springboot工程自定義response註解、自定義規範化返回數據結構

一、需求背景

       在作項目的時候你可能接到這樣的需求,對於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幣已經不太夠用,下載會象徵性收幾個幣,見諒,不喜勿噴。

相關文章
相關標籤/搜索