在springboot中,能夠用統一@ControllerAdvice + @ExceptionHandle
,可是沒法捕獲404異常。若是用瀏覽器直接訪問,能夠看到出現如下內容:
能夠看到在404以後,因爲沒有被@ControllerAdvice
捕獲,springboot默認會返回/error接口的內容,經過搜索,咱們能夠在BasicErrorController
找到對應的代碼。java
@Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController {
BasicErrorController
是默認的異常處理嘞,類裏有對error的處理:web
@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, isIncludeStackTrace(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 ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new ResponseEntity<>(body, status); }
那咱們是否是能夠自定義/error
處理類呢?
在ErrorMvcAutoConfiguration
裏能夠看到BasicErrorController
Bean的生成條件:spring
@Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers); }
因此只要咱們自定義的異常處理類實現了ErrorController
便可。瀏覽器
@Controller public class NotFoundErrorController implements ErrorController { @Override public String getErrorPath() { return "/error"; } @RequestMapping(value = {"/error"}) @ResponseBody public ResponseEntity<String> error(HttpServletRequest request) { return new ResponseEntity("404 Not Found", HttpStatus.OK); } }
若是咱們想根據不一樣的URI作不一樣的處理,調用request.getRequestURI()
卻發現方法返回的是/error
,那麼要怎麼取到原始的地址,以及springboot是怎麼轉到/error
的?
經過debug,跟蹤到StandardHostValve
類的invoke
方法裏springboot
// 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); } } }
繼續跟進status方法:app
/** * Handle the HTTP status code (and corresponding message) generated * while processing the specified Request to produce the specified * Response. Any exceptions that occur during generation of the error * report are logged and swallowed. * * @param request The request being processed * @param response The response being generated */ 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; } ErrorPage errorPage = context.findErrorPage(statusCode); if (errorPage == null) { // Look for a default error page 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); } } } } /** * Handle an HTTP status code or Java exception by forwarding control * to the location included in the specified errorPage object. It is * assumed that the caller has already recorded any request attributes * that are to be forwarded to this page. Return <code>true</code> if * we successfully utilized the specified error page location, or * <code>false</code> if the default error report should be rendered. * * @param request The request being processed * @param response The response being generated * @param errorPage The errorPage directive we are obeying */ 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()); } 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; } }
能夠看到最後是經過forward
作跳轉,而在跳轉以前,經過request.setAttribute
將原始請求的屬性設置到request裏。
因此若是ErrorController
裏要獲取原始的請求信息,能夠用request.getAttribute
。
總體代碼以下:ide
@Controller @RequestMapping("${server.error.path:${error.path:/error}}") public abstract class UncaughtErrorControllor implements ErrorController { @Value("${server.error.path:${error.path:/error}}") protected String errorPath; @Override public String getErrorPath() { return errorPath; } /** * @param request * @param response * @return java.lang.Object */ @RequestMapping(value = "") @ResponseBody public Object error(HttpServletRequest request, HttpServletResponse response) { return handleError(request, response); } /** * @param request * @param response * @return java.lang.Object */ protected abstract Object handleError(HttpServletRequest request, HttpServletResponse response); } public class DefaultUncaughtErrorControllor extends UncaughtErrorControllor { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override protected Object handleError(HttpServletRequest request, HttpServletResponse response) { HttpStatus statusCode = getHttpStatusCode(request); String message = buildErrorMessage(request, statusCode); log(request, message, statusCode); return buildResult(request, message, statusCode); } /** * @param request * @return org.springframework.http.HttpStatus */ protected HttpStatus getHttpStatusCode(HttpServletRequest request) { try { Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } return HttpStatus.valueOf(statusCode); } catch (Exception e) { return HttpStatus.INTERNAL_SERVER_ERROR; } } /** * @param request * @param statusCode * @return java.lang.String */ protected String buildErrorMessage(HttpServletRequest request, HttpStatus statusCode) { String uri = (String) request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI); return uri + (is404(request, statusCode) ? " Not Found" : " error"); } /** * @param request * @param statusCode * @return boolean */ protected boolean is404(HttpServletRequest reques, HttpStatus statusCode) { return statusCode.equals(HttpStatus.NOT_FOUND); } /** * @param request * @param message * @param statusCode */ protected void log(HttpServletRequest request, String message, HttpStatus statusCode) { if (statusCode.is5xxServerError()) { logger.error(message); } else { logger.info(message); } } /** * @param request * @param message * @param statusCode * @return java.lang.Object */ protected Object buildResult(HttpServletRequest request, String message, HttpStatus statusCode) { return new ResponseEntity(ResponseResult.fail(message), statusCode); } } @Configuration @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) public class WebConfiguration { @Value("${server.error.path:${error.path:/error}}") private String errorPath; /** * @return com.xxx.tscm.web.converter.UncaughtErrorControllor */ @Bean @ConditionalOnMissingBean(UncaughtErrorControllor.class) public UncaughtErrorControllor getUncaughtErrorControllor() { return new DefaultUncaughtErrorControllor(); } }
須要注意的事,StandardHostValve
對ERROR的處理,是在全部filter(好比spring的OncePerRequestFilter
)執行完以後,也就是:即便在自定義error處理方法裏,將返回的HTTP Status改成200,可是filter裏獲取到的仍是404。ui