赞
踩
spring boot 统一响应三步曲:
@Data public class ResponseResult<T> implements Serializable { private static final String SUC = "1"; private static final String FAIL = "0"; private String code; private String msg; private T data; public ResponseResult() { } public ResponseResult(String code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } @JsonIgnore public T getCheckedData() { if (!this.code.equals(SUC)) { throw new RuntimeException("调用异常"); } return data; } public static <T> ResponseResult<T> success(T data) { return new ResponseResult<>(SUC, null, data); } public static <T> ResponseResult<T> fail(String msg) { return new ResponseResult<>(FAIL, msg, null); } } /** * 响应自定义格式 * 而不是默认数据格式 R * @Date: 2024/5/16 14:47 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RawResponse { }
自定义 ResponseBodyAdvice
@Component @ControllerAdvice public class ResponseBodyWriteAdvice implements ResponseBodyAdvice<Object> { private ObjectMapper objectMapper; public ResponseBodyWriteAdvice(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (returnType.hasMethodAnnotation(RawResponse.class)) { //有些接口需要返回自定义格式 return body; } else if (body instanceof ResponseResult) { return body; } else if (body instanceof String) { // 将 Content-Type 设为 application/json,返回类型是String时,默认 Content-Type = text/plain ((ServletServerHttpResponse) response).getServletResponse().setCharacterEncoding(StandardCharsets.UTF_8.name()); HttpHeaders headers = response.getHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); try { return objectMapper.writeValueAsString(ResponseResult.success(body)); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } return ResponseResult.success(body); } }
处理 spring mvc 响应中文乱码问题
@Configuration public class FastJsonHttpMessageConverterConfig implements WebMvcConfigurer { @Bean public HttpMessageConverters messageConverters() { //1.需要定义一个convert转换消息的对象; MappingJackson2HttpMessageConverter fastJsonHttpMessageConverter = new MappingJackson2HttpMessageConverter(); //3处理中文乱码问题 List<MediaType> fastMediaTypes = new ArrayList<>(); fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); //4.在convert中添加配置信息. fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes); return new HttpMessageConverters(fastJsonHttpMessageConverter); } @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { //解决@RawResponse 返回 string 类型,且 content-type 为 text/plain 时中文乱码问题 converters.add(0,new StringHttpMessageConverter(StandardCharsets.UTF_8)); } }
@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseResult<String> exceptionHandler(Exception e) { log.info("internal error: ", e); return ResponseResult.fail(e.getMessage()); } @ExceptionHandler({AccessDeniedException.class}) @ResponseStatus(HttpStatus.FORBIDDEN) public ResponseResult<String> handleAccessDeniedException(AccessDeniedException e) { log.info("access error: ", e); return ResponseResult.fail(e.getMessage()); } }
因为我们统一了响应结构, 所以在响应404时,包装了一层
{
"code": "1",
"msg": null,
"data": {
"timestamp": 1723535533933,
"status": 404,
"error": "Not Found",
"path": "/u"
}
}
那怎么去掉里面的结构呢, 自定义实现ErrorController
@RestController @RequestMapping("${server.error.path:/error}") public class MBasicErrorController extends AbstractErrorController { private ServerProperties serverProperties; private ErrorProperties errorProperties; /** * Create a new {@link BasicErrorController} instance. * @param errorAttributes the error attributes * @param errorProperties configuration properties * @param errorViewResolvers error view resolvers */ public MBasicErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties, List<ErrorViewResolver> errorViewResolvers) { super(errorAttributes, errorViewResolvers); Assert.notNull(serverProperties, "ErrorProperties must not be null"); this.errorProperties = serverProperties.getError(); } @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } @RequestMapping public ResponseResult<String> error(HttpServletRequest request) { HttpStatus status = getStatus(request); Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); return ResponseResult.fail("404_资源不存在"); } protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, MediaType mediaType) { ErrorAttributeOptions options = ErrorAttributeOptions.defaults(); if (this.errorProperties.isIncludeException()) { options = options.including(ErrorAttributeOptions.Include.EXCEPTION); } if (isIncludeStackTrace(request, mediaType)) { options = options.including(ErrorAttributeOptions.Include.STACK_TRACE); } if (isIncludeMessage(request, mediaType)) { options = options.including(ErrorAttributeOptions.Include.MESSAGE); } if (isIncludeBindingErrors(request, mediaType)) { options = options.including(ErrorAttributeOptions.Include.BINDING_ERRORS); } return options; } /** * Determine if the stacktrace attribute should be included. * @param request the source request * @param produces the media type produced (or {@code MediaType.ALL}) * @return if the stacktrace attribute should be included */ protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) { switch (getErrorProperties().getIncludeStacktrace()) { case ALWAYS: return true; case ON_PARAM: return getTraceParameter(request); default: return false; } } /** * Determine if the message attribute should be included. * @param request the source request * @param produces the media type produced (or {@code MediaType.ALL}) * @return if the message attribute should be included */ protected boolean isIncludeMessage(HttpServletRequest request, MediaType produces) { switch (getErrorProperties().getIncludeMessage()) { case ALWAYS: return true; case ON_PARAM: return getMessageParameter(request); default: return false; } } /** * Determine if the errors attribute should be included. * @param request the source request * @param produces the media type produced (or {@code MediaType.ALL}) * @return if the errors attribute should be included */ protected boolean isIncludeBindingErrors(HttpServletRequest request, MediaType produces) { switch (getErrorProperties().getIncludeBindingErrors()) { case ALWAYS: return true; case ON_PARAM: return getErrorsParameter(request); default: return false; } } /** * Provide access to the error properties. * @return the error properties */ protected ErrorProperties getErrorProperties() { return this.errorProperties; } }
结果返回就变成:
{
"code": "0",
"msg": "404_资源不存在",
"data": null
}
spring mvc是如何定位到 ErrorController的?
比如请求一个不存在的资源 /u
,其实它是经过两次请求
/u
, 发现不存在, 设置reponse 响应码为404/error
地址,然后request.forward 到 /error 指定的 Controller第一次请求到 ResourceHttpRequestHandler.handleRequest
方法:
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// For very general mappings (e.g. "/") we need to check 404 first
Resource resource = getResource(request);
if (resource == null) {
logger.debug("Resource not found");
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// omit...
}
然后又返回到 tomcat 容器中处理, 即 StandardHostValve.invoke
:
// Look for (and render if found) an application level error page if (response.isErrorReportRequired()) { // If an error has occurred that prevents further I/O, don't waste time // producing an error report that will never be read AtomicBoolean result = new AtomicBoolean(false); response.getCoyoteResponse().action(ActionCode.IS_IO_ALLOWED, result); if (result.get()) { if (t != null) { throwable(request, response, t); } else { status(request, response); } } } private void status(Request request, Response response) { int statusCode = response.getStatus(); // Handle a custom error page for this status code Context context = request.getContext(); if (context == null) { return; } /* * Only look for error pages when isError() is set. isError() is set when response.sendError() is invoked. This * allows custom error pages without relying on default from web.xml. */ if (!response.isError()) { return; } //根据响应码查询 配置的 error 页面 ErrorPage errorPage = context.findErrorPage(statusCode); if (errorPage == null) { // Look for a default error page //如果没有找到,就取第一个 // 默认配置的一个为 /error errorPage = context.findErrorPage(0); } if (errorPage != null && response.isErrorReportRequired()) { response.setAppCommitted(false); request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, Integer.valueOf(statusCode)); String message = response.getMessage(); if (message == null) { message = ""; } request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message); request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, errorPage.getLocation()); request.setAttribute(Globals.DISPATCHER_TYPE_ATTR, DispatcherType.ERROR); Wrapper wrapper = request.getWrapper(); if (wrapper != null) { request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, wrapper.getName()); } request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI()); if (custom(request, response, errorPage)) { response.setErrorReported(); try { response.finishResponse(); } catch (ClientAbortException e) { // Ignore } catch (IOException e) { container.getLogger().warn("Exception Processing " + errorPage, e); } } } } private boolean custom(Request request, Response response, ErrorPage errorPage) { if (container.getLogger().isDebugEnabled()) { container.getLogger().debug("Processing " + errorPage); } try { // Forward control to the specified location ServletContext servletContext = request.getContext().getServletContext(); RequestDispatcher rd = servletContext.getRequestDispatcher(errorPage.getLocation()); if (rd == null) { container.getLogger() .error(sm.getString("standardHostValue.customStatusFailed", errorPage.getLocation())); return false; } if (response.isCommitted()) { // Response is committed - including the error page is the // best we can do rd.include(request.getRequest(), response.getResponse()); // Ensure the combined incomplete response and error page is // written to the client try { response.flushBuffer(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } // Now close immediately as an additional signal to the client // that something went wrong response.getCoyoteResponse().action(ActionCode.CLOSE_NOW, request.getAttribute(RequestDispatcher.ERROR_EXCEPTION)); } else { // Reset the response (keeping the real error code and message) response.resetBuffer(true); response.setContentLength(-1); rd.forward(request.getRequest(), response.getResponse()); // If we forward, the response is suspended again response.setSuspended(false); } // Indicate that we have successfully processed this custom page return true; } catch (Throwable t) { ExceptionUtils.handleThrowable(t); // Report our failure to process this custom page container.getLogger().error("Exception Processing " + errorPage, t); return false; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。