springboot工程自定義response註解、自定義規範化返回資料結構
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、啟動呼叫
5、總結
以上為實現該功能的大致過程,簡單的request、response的流程圖如下
具體的原始碼下載連結https://download.csdn.net/download/lw1124052197/12800131,由於個人C幣已經不太夠用,下載會象徵性收幾個幣,見諒,不喜勿噴。