1. Abnormal source
To handle an exception that occurs in a program, you first need to know where the exception comes from?
1. The front-end wrong request path will cause 4xx errors in the program, the most common is 404. By default, when this error occurs in the spring boot request path, the pc response page is as follows
If it is mobile terminal (mobile terminal), it will respond to json format data, as follows
2. Spring boot exception handling principle
Why do we ask for the wrong path, and boot will return us an error page or json format data? What is the principle?
After the Springboot project is started, execute the main method of the startup class annotated with @ SpringBootApplication, and load it through @ EnableAutoConfiguration
All configuration classes in META-INF/spring.factories under springbootAutoConfiguration.jar package (after these configuration classes are loaded, the components in each configuration class will be injected into the container and then used) one of the autoconfiguration classes
ErrorMvcAutoConfiguration, through the code, you can see that the following four components are used
DefaultErrorAttributes,BasicErrorController,errorPageCustomizer,DefaultErrorViewResolver
The other three are basically similar. When an error such as 4xx or 5xx occurs, the errorPageCustomizer will take effect. this.properties.getError().getPath()) and come to the / error request. Core code
//errorPageCustomizer @Value("${error.path:/error}") private String path = "/error";
The / error request is processed by the BasicErrorController, which is a Controller. There are two processing methods, one is HTML,
One is JSON format. Where the visitor's information can be obtained from getErrorAttributes. DefaultErrorAttributes is the implementation class of ErrorAttributes.
critical code
@RequestMapping(produces = "text/html") //HTML 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 ? new ModelAndView("error", model) : modelAndView); } @RequestMapping @ResponseBody //JSON 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); }
When in HTML mode, a resolveErrorView class is built, and resolveErrorView continues to call ErrorViewResolver. critical code
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; }
When we do not make a custom configuration, ErrorViewResolver will point to DefaultErrorViewResolver.
static { //You can use 4xx and 5xx filenames to uniformly match errors, but the principle of precise priority will be followed 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) { ModelAndView modelAndView = resolve(String.valueOf(status), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { //Splice error code after error String errorViewName = "error/" + viewName; TemplateAvailabilityProvider provider = this.templateAvailabilityProviders .getProvider(errorViewName, this.applicationContext); //If the template engine is available, let the template engine parse for example: Template/error/404 if (provider != null) { return new ModelAndView(errorViewName, model); } //If the template engine is not available, find the resource file in the static resource folder, error/404 return resolveResource(errorViewName, model); }
3. How to customize error and exception response
Understand the exception handling mechanism of boot, how can we customize the exception response rules?
First, the pc returns the static error page, and the mobile returns the boot default json data
If there is a template engine (jsp,thmeleaf,freemarker) in the project, you can name the error page as the status code. html and put it in the error folder under the template engine folder,
If an exception occurs, whether it is a front-end request or a back-end program error, it will come to the corresponding error page. You can name the error page 4xx and 5xx to match all the errors,
However, the exact status code. html page is preferred; and the following information can be obtained on the template engine page
Here the template engine uses thmeleaf
4xx code
5xx code<!DOCTYPE html> <html lang="en"> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title th:text="'Status code:'+${status}"></title> </head> <body> < img src="../images/404.jpg" style="width: 40%;"> <h1 th:text="'time:'+${timestamp}"></h1> <h1 th:text="'Error prompt:'+${error}"></h1> <h1 th:text="'Exception object:'+${exception}"></h1> <h1 th:text="'Abnormal information:'+${message}"></h1> </body> </html>
We requested a wrong address path<!DOCTYPE html> <html lang="en"> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title th:text="'Status code:'+${status}"></title> </head> <body> <h2>500</h2> <h1 th:text="'time:'+${timestamp}"></h1> <h1 th:text="'Error prompt:'+${error}"></h1> <h1 th:text="'Exception object:'+${exception}"></h1> <h1 th:text="'Abnormal information:'+${message}"></h1> </body> </html>
We create an exception in the program code and request a response
Above is how to handle errors and exceptions with template engine,
If there is no template engine in the project (the template engine cannot find the error page), the corresponding 4xx or 5xx or more accurate error page will be found under the static resource folder. But if you don't use the template engine, the page can't get the above page information;
The above two methods use mobile access to return the boot default json data
Second, the pc side returns the dynamic page, and the mobile side returns the dynamic json data
The first one above can easily handle exceptions. You only need to place a static page (without a template engine) or a page (with a template engine) with relevant information in the specified path.
The disadvantage is that we can't carry the data we want to display on the page. For example, when an exception is released somewhere in our program, we need to return the error information we prompted. How to deal with this kind of exception?
By default, in Spring Boot, all the exception data is actually the first five pieces of data shown. These data are defined in the org.springframework.boot.web.reactive.error.DefaultErrorAttributes class, and specifically in the getErrorAttributes method: the core code is as follows
@Override public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<>(); errorAttributes.put("timestamp", new Date()); errorAttributes.put("path", request.path()); Throwable error = getError(request); HttpStatus errorStatus = determineHttpStatus(error); errorAttributes.put("status", errorStatus.value()); errorAttributes.put("error", errorStatus.getReasonPhrase()); errorAttributes.put("message", determineMessage(error)); handleException(errorAttributes, determineException(error), includeStackTrace); return errorAttributes; }
The DefaultErrorAttributes class itself is defined in the org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration exception autoconfiguration class,
If the developer does not provide an instance of ErrorAttributes, then Spring Boot will automatically provide an instance of ErrorAttributes, which is DefaultErrorAttributes.
Based on this, developers can customize ErrorAttributes in two ways:
1. Directly implement the ErrorAttributes interface
Inherit DefaultErrorAttributes (recommended), because the processing of exception data in DefaultErrorAttributes has been completed, and developers can use it directly.
package com.javayihao.top.config; import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; import org.springframework.stereotype.Component; import org.springframework.web.context.request.WebRequest; import java.util.Map; @Component public class MyErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace); if ((Integer)map.get("status") == 500) { //Set according to your own needs here map.put("message", "Server internal error!"); } if ((Integer)map.get("status") == 404) { map.put("message", "path does not exist!"); } return map; } }
Our server access error path
Client response
Access controller with exception
Client response
Of course, I can throw an exception anywhere in the program and use the global exception handler to handle it
Custom exception
Global exception handler
@ControllerAdvice public class MyExceptionHandler { @ExceptionHandler(MyException.class) public String jsonErrorHandler(HttpServletRequest request, Exception e) { Map<String, Object> map = new HashMap<>(); request.setAttribute("java.servlet.error.status_code", 500); map.put("code", -1); map.put("msg", e.getMessage()); request.setAttribute("ext", map); //Forward to error return "forward:/error"; } }
Custom ErrorAttributes
@Component public class MyErrorAttributes extends DefaultErrorAttributes { //The returned map is all the fields that the page or json can get @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace); //Additional content can be added map.put("company", "javayihao"); //Take out the data carried in the exception processor Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);//Passing in 0 means getting from request map.put("ext", ext); return map; } }
The third type: the pc side returns dynamic json data, and the mobile side also returns dynamic json data
This method is mainly aimed at the project with front and back separation. We define an exception to throw in the program
Define a return result object (or use map directly without definition) to store exception information
Define a global exception handler
/*ControllerAdvice Used to configure the package and annotation types that need exception handling, For example, @ ControllerAdvice(annotations = RestController.class) Only classes marked with rescontrolle will be blocked */ @ControllerAdvice public class MyExceptionHandler { //The exception created by yourself can be returned according to the information written by yourself @ExceptionHandler(value = MyException.class) @ResponseBody public ErrorInfo<String> errorInfo(HttpServletRequest req, MyException e) { ErrorInfo<String> r = new ErrorInfo<>(); r.setCode(ErrorInfo.ERROR); r.setMessage(e.getMessage()); r.setData("test data"); r.setUrl(req.getRequestURL().toString()); return r; } //When the system is abnormal, the returned exception number is - 1, and the returned exception information is "the system is under maintenance"; the original exception information cannot be returned @ExceptionHandler(value = Exception.class) @ResponseBody public ErrorInfo<String> errorInfo(HttpServletRequest req, Exception e) { ErrorInfo<String> r = new ErrorInfo<>(); r.setCode(ErrorInfo.ERROR); r.setMessage("System maintenance in progress"); return r; } }