一、背景
前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。通常,后端会以统一风格格式返回给前端。如:
{
code:integer, #返回状态码
message:string, #返回信息描述
data:object #返回值
}
二、实现思路
1、定义一个注解@ResponseResult,表示这个接口返回的值需要包装一下
2、拦截请求,判断此请求是否需要被@ResponseResult注解
3、核心步骤就是实现接口ResponseBodyAdvice和@ControllerAdvice,判断是否需要包装返回值,如果需要,就把Controller接口的返回值进行重写。
三、代码实现
1、编写注解
用来标记方法的返回值,是否需要包装:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseResult {
}
2、编写通用返回格式
用来统一返回的格式:
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Result {
private Integer code;
private String message;
private Object data;
public static Result success(){
return new Result(0,"success",null);
}
public static Result success(Object data){
return new Result(0,"success",data);
}
public static Result failure(Integer code,String message){
return new Result(code,message,null);
}
}
3、编写拦截器
拦截请求,是否此请求返回的值需要包装,其实就是运行的时候,解析@ResponseResult注解:
import com.tang.rest.annotation.ResponseResult;
import lombok.extern.slf4j.Slf4j;
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;
public class ResponseResultInterceptor implements HandlerInterceptor {
public static String RESP_RESULT_ANNO="RESP-RESULT-ANNO";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(handler instanceof HandlerMethod){
HandlerMethod handlerMethod=(HandlerMethod)handler;
Class<?> clazz=handlerMethod.getBeanType();
Method method=handlerMethod.getMethod();
if(clazz.isAnnotationPresent(ResponseResult.class)){
// 如果在类上使用了ResponseResult注解
request.setAttribute(RESP_RESULT_ANNO,clazz.getAnnotation(ResponseResult.class));
}else if(method.isAnnotationPresent(ResponseResult.class)){
// 如果在方法上使用了ResponseResult注解
request.setAttribute(RESP_RESULT_ANNO,method.getAnnotation(ResponseResult.class));
}
}
return true;
}
}
然后通过配置类注册该拦截器:
import com.tang.rest.interceptor.ResponseResultInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(new ResponseResultInterceptor());
}
}
4、重写返回体
import com.tang.rest.annotation.ResponseResult;
import com.tang.rest.domain.Result;
import com.tang.rest.interceptor.ResponseResultInterceptor;
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;
@Slf4j
@ControllerAdvice
public class CommonController implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request=attributes.getRequest();
ResponseResult annotation=(ResponseResult)request.getAttribute(ResponseResultInterceptor.RESP_RESULT_ANNO);
if(null!=annotation){
return true;
}
return false;
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
return Result.success(o);
}
}
上面代码就是判断是否需要返回值包装,如果需要就直接包装。这里我们只处理了正常成功的包装,如果方法体报异常怎么办?处理异常也比较简单,代码如下:
import com.tang.rest.domain.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class ExceptionController {
@ExceptionHandler(value = Exception.class)
public Result errorHandler(Exception e) {
log.error("Exception:", e);
if (e instanceof NullPointerException) {
return Result.failure(-1, "Internal Exception : Null Pointer Exception ");
}
return Result.failure(-1, "Internal Exception : " + e.getMessage());
}
}
5、编写Controller
import com.tang.rest.annotation.ResponseResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ResponseResult
@RestController
@RequestMapping(value = "/test")
public class TestController {
@GetMapping("/list")
public List<String> getNames(){
List<String> r=new ArrayList<>();
r.add("hello");
r.add("world");
return r;
}
@GetMapping("/map")
public Map<String,Object> getMap(){
Map<String,Object> m=new HashMap<>();
m.put("hello","world");
m.put("number",123);
return m;
}
@GetMapping("/excep")
public List<String> mockExcept(){
throw new RuntimeException("Error mock!");
}
}
在控制器类上或者方法体上加上@ResponseResult注解,这样就ok了。