在最流行的前后端交互的項目中,后端一般都是返回指定格式的數據供前端解析,本文使用注解方式返回統一格式的數據,那么下面就看看是如何使用的吧
1)返回響應碼實體
package com.zxh.example.entity.model; import lombok.Data; public enum ResultCode { SUCCESS(200, "處理成功"), FAILURE(201, "處理失敗"), PARAM_ERROR(400, "參數錯誤"), NOT_PERMISSION(402, "未授權,無法訪問"), WRONG_PASSWORD(403, "用戶名或密碼錯誤"), NOT_FOUND(404, "文件不存在或已被刪除"), METHOD_NOT_SUPPORT(405, "方法不被允許"), SERVER_ERROR(500, "服務器異常,請聯系管理員"); private Integer code; private String message; ResultCode(Integer code, String message) { this.code = code; this.message = message; } public Integer code(){ return this.code; } public String message(){ return this.message; } }
2)返回數據實體
package com.zxh.example.entity.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.io.Serializable; @Data @AllArgsConstructor @NoArgsConstructor @Accessors(chain = true) public class ResultData implements Serializable { //請求狀態碼 private Integer code; //返回信息描述 private String message; //返回內容 private Object data; public ResultData(Integer code, String message) { this.code = code; this.message = message; } public ResultData(ResultCode resultCode, Object data) { this.code = resultCode.code(); this.message = resultCode.message(); this.data = data; } public ResultData(ResultCode resultCode) { this.code = resultCode.code(); this.message = resultCode.message(); } public static ResultData success() { return new ResultData(ResultCode.SUCCESS); } public static ResultData success(Object data) { return new ResultData(ResultCode.SUCCESS, data); } public static ResultData failure(Integer code, String msg) { return new ResultData(code, msg); } public static ResultData failure(ResultCode resultCode) { return new ResultData(resultCode); } public static ResultData failure(ResultCode resultCode, Object data) { return new ResultData(resultCode, data); } }
若上述靜態方法還不滿足,可自定義添加。
3)定義注解
package com.zxh.example.anno; import java.lang.annotation.*; /** * 返回體標記注解 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD, ElementType.TYPE}) public @interface ResponseResult { }
此注解用在類或方法上,用來標記是否需要統一格式返回。
4)定義系統常量
package com.zxh.example.config; public class SysConst { public static final String RESPONSE_KEY = "response_result"; }
對於多個地方共有的方法或變量,應公共定義。
5)配置響應攔截器
package com.zxh.example.config; import com.zxh.example.anno.ResponseResult; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; /** * 響應攔截器 */ @Component public class ResponseResultInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { //是否是請求的方法,若在方法或類上使用了注解,就設置到request對象中 if (handler instanceof HandlerMethod) { final HandlerMethod handlerMethod = (HandlerMethod) handler; final Class<?> clazz = handlerMethod.getBeanType(); final Method method = handlerMethod.getMethod(); Class<ResponseResult> aClass = ResponseResult.class; //判斷是否需要轉換,在類上或者方法上 if (clazz.isAnnotationPresent(aClass)) { request.setAttribute(SysConst.RESPONSE_KEY, clazz.getAnnotation(aClass)); } else if (method.isAnnotationPresent(aClass)) { request.setAttribute(SysConst.RESPONSE_KEY, method.getAnnotation(aClass)); } } return true; } }
此攔截器的主要作用是判斷是否在類或方法上添加了上述定義的注解,若添加了則給request屬性設置參數,以便在響應體返回時判斷是否需要重寫。
6)重寫響應體
package com.zxh.example.config; import com.zxh.example.anno.ResponseResult; import com.zxh.example.entity.model.ResultData; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import javax.servlet.http.HttpServletRequest; @ControllerAdvice public class ResponseResultHandler implements ResponseBodyAdvice<Object> { /** * 是否使用了包裝注解,若使用了則會走 beforeBodyWrite方法 */ @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); ResponseResult responseResult = (ResponseResult) request.getAttribute(SysConst.RESPONSE_KEY); //若返回false則下面的配置不生效 return responseResult == null ? false : true; } @Override public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { if (body instanceof ResultData) { return body; } return ResultData.success(body); } }
此類用於判斷是否需要統一返回,若需要則按格式重寫響應體並返回,否則不重寫。
7)配置mvc攔截器
package com.zxh.example.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; /** * mvc配置類,配置攔截器 */ @Configuration public class WebAppConfigurer implements WebMvcConfigurer { @Autowired private ResponseResultInterceptor interceptor; /** * 配置攔截器 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(interceptor).addPathPatterns("/**"); } /** * 配置消息轉換器 * @param converters */ @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { //去掉String類型轉換器。否則當controller返回String類型時會出現類型轉換異常ClassCastException converters.removeIf(httpMessageConverter -> httpMessageConverter.getClass() == StringHttpMessageConverter.class); } }
將響應攔截器加入mvc攔截器中,使其生效。若不配置則響應攔截器不會生效。
需要注意的是,必須要配置消息轉換器,用來去掉String類型轉換器,否則當controller中方法返回類型是String時會報錯!
8)創建controller接口
package com.zxh.example.controller; import com.zxh.example.anno.ResponseResult; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api") @Slf4j //@ResponseResult public class TestController { @ResponseResult @GetMapping("/test") public String test(String name) { return "hello" + name; } @GetMapping("/test2") public Map test2() { Map<String, Object> map = new HashMap<>(); map.put("name", "張三"); map.put("age", 20); return map; } }
此接口中有兩個方法,一個使用注解標記了,另一個直接返回相應的對象。
9)自定義異常類(需要時可創建)
package com.zxh.example.config; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; /** * 自定義異常類 */ @Setter @Getter @AllArgsConstructor @NoArgsConstructor public class ApiExceptionHandler extends RuntimeException { /** * 錯誤碼 */ protected Integer errorCode; /** * 錯誤信息 */ protected String errorMsg; public ApiExceptionHandler(String errorMsg) { super(errorMsg); this.errorMsg = errorMsg; } public ApiExceptionHandler(Integer errorCode, String errorMsg, Throwable cause) { super(errorCode.toString(), cause); this.errorCode = errorCode; this.errorMsg = errorMsg; } @Override public synchronized Throwable fillInStackTrace() { return this; } }
10)全局異常處理(需要時可創建)
package com.zxh.example.config; import com.zxh.example.entity.model.ResultCode; import com.zxh.example.entity.model.ResultData; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.io.FileNotFoundException; /** * 全局異常處理 */ @ControllerAdvice @Slf4j @RestController public class GlobalExceptionHandler { /** * 處理自定義的業務異常 * * @param e * @return */ @ExceptionHandler(value = ApiExceptionHandler.class) public ResultData apiExceptionHandler(ApiExceptionHandler e) { log.error("發生業務異常!原因是:{}", e.getErrorMsg()); return ResultData.failure(e.errorCode, e.errorMsg); } /** * 處理空指針的異常 * * @param e * @return */ @ExceptionHandler(value = NullPointerException.class) public ResultData exceptionHandler(NullPointerException e) { log.error("發生空指針異常!原因是:", e); return ResultData.failure(ResultCode.PARAM_ERROR); } /** * 處理文件找不到的異常 * * @param e * @return */ @ExceptionHandler(value = FileNotFoundException.class) public ResultData exceptionHandler(FileNotFoundException e) { log.error("發生文件找不到異常!原因是:", e); return ResultData.failure(ResultCode.NOT_FOUND); } /** * 索引越界的異常 * * @param e * @return */ @ExceptionHandler(value = ArrayIndexOutOfBoundsException.class) public ResultData exceptionHandler(ArrayIndexOutOfBoundsException e) { log.error("發生數組索引越界異常!原因是:", e); return ResultData.failure(ResultCode.SERVER_ERROR.code(), "數組越界,請檢查數據"); } /** * 處理其他異常 * * @param e * @return */ @ExceptionHandler(value = Exception.class) public ResultData exceptionHandler(Exception e, HttpServletResponse response) { response.setCharacterEncoding("UTF-8"); log.error("發生未知異常!原因是:", e); return ResultData.failure(ResultCode.SERVER_ERROR); } }
異常處理是必不可少的,在異常中也需要統一返回格式。
11)啟動測試
啟動項目,分別訪問接口 localhost:8080/api/test?name=11112 和 localhost:8080/api/test2,返回結果如下
圖1
圖2
上述就是在方法上使用注解 @ResponseResult
進行統一返回格式,與此同時controller中的方法也都根據自己的特性返回了對應實體。
若此controller中所有方法都需要統一格式,則可直接在類加上注解 @ResponseResult
即可,無需在每個方法上都加此注解。
12)分頁補充
對於需要分頁返回的格式,則可再新建一個實體類,用於存儲分頁的數據,
package com.zxh.example.entity.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.io.Serializable; @Data @AllArgsConstructor @NoArgsConstructor @Accessors(chain = true) public class ResultPage implements Serializable { //數據內容 private Object data; //數據總條數 private Long total; }
在controller中新建一個方法模擬分頁返回的數據,添加注解進行標記即可
@ResponseResult @GetMapping("/test3") public ResultPage test3() { List<String> list = Arrays.asList("123", "23444", "8544"); return new ResultPage(list, 3L); }
返回數據如下圖