SpringBoot global exception handling

1. Types of exception handling

I roughly divide a request into three stages to handle global exceptions separately.

  1. Before entering the Controller, for example, requesting an address that does not exist, 404 error.
  2. When executing @RequestMapping, before entering the logic processing stage. For example, the passed parameter type is wrong.
  3. When all the above are normal, an exception occurs when the logic code is executed in the controller. For example NullPointerException.

2. The first case

2.1. Error reporting

When using SpringBoot to call the controller, if the system displays error messages such as 404, 405, 500, etc., the system will display the following information by default:

2.2, SpringBoot default exception handling BasicErrorController

SpringBoot's default exception handling BasicErrorController, it will display the error page according to the configuration parameter server.error.path. These two methods of BasicErrorController are the key.

  • getErrorPath() error page path
  • errorHtml() returns error page information
 public String getErrorPath() {
        return this.errorProperties.getPath();
    }

    @RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

2.3, custom error exception

By studying BasicErrorController, we need about four points to implement ErrorController for customizing error exceptions.

  • Implement the ErrorController interface, and rewrite the getErrorPath() method to specify the url for the exception jump;
  • Add the @RestController annotation to the class, and add a Controller for abnormal jump url;
  • Get the type of request response, and do different logical processing for different response error types.
  • The HTML file of the error page is stored in the template directory.
@Slf4j
@Controller
@RequestMapping("/error")
@EnableConfigurationProperties({ServerProperties.class})
public class ErrorPagesController implements ErrorController {

    private ErrorAttributes errorAttributes;

    @Autowired
    private ServerProperties serverProperties;

    /**
     * Initialize ExceptionController
     *
     * @param errorAttributes
     */
    @Autowired
    public ErrorPagesController(ErrorAttributes errorAttributes) {
        Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
        this.errorAttributes = errorAttributes;
    }

    @RequestMapping("/404")
    public ModelAndView errorHtml404(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
        response.setStatus(HttpStatus.NOT_FOUND.value());
        Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
        model.put("queryString", request.getQueryString());

        return new ModelAndView("error/404", model);
    }

    @RequestMapping("/403")
    public ModelAndView errorHtml403(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
        response.setStatus(HttpStatus.FORBIDDEN.value());
        // 404 interception rules, if 404 occurs in static files, it will not be recorded to DB
        Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
        model.put("queryString", request.getQueryString());
        if (!String.valueOf(model.get("path")).contains(".")) {
            model.put("status", HttpStatus.FORBIDDEN.value());
        }
        return new ModelAndView("error/403", model);
    }

    @RequestMapping("/400")
    public ModelAndView errorHtml400(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
        model.put("queryString", request.getQueryString());
        return new ModelAndView("error/400", model);
    }

    @RequestMapping("/401")
    public ModelAndView errorHtml401(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
        model.put("queryString", request.getQueryString());
        return new ModelAndView("error/401", model);
    }

    @RequestMapping("/500")
    public ModelAndView errorHtml500(HttpServletRequest request, HttpServletResponse response, WebRequest webRequest) {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        Map<String, Object> model = getErrorAttributes(webRequest, isIncludeStackTrace(request, MediaType.TEXT_HTML));
        model.put("queryString", request.getQueryString());
        return new ModelAndView("error/500", model);
    }

    /**
     * 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) {
        ErrorProperties.IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace();
        if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
            return true;
        }
        return include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM && getTraceParameter(request);
    }


    /**
     * get wrong information
     *
     * @param webRequest
     * @param includeStackTrace
     * @return
     */
    private Map<String, Object> getErrorAttributes(WebRequest webRequest,
                                                   boolean includeStackTrace) {
        return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    }

    /**
     * Whether to include trace
     *
     * @param request
     * @return
     */
    private boolean getTraceParameter(HttpServletRequest request) {
        String parameter = request.getParameter("trace");
        return parameter != null && !"false".equalsIgnoreCase(parameter);
    }

    /**
     * get error code
     *
     * @param request
     * @return
     */
    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request
                .getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        try {
            return HttpStatus.valueOf(statusCode);
        } catch (Exception ex) {
            log.error("get current HttpStatus An exception occurs", ex);
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }

    /**
     * Implementing the wrong path, temporarily useless
     *
     * @return
     */
    @Override
    public String getErrorPath() {
        return "";
    }
}

3. The second case

When executing @RequestMapping, before entering the logic processing stage. For example, the passed parameter type is wrong.

/**
 * Created by wuwf on 17/3/31.
 * global exception handling
 */
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
    private static Logger logger = LogManager.getLogger(GlobalExceptionHandler.class.getName());

    /**
     * Before the content in the controller is executed, verify that some parameters do not match, the Get post method is wrong, etc.
     */
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
        System.out.println("mistake");
		// TODO
        return new ResponseEntity<Object>("error", NOT_EXTENDED);

    }
}

Define a class, use the @ControllerAdvice annotation, and inherit the ResponseEntityExceptionHandler class. There are many methods implemented in this class, you can go and have a look, including some parameter conversions, exceptions such as request method not supported, etc. will be caught.
The reason for being caught is the @ExceptionHandler tag, as long as all exception classes in it occur, they will be caught by this method.

4. The third case

When the first and second types do not have an exception, enter the actual logic execution, and then an exception occurs, so that you can define an ExceptionHandler method to handle the corresponding Exception.

/**
 * Created by wuwf on 17/3/31.
 * global exception handling
 */
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
    private static Logger logger = LogManager.getLogger(GlobalExceptionHandler.class.getName());

    /**
     * Before the content in the controller is executed, verify that some parameters do not match, the Get post method is wrong, etc.
     */
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
        System.out.println("mistake");

        return new ResponseEntity<Object>("error", NOT_EXTENDED);

    }

	@ExceptionHandler(value = Exception.class)
	@ResponseBody
	public Map<String, String> jsonExceptionHandler(HttpServletRequest req, ServiceException e) {
		Map<String, String> re = new HashMap<String, String>();
		re.put("status", e.getStatus());
		re.put("msg", e.getMessage());
		return re;
	}
}

Tags: Java Spring Spring Boot

Posted by dpluth on Thu, 22 Dec 2022 20:27:24 +0300