preface
At present, we basically choose front-end and back-end separation for large and medium-sized projects. Front-end and back-end separation has become a trend, so always. We need to agree on a unified api interface with the front end to return json format,
In this way, we need to encapsulate a unified general global template api return format, which can be used directly when writing the project next time
Convention JSON format
Generally, we agree with the front end that the json format is like this
{ "code": 200, "message": "success", "data": { } }
- Code: return status code
- Message: description of the returned message
- data: return value
Encapsulating Java beans
Define state enumeration
package cn.soboys.core.ret; import lombok.Data; import lombok.Getter; /** * @author kenx * @version 1.0 * @date 2021/6/17 15:35 * Response code enumeration, corresponding to HTTP status code */ @Getter public enum ResultCode { SUCCESS(200, "success"),//success //FAIL(400, "failed"), / / failed BAD_REQUEST(400, "Bad Request"), UNAUTHORIZED(401, "Authentication failed"),//Not certified NOT_FOUND(404, "Interface does not exist"),//Interface does not exist INTERNAL_SERVER_ERROR(500, "System busy"),//Server internal error METHOD_NOT_ALLOWED(405,"Method not allowed"), /*Parameter error: 1001-1999*/ PARAMS_IS_INVALID(1001, "Invalid parameter"), PARAMS_IS_BLANK(1002, "The parameter is null"); /*User error 2001-2999*/ private Integer code; private String message; ResultCode(int code, String message) { this.code = code; this.message = message; } }
Define the return status code, which corresponds to the information one by one. We can agree on the error code of xxx ~ xxx, so as to prevent the repetition of error codes in the later stage, confusion and unclear use,
Define return result body
package cn.soboys.core.ret; import lombok.Data; import java.io.Serializable; /** * @author kenx * @version 1.0 * @date 2021/6/17 15:47 * Unified API response result format encapsulation */ @Data public class Result<T> implements Serializable { private static final long serialVersionUID = 6308315887056661996L; private Integer code; private String message; private T data; public Result setResult(ResultCode resultCode) { this.code = resultCode.getCode(); this.message = resultCode.getMessage(); return this; } public Result setResult(ResultCode resultCode,T data) { this.code = resultCode.getCode(); this.message = resultCode.getMessage(); this.setData(data); return this; } }
code, and message are obtained from the defined state enumeration
There are two things to note here. My data type T data returns a generic type instead of an object type, and my results implement the Serializable interface
I see that many objects are returned on the Internet. Finally, generic types are returned because the efficiency of generic types is higher than that of objects. Objects need forced type conversion. Finally, the Serializable interface is implemented because it is transmitted through the web through streaming bytes, which is faster
Define the method to return the result
Generally, business returns either success or failure, so we need to define two methods to return entity objects separately,
package cn.soboys.core.ret; /** * @author kenx * @version 1.0 * @date 2021/6/17 16:30 * Response result return encapsulation */ public class ResultResponse { private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS"; // Return status only public static Result success() { return new Result() .setResult(ResultCode.SUCCESS); } // Data returned successfully public static Result success(Object data) { return new Result() .setResult(ResultCode.SUCCESS, data); } // fail public static Result failure(ResultCode resultCode) { return new Result() .setResult(resultCode); } // fail public static Result failure(ResultCode resultCode, Object data) { return new Result() .setResult(resultCode, data); } }
Note that I define static tool methods here, because it is too troublesome to use constructor methods to create object calls. It is convenient for us to use static methods to directly call classes
In this way, we can easily return the unified api format in the controller
package cn.soboys.mallapi.controller; import cn.soboys.core.ret.Result; import cn.soboys.core.ret.ResultResponse; import cn.soboys.mallapi.bean.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author kenx * @version 1.0 * @date 2021/7/2 20:28 */ @RestController //json is returned by default @RequestMapping("/user") public class UserController { @GetMapping("/list") public Result getUserInfo(){ User u=new User(); u.setUserId("21"); u.setUsername("kenx"); u.setPassword("224r2"); return ResultResponse.success(u); } }
The returned result conforms to our expected json format
However, this code can be optimized and is not perfect. For example, every time the return of all methods in the controller must be the Result type. We want to return the format of other types, and it is not semantic enough. Other developers simply don't know what information your method returns
It would be perfect if it changed to this
@GetMapping("/list") public User getUserInfo() { User u = new User(); u.setUserId("21"); u.setUsername("kenx"); u.setPassword("224r2"); return u; }
Other developers know exactly what data is returned at a glance. But how can this format be unified?
In fact, we can optimize it in this way and conduct unified response processing through the ResponseBodyAdvice provided by SpringBoot
- Customize the annotation @ ResponseResult to intercept the representative of this controller annotation class. It needs to return the json format uniformly. If not, it will return as it was
package cn.soboys.core.ret; import java.lang.annotation.*; /** * @author kenx * @version 1.0 * @date 2021/6/17 16:43 * The value Result returned by the unified packaging interface */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ResponseResult { }
- Define the handler method with this annotation obtained by the request interceptor through reflection, and set the packaging interception flag
package cn.soboys.core.ret; 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; /** * @author kenx * @version 1.0 * @date 2021/6/17 17:10 * Request interception */ public class ResponseResultInterceptor implements HandlerInterceptor { //Tag name public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //Request method if (handler instanceof HandlerMethod) { final HandlerMethod handlerMethod = (HandlerMethod) handler; final Class<?> clazz = handlerMethod.getBeanType(); final Method method = handlerMethod.getMethod(); //Determine whether annotations are added to the object if (clazz.isAnnotationPresent(ResponseResult.class)) { //Set that the request return body needs to be wrapped and passed down. Judge it on the ResponseBodyAdvice interface request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class)); //Is there a comment on the method body } else if (method.isAnnotationPresent(ResponseResult.class)) { //Set that the request return body needs to be wrapped and passed down, and judge on the ResponseBodyAdvice interface request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class)); } } return true; } }
- Implement the responsebodyadvice < Object > interface to customize the json return parser, and judge whether to customize the return type according to the packaging interception flag
package cn.soboys.core.ret; import cn.soboys.core.utils.HttpContextUtil; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; 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; /** * @author kenx * @version 1.0 * @date 2021/6/17 16:47 * Global unified response return body processing */ @Slf4j @ControllerAdvice public class ResponseResultHandler implements ResponseBodyAdvice<Object> { public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN"; /** * @param methodParameter * @param aClass * @return If false is returned here, the current Advice business will not be executed * Whether the request contains a wrapper annotation mark and does not return directly. It is not necessary to rewrite the return body, */ @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { HttpServletRequest request = HttpContextUtil.getRequest(); //Determine whether the request has a packaging mark ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN); return responseResultAnn == null ? false : true; } /** * @param body * @param methodParameter * @param mediaType * @param aClass * @param serverHttpRequest * @param serverHttpResponse * @return Specific business methods for processing response */ @Override public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { if (body instanceof Result) { return body; } else if (body instanceof String) { return JSON.toJSONString(ResultResponse.success(body)); } else { return ResultResponse.success(body); } } }
Note that the return of string type here should be returned separately by json serialization, otherwise conversion exceptions will be reported
In this way, we can return any type in the controller without having to return Result every time, such as
package cn.soboys.mallapi.controller; import cn.soboys.core.ret.ResponseResult; import cn.soboys.core.ret.Result; import cn.soboys.core.ret.ResultResponse; import cn.soboys.mallapi.bean.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author kenx * @version 1.0 * @date 2021/7/2 20:28 */ @RestController //json is returned by default @RequestMapping("/user") @ResponseResult public class UserController { @GetMapping("/list") public User getUserInfo() { User u = new User(); u.setUserId("21"); u.setUsername("kenx"); u.setPassword("224r2"); return u; } @GetMapping("/test") public String test() { return "ok"; } @GetMapping("/test2") public Result test1(){ return ResultResponse.success(); } }
Here's another question? Under normal circumstances, if the return is successful, the json format is unified, but the return fails or is abnormal. How can the error json format be unified? Springboot has its own error format?
Please refer to my previous article, spring boot's elegant global exception handling
Scan code to pay attention to official account of apes and learn more about good things.