Exception handling components in SpringBoot

Summary of Java knowledge points: If you want to see it, you can enter it from here

Table of Contents

      • 1.8. Automatic exception handling
        • 1.8.1. Response rules
        • 1.8.2, error controller
        • 1.8.3. View resolver
        • 1.8.4, error attribute processing

1.8, automatic exception handling

Spring Boot provides automatic configuration for exception handling through the configuration class ErrorMvcAutoConfiguration, which injects the following four components into the container.

  • ErrorPageCustomizer: This component will forward the request to “/error” by default after an exception occurs in the system.

  • BasicErrorController: Handles the default “/error” request.

  • DefaultErrorViewResolver: The default error view resolver, which resolves exception information to the corresponding error view.

  • DefaultErrorAttributes: error attribute processing tool, which can get exception or error information from the request

1.8.1, Response rules

ErrorMvcAutoConfiguration injects a component called ErrorPageCustomizer into the container, which is mainly used to customize the response rules of error pages.

@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {<!-- -->
    return new ErrorPageCustomizer(this. serverProperties, dispatcherServletPath);
}

Register error page response rules through the registerErrorPages() method. When an exception occurs in the system, the ErrorPageCustomizer component will automatically take effect

static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {<!-- -->
//server properties
    private final ServerProperties properties;
    //Servlet path
    private final DispatcherServletPath dispatcherServletPath;

    protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {<!-- -->
        this.properties = properties;
        this. dispatcherServletPath = dispatcherServletPath;
    }

    @Override
    public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {<!-- -->
        //Forward the request to /errror(this.properties.getError().getPath())
        ErrorPage errorPage = new ErrorPage(
            this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
        // register error page
        errorPageRegistry. addErrorPages(errorPage);
    }

    @Override
    public int getOrder() {<!-- -->
        return 0;
    }
}

1.8.2, error controller

ErrorMvcAutoConfiguration also injects an error controller component BasicErrorController into the container

@Bean
@ConditionalOnMissingBean(value = ErrorController. class, search = SearchStrategy. CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
                       ObjectProvider<ErrorViewResolver> errorViewResolvers) {<!-- -->
    return new BasicErrorController(errorAttributes, this. serverProperties. getError(),
                                   errorViewResolvers.orderedStream().collect(Collectors.toList()));
}

It is a Controller, and Spring Boot performs unified error handling through BasicErrorController, which is mainly used to process requests whose path is /error

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {<!-- -->
    // error attribute
    private final ErrorProperties errorProperties;
    .....Construction method....
        
    //Used to handle the exception that occurred in the request of the browser client
    @RequestMapping(produces = MediaType. TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {<!-- -->
        // get error status code
        HttpStatus status = getStatus(request);
        //Encapsulate model data according to error information for page display
        Map<String, Object> model = Collections
            .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        //Set the error status code for the response object
        response.setStatus(status.value());
        //Call the resolveErrorView() method to generate a ModelAndView object using the view resolver
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        //Set the page corresponding to the error error
        return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    }

    //Used to handle errors in machine client requests (such as Android, IOS, Postman, etc.)
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {<!-- -->
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {<!-- -->
            return new ResponseEntity<>(status);
        }
        Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType. ALL));
        return new ResponseEntity<>(body, status);
    }
}

When using a browser to access an exception, it will enter the errorHtml() method in the BasicErrorController controller for processing. In the errorHtml() method, the resolveErrorView() method of the parent class (AbstractErrorController) will be called to obtain all ErrorViewResolver objects in the container. (error view resolvers, including DefaultErrorViewResolver), together to resolve exception information.

protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,Map<String, Object> model) {<!-- -->
    //Get all error view resolvers in the container to handle the exception information
    for (ErrorViewResolver resolver : this. errorViewResolvers) {<!-- -->
        //Call the resolveErrorView of the view resolver to resolve to the error view page
        ModelAndView modelAndView = resolver. resolveErrorView(request, status, model);
        if (modelAndView != null) {<!-- -->
            return modelAndView;
        }
    }
    return null;
}

1.8.3, view resolver

ErrorMvcAutoConfiguration injects a default error view resolver component DefaultErrorViewResolver into the container

@Bean
@ConditionalOnBean(DispatcherServlet. class)
@ConditionalOnMissingBean(ErrorViewResolver. class)
DefaultErrorViewResolver conventionErrorViewResolver() {<!-- -->
    return new DefaultErrorViewResolver(this. applicationContext, this. resourceProperties);
}

When the requesting client is a browser, Spring Boot will obtain all ErrorViewResolver objects in the container, and call their resolveErrorView() method to resolve the exception information.

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {<!-- -->
//series view
    private static final Map<Series, String> SERIES_VIEWS;
    static {<!-- -->
        Map<Series, String> views = new EnumMap<>(Series. class);
        views.put(Series.CLIENT_ERROR, "4xx");
        views. put(Series. SERVER_ERROR, "5xx");
        SERIES_VIEWS = Collections. unmodifiableMap(views);
    }
.........
//Resolve error view
    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {<!-- -->
         //Try to parse the error status code as the error page name
        ModelAndView modelAndView = resolve(String. valueOf(status. value()), model);
        if (modelAndView == null & amp; & amp; SERIES_VIEWS. containsKey(status. series())) {<!-- -->
            // Parse with 4xx or 5xx as the error page
            modelAndView = resolve(SERIES_VIEWS. get(status. series()), model);
        }
        return modelAndView;
    }

    private ModelAndView resolve(String viewName, Map<String, Object> model) {<!-- -->
        //Error template page, such as error/404
        String errorViewName = "error/" + viewName;
        //When the template engine can parse these template pages, use the template engine to parse
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,this.applicationContext);
        if (provider != null) {<!-- -->
             //If the template can be parsed to the template page, return the view specified by errorViewName
            return new ModelAndView(errorViewName, model);
        }
        //If the template engine can't resolve it, go to the static resource to find the page corresponding to errorViewName
        return resolveResource(errorViewName, model);
    }
//Find the page corresponding to errorViewName in the static resource file
    private ModelAndView resolveResource(String viewName, Map<String, Object> model) {<!-- -->
        //Loop through all static resource folders
        for (String location : this.resourceProperties.getStaticLocations()) {<!-- -->
            try {<!-- -->
                Resource resource = this.applicationContext.getResource(location);
                resource = resource.createRelative(viewName + ".html");
                //If the above error page exists under the static resource folder, return directly
                if (resource. exists()) {<!-- -->
                    return new ModelAndView(new HtmlResourceView(resource), model);
                }
            }
            catch (Exception ex) {<!-- -->
            }
        }
        return null;
    }
}

DefaultErrorViewResolver generates an error view error/status according to the error status code (such as 404, 500, 400, etc.), and then tries to parse it. From the templates directory under the classpath classpath, search for error/status.html, if the template engine can resolve to error /status view, the view and data will be encapsulated into ModelAndView to return and end. If not found, search for error/status.html from the static resource folder in turn, if the error page is found in the static folder, return and end the entire parsing flow, if still not found, process the default ” /error ” request, using Spring Boot’s default error page (Whitelabel Error Page).

1.8.4, error attribute processing

ErrorMvcAutoConfiguration injects a component default error attribute processing tool DefaultErrorAttributes into the container, which can get exception or error information from the request and encapsulate it as a Map object to return.

@Bean
@ConditionalOnMissingBean(value = ErrorAttributes. class, search = SearchStrategy. CURRENT)
public DefaultErrorAttributes errorAttributes() {<!-- -->
    return new DefaultErrorAttributes();
}
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {<!-- -->
    // get error attribute
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {<!-- -->
        Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
        if (Boolean. TRUE. equals(this. includeException)) {<!-- -->
            options = options. including(Include. EXCEPTION);
        }
        if (!options.isIncluded(Include.EXCEPTION)) {<!-- -->
            errorAttributes. remove("exception");
        }
        if (!options.isIncluded(Include.STACK_TRACE)) {<!-- -->
            errorAttributes. remove("trace");
        }
        if (!options.isIncluded(Include.MESSAGE) & amp; & amp; errorAttributes.get("message") != null) {<!-- -->
            errorAttributes. put("message", "");
        }
        if (!options.isIncluded(Include.BINDING_ERRORS)) {<!-- -->
            errorAttributes. remove("errors");
        }
        return errorAttributes;
    }
    @Override
    @Deprecated
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {<!-- -->
        Map<String, Object> errorAttributes = new LinkedHashMap<>();
        errorAttributes. put("timestamp", new Date());
        addStatus(errorAttributes, webRequest);
        addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        addPath(errorAttributes, webRequest);
        return errorAttributes;
    }

    //add error status code
    private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {<!-- -->
        Integer status = getAttribute(requestAttributes, RequestDispatcher. ERROR_STATUS_CODE);
        if (status == null) {<!-- -->
            errorAttributes. put("status", 999);
            errorAttributes. put("error", "None");
            return;
        }
        errorAttributes. put("status", status);
        try {<!-- -->
            errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
        }
        catch (Exception ex) {<!-- -->
            // Unable to get reason
            errorAttributes. put("error", "Http Status " + status);
        }
    }
    // add error details
    private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest,
                                 boolean includeStackTrace) {<!-- -->
        Throwable error = getError(webRequest);
        if (error != null) {<!-- -->
            while (error instanceof ServletException & amp; & amp; error. getCause() != null) {<!-- -->
                error = error. getCause();
            }
            errorAttributes. put("exception", error. getClass(). getName());
            if (includeStackTrace) {<!-- -->
                addStackTrace(errorAttributes, error);
            }
        }
        addErrorMessage(errorAttributes, webRequest, error);
    }

    // add error/exception message
    private void addErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {<!-- -->
        BindingResult result = extractBindingResult(error);
        if (result == null) {<!-- -->
            addExceptionErrorMessage(errorAttributes, webRequest, error);
        }
        else {<!-- -->
            addBindingResultErrorMessage(errorAttributes, result);
        }
    }

    //Add the exception object that caused the request processing to fail
    private void addExceptionErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {<!-- -->
        Object message = getAttribute(webRequest, RequestDispatcher. ERROR_MESSAGE);
        if (StringUtils.isEmpty(message) & amp; & amp; error != null) {<!-- -->
            message = error. getMessage();
        }
        if (StringUtils. isEmpty(message)) {<!-- -->
            message = "No message available";
        }
        errorAttributes. put("message", message);
    }

    //Add binding result error message
    private void addBindingResultErrorMessage(Map<String, Object> errorAttributes, BindingResult result) {<!-- -->
        errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'."
                             + "Error count: " + result. getErrorCount());
        errorAttributes. put("errors", result. getAllErrors());
    }

    //error/exception stack information
    private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {<!-- -->
        StringWriter stackTrace = new StringWriter();
        error. printStackTrace(new PrintWriter(stackTrace));
        stackTrace. flush();
        errorAttributes. put("trace", stackTrace. toString());
    }
    //The requested URL path when an error/exception is thrown
    private void addPath(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {<!-- -->
        String path = getAttribute(requestAttributes, RequestDispatcher.ERROR_REQUEST_URI);
        if (path != null) {<!-- -->
            errorAttributes. put("path", path);
        }
    }

    // error message
    @Override
    public Throwable getError(WebRequest webRequest) {<!-- -->
        Throwable exception = getAttribute(webRequest, ERROR_ATTRIBUTE);
        return (exception != null) ? exception : getAttribute(webRequest,RequestDispatcher.ERROR_EXCEPTION);
    }
}

When BasicErrorController handles errors, it will call the getErrorAttributes() method of DefaultErrorAttributes to obtain error or exception information, encapsulate it into model data (Map object), and return it to the page or JSON data.

  • timestamp: timestamp;
  • status: error status code
  • error: error message
  • exception: the exception object that caused the request processing to fail
  • message: error/exception message
  • trace: error/exception stack information
  • path: URL path requested when an error/exception is thrown