Preface
I'm sure you'll often encounter this page when you first start experiencing Springboot, which is the default return page to a page that doesn't exist.
If it is a request from another client, such as an interface test tool, the JSON data is returned by default.
{ "timestamp":"2019-01-06 22:26:16", "status":404, "error":"Not Found", "message":"No message available", "path":"/asdad" }
Clearly, SpringBoot handles responses differently based on the HTTP request header information. HTTP-related knowledge can be found here.
1. SpringBoot exception handling mechanism
Following the SpringBoot source can analyze the default error handling mechanism.
// org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration // Bind some error messages to 1 @Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes( this.serverProperties.getError().isIncludeException()); } // Default processing/error count as 2 @Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers); } // Error handling pages are marked as 3 @Bean public ErrorPageCustomizer errorPageCustomizer() { return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath); } @Configuration static class DefaultErrorViewResolverConfiguration { private final ApplicationContext applicationContext; private final ResourceProperties resourceProperties; DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, ResourceProperties resourceProperties) { this.applicationContext = applicationContext; this.resourceProperties = resourceProperties; } // Deciding which error page to go to is marked 4 @Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean public DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties); } }
With the above comments, the four methods in the code above are the main part of the Springboot implementation that returns the error page by default.
1.1. errorAttributes
ErorAttributes are literally translated into error attributes, which is exactly the way to track source code directly.
The code is located at:
// org.springframework.boot.web.servlet.error.DefaultErrorAttributes
This class shares a lot of error information for error situations, such as.
errorAttributes.put("timestamp", new Date()); errorAttributes.put("status", status); errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase()); errorAttributes.put("errors", result.getAllErrors()); errorAttributes.put("exception", error.getClass().getName()); errorAttributes.put("message", error.getMessage()); errorAttributes.put("trace", stackTrace.toString()); errorAttributes.put("path", path);
This information is returned as shared information, so when we use the template engine, it is as easy to take out as other parameters.
1.2. basicErrorControll
Tracking the source content of the BasicErrorController directly reveals the following section of code.
// org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController @Controller // Define the request path, if there is no error.path path, then the path is/error @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController { // If supported format text/html @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); // Get the value to return Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes( request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); // Parse the error view information, which is the logic in 1.4 below ModelAndView modelAndView = resolveErrorView(request, response, status, model); // Return to the view and use the default error view template if no page template exists return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { // If you accept HTTP requests in all formats Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); // Response to HttpEntity return new ResponseEntity<>(body, status); } }
As you can see from this, basicErrorControll is used to create a controller class for request returns and to return corresponding information in different formats that are acceptable for HTTP requests, so there are differences in the results returned when testing with browser and interface testing tools.
1.3. ererrorPageCustomizer
Look directly at the new ErrorPageCustomizer (this.serverProperties, this.dispatcher ServletPath) in the method;
//org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.ErrorPageCustomizer /** * {@link WebServerFactoryCustomizer} that configures the server's error pages. */ private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { private final ServerProperties properties; private final DispatcherServletPath dispatcherServletPath; protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) { this.properties = properties; this.dispatcherServletPath = dispatcherServletPath; } // Registration Error Page // this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()) @Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { //getPath() gets the following address, and if there is no custom error.path attribute, go/error location //@Value("${error.path:/error}") //private String path = "/error"; ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath .getRelativePath(this.properties.getError().getPath())); errorPageRegistry.addErrorPages(errorPage); } @Override public int getOrder() { return 0; } }
As you can see from the above, when an error is encountered, the request is forwarded to/error if there is no custom error.path attribute.
1.4. conventionErrorViewResolver
From the code above, take a step-by-step look at the default error handling implementation of SpringBoot and the conventionErrorViewResolver method.Below is some code for the DefaultErrorViewResolver class, comment resolution.
// org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver // Initialization parameter, key is the first bit of HTTP status code. 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); } @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { // Use the HTTP full status code to check if there are any pages to match ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { // Create a view object using parameters in the first bitmatch initialization of the HTTP status code modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { // Split Error View Path/eroor/[viewname] String errorViewName = "error/" + viewName; // Attempting to create a view object using a template engine TemplateAvailabilityProvider provider = this.templateAvailabilityProviders .getProvider(errorViewName, this.applicationContext); if (provider != null) { return new ModelAndView(errorViewName, model); } // No template engine, use static resource folders to resolve views return resolveResource(errorViewName, model); } private ModelAndView resolveResource(String viewName, Map<String, Object> model) { // Traverse through the static resource folder to check for views for (String location : this.resourceProperties.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return new ModelAndView(new HtmlResourceView(resource), model); } } catch (Exception ex) { } } return null; }
Thymeleaf parses error pages.
//org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider public class ThymeleafTemplateAvailabilityProvider implements TemplateAvailabilityProvider { @Override public boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader, ResourceLoader resourceLoader) { if (ClassUtils.isPresent("org.thymeleaf.spring5.SpringTemplateEngine", classLoader)) { String prefix = environment.getProperty("spring.thymeleaf.prefix", ThymeleafProperties.DEFAULT_PREFIX); String suffix = environment.getProperty("spring.thymeleaf.suffix", ThymeleafProperties.DEFAULT_SUFFIX); return resourceLoader.getResource(prefix + view + suffix).exists(); } return false; } }
Thus, we can know that the error page first checks the / error/HTTP status code file under the template engine folder, if it does not exist, then checks the / error/4xx or / error/5xx files under the template engine, and if it does not exist, checks the corresponding files under the static resource folder.
2. Customize Exception Page
From the source analysis of the SpringBoot error mechanism above, you know that when an error occurs, SpringBoot will first return to the / error/HTTP status code file under the Template Engine folder. If it does not exist, check the / error/4xx or / error/5xx files under the De-Template Engine. If it does not exist, check the corresponding files under the static resource folder.It also shares some error information when returned, which can be used directly in the template engine.
errorAttributes.put("timestamp", new Date()); errorAttributes.put("status", status); errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase()); errorAttributes.put("errors", result.getAllErrors()); errorAttributes.put("exception", error.getClass().getName()); errorAttributes.put("message", error.getMessage()); errorAttributes.put("trace", stackTrace.toString()); errorAttributes.put("path", path);
Therefore, you need to customize the error page by preventing 4xx or 5xx files in the error folder under the template folder.
<!doctype html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>[[${status}]]</title> <!-- Bootstrap core CSS --> <link href="/webjars/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"> </head> <body > <div class="m-5" > <p>Error code:[[${status}]]</p> <p >Information:[[${message}]]</p> <p >Time:[[${#dates.format(timestamp,'yyyy-MM-dd hh:mm:ss ')}]]</p> <p >Request path:[[${path}]]</p> </div> </body> </html>
No path exists for random access.
3. Custom error JSON
Based on the analysis of SpringBoot error handling principle above, it is known that the JSON information returned is converted from a map object, so as long as the value in the map can be customized, you can customize the JSON format of the error information.You can simply override the getErrorAttributes method of the DefaultErrorAttributes class.
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; import org.springframework.stereotype.Component; import org.springframework.web.context.request.WebRequest; import java.util.HashMap; import java.util.Map; /** * <p> * Custom error message JSON value * * @Author niujinpeng * @Date 2019/1/7 15:21 */ @Component public class ErrorAttributesCustom extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace); String code = map.get("status").toString(); String message = map.get("error").toString(); HashMap<String, Object> hashMap = new HashMap<>(); hashMap.put("code", code); hashMap.put("message", message); return hashMap; } }
Request tests using postman.
4. Unified exception handling
Unified exception handling can be achieved using @ControllerAdvice with the @ExceptionHandler annotation, and the @ExceptionHandler annotated class automatically applies to each method annotated by @RequestMapping.Throw layer by layer when an exception occurs in the program
import lombok.extern.slf4j.Slf4j; import net.codingme.boot.domain.Response; import net.codingme.boot.enums.ResponseEnum; import net.codingme.boot.utils.ResponseUtill; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; /** * <p> * Unified exception handling * * @Author niujinpeng * @Date 2019/1/7 14:26 */ @Slf4j @ControllerAdvice public class ExceptionHandle { @ResponseBody @ExceptionHandler(Exception.class) public Response handleException(Exception e) { log.info("abnormal {}", e); if (e instanceof BaseException) { BaseException exception = (BaseException) e; String code = exception.getCode(); String message = exception.getMessage(); return ResponseUtill.error(code, message); } return ResponseUtill.error(ResponseEnum.UNKNOW_ERROR); } }
The request exception page was responded as follows.
{ "code": "-1", "data": [], "message": "unknown error" }
The article code has been uploaded to GitHub Spring Boot Web Development-Error Mechanism.
<Finish>
This article originates from a personal blog: https://www.codingme.net Please indicate the source for reprinting.