思路:自定义一个注解,拦截请求查看该请求处理的controller是否有该注解,有则包装response,否则直接返回。
- 定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Documented;
/**
* 用来标记在controller层类or方法上
* 表示请求该URI的response需要被转换
* * @author moguchen
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface ResponseConversion {
}
- 定义通用response返回
/**
* 通用返回体
*
* @author moguchen
*/
public class CommonResponse {
/**
* 返回码
*/
private Integer code;
/**
* 是否请求处理成功
*/
private Boolean success;
/**
* 返回信息描述
*/
private String msg;
/**
* 返回数据
*/
private Object data;
public void setCode(Integer code) {
this.code = code;
}
public void setSuccess(Boolean success) {
this.success = success;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void setData(Object data) {
this.data = data;
}
public Integer getCode() {
return code;
}
public Boolean getSuccess() {
return success;
}
public String getMsg() {
return msg;
}
public Object getData() {
return data;
}
/**
* 成功的返回
*
* @param data data
* @return response
*/
public static CommonResponse success(Object data) {
CommonResponse response = new CommonResponse();
response.setCode(HttpStatus.OK.value());
response.setMsg(HttpStatus.OK.getReasonPhrase());
response.setSuccess(true);
response.setData(data);
return response;
}
/**
* 失败的返回
*
* @param e error
* @return response
*/
public static CommonResponse failure(CommonException e) {
CommonResponse response = new CommonResponse();
response.setCode(e.getErrorCode());
response.setMsg(e.getErrorMessage());
response.setSuccess(false);
return response;
}
}
- 请求拦截器处理,此处的常量为一个字符串标记
/**
* response需要被统一转换包装的标记
*/
public static final String RESPONSE_NEED_CONVERSION_MARK = "RESPONSE_NEED_CONVERSION_MARK";
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import static com.xxx.project.common.constant.ArmsConstant.RESPONSE_NEED_CONVERSION_MARK;
/**
* request拦截器
* 根据注解看此次请求的response是否需要被转换包装
*
* @author moguchen
*/
@Slf4j
@Component
public class ArmsRequestInterceptor implements HandlerInterceptor {
@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();
// 如果本次请求的controller的类上或者方法上带有注解 那么放置标记
if (clazz.isAnnotationPresent(ResponseConversion.class) || method.isAnnotationPresent(ResponseConversion.class)) {
request.setAttribute(RESPONSE_NEED_CONVERSION_MARK, true);
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
- 响应处理,这里采用Spring提供的
ResponseBodyAdvice<Object>
来处理,supports
的返回表示此次请求是否要进行beforeBodyWrite
方法的处理
import org.springframework.core.MethodParameter;
import org.springframework.core.io.Resource;
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.util.ObjectUtils;
import org.springframework.web.bind.annotation.RestControllerAdvice;
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;
import static com.xxx.project.common.constant.ArmsConstant.RESPONSE_NEED_CONVERSION_MARK;
/**
* response转换类
* * @author moguchen
*/
@RestControllerAdvice
public class ArmsResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter,
Class<? extends HttpMessageConverter<?>> aClass) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return false;
}
// 看请求有没有带上mark标记 没有带response不需要转换包装
HttpServletRequest request = requestAttributes.getRequest();
return !ObjectUtils.isEmpty(request.getAttribute(RESPONSE_NEED_CONVERSION_MARK));
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
// 本身就为转换的response或者文件上传下载 不需要转换 直接返回
if (o instanceof CommonResponse || o instanceof Resource) {
return o;
}
return CommonResponse.success(o);
}
}
- 完成之后只需要在controller层加上该注解即可,然后只需要正常返回需要包装的对象即可,可以省去冗余代码。
/**
* 测试用controller
* * @author moguchen
*/
@RestController
@ResponseConversion
@RequestMapping("/demo")
public class DemoController {
private final DemoService demoService;
public DemoController(DemoService demoService) {
this.demoService = demoService;
}
@GetMapping("/{name}")
public DemoResponse getDemo(@PathVariable String name) {
return demoService.getDemo(name);
}
}
- 异常的处理,异常不会走到
ResponseBodyAdvice<Object>
的方法中来,而会被直接抛出,所以我们需要自定义全局的异常处理,这里贴出几个常用的异常
/**
* 全局异常处理
*
* @author moguchen
*/
@Slf4j
@ResponseBody
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = CommonException.class)
@ResponseStatus(code = HttpStatus.OK)
public CommonResponse baseExceptionHandler(CommonException e) {
log.error("自定义异常捕获:" + e.getErrorMessage(), e);
return CommonResponse.failure(e);
}
@ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})
@ResponseStatus(code = HttpStatus.OK)
public CommonResponse baseExceptionHandler(Exception e) {
BindingResult result;
if (e instanceof MethodArgumentNotValidException) {
result = ((MethodArgumentNotValidException) e).getBindingResult();
} else {
result = ((BindException) e).getBindingResult();
}
List<String> res = new ArrayList<>();
if (result.hasErrors()) {
List<FieldError> fieldErrors = result.getFieldErrors();
for (FieldError error : fieldErrors) {
res.add(error.getDefaultMessage());
}
} else {
res = Collections.emptyList();
}
String errorMsg = String.join(",", res);
log.error("参数异常捕获:" + errorMsg, e);
return CommonResponse.failure(new CommonException(HttpStatus.BAD_REQUEST.value(), errorMsg));
}
@ExceptionHandler(value = NullPointerException.class)
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public CommonResponse exceptionHandler(NullPointerException e) {
log.error("空指针异常捕获:", e);
return CommonResponse.failure(new CommonException(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()));
}
@ExceptionHandler(value = Exception.class)
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public CommonResponse exceptionHandler(Exception e) {
log.error("未知异常捕获:", e);
return CommonResponse.failure(new CommonException(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()));
}
}
然后由于我们在beforeBodyWrite
方法中判断了是CommonResponse
就会直接返回,所以这里response不会被重复包装。
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
// 本身就为转换的response或者文件上传下载 不需要转换 直接返回
if (o instanceof CommonResponse || o instanceof Resource) {
return o;
}
return CommonResponse.success(o);
}
- service实现类test处理
/**
* @author moguchen
*/
@Service
public class DemoServiceImpl implements DemoService {
@Override
public DemoResponse getDemo(String name) {
if ("alice".equals(name)) {
throw new CommonException(UserExceptionEnum.USER_NOT_EXIST_ERROR);
}
DemoResponse response = new DemoResponse();
response.setName(name);
response.setAddress("address");
response.setSchool("school");
return response;
}
}
- 测试
正常的response
异常抛出的response