SpringBoot错误处理

SpringBoot错误处理

1 SpringMVC写法

1.1 在单独的Controller写一个处理异常的方法处理

@Slf4j
@RestController
public class HelloController {

    @GetMapping("/exception")
    public String exception(){
        int i = 1 / 0;
        return "异常";
    }

    /**
     * 只能处理当前Controller发生的错误
     */
    @ExceptionHandler
    public String handlerException(Exception e){
        return "exception...." + e.getMessage();
    }
}

1.2 写一个异常处理类,使用@ControllerAdvice注解处理所有的Controller异常

public class GlobalExceptionHandler {

    @ResponseBody
    @ExceptionHandler(Exception.class)
    public String handlerException(Exception e){
        return "发生错误,原因:" + e.getMessage();
    }
}

2. SpringBoot处理流程分析

2.1 默认机制

错误处理的自动配置都在ErrorMvcAutoConfiguration中,两大核心机制:

  • SpringBoot 会自适应处理错误,响应页面JSON数据
  • SpringMVC的错误处理机制依然保留,MVC处理不了,才会交给boot进行处理
  1. 解析一个错误页
    a. 如果发生了500、404、503、403 这些错误
    ⅰ. 如果有模板引擎,默认在 classpath:/templates/error/精确码.html
    ⅱ. 如果没有模板引擎,在静态资源文件夹下找 精确码.html
    b. 如果匹配不到精确码.html这些精确的错误页,就去找5xx.html,4xx.html模糊匹配
    ⅰ. 如果有模板引擎,默认在 classpath:/templates/error/5xx.html
    ⅱ. 如果没有模板引擎,在静态资源文件夹下找 5xx.html
  2. 如果模板引擎路径templates下有 error.html页面,就直接渲染

2.2 源码分析

发生错误以后,转发给/error路径,SpringBoot在底层写好一个 BasicErrorController的组件,专门处理这个请求。通过内容协商判断调用返回页面(errorHtml方法)还是json或xml格式(error方法)数据。

BasicErrorController.java

	@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, getErrorAttributeOptions(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) {
		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);
	}

分析返回页面

//1、解析错误的自定义视图地址
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//2、如果解析不到错误页面的地址,默认的错误页就是 error
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);

解析自定义视图 AbstractErrorController.java

	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;
	}

通过容器的默认视图解析器解析错误页面DefaultErrorViewResolver.java

流程参考图示

	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
		ModelAndView modelAndView = resolve(String.valueOf(status.value()), 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) {
		String errorViewName = "error/" + viewName;
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
				this.applicationContext);
		if (provider != null) {
			return new ModelAndView(errorViewName, model);
		}
		return resolveResource(errorViewName, model);
	}

	private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
		for (String location : this.resources.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;
	}

最佳实践

  • 前后分离
    后台发生的所有错误,@ControllerAdvice + @ExceptionHandler进行统一异常处理。
  • 服务端页面渲染
    • 不可预知的一些,HTTP码表示的服务器或客户端错误
      • classpath:/templates/error/下面,放常用精确的错误码页面。500.html404.html
      • classpath:/templates/error/下面,放通用模糊匹配的错误码页面。 5xx.html4xx.html
    • 发生业务错误
      • 核心业务,每一种错误,都应该代码控制,跳转到自己定制的错误页。
      • 通用业务,classpath:/templates/error.html页面,显示错误信息。

参考
https://www.yuque.com/leifengyang/springboot3/wp5l9qbu1k64frz1#CLn90

posted @ 2023-10-26 14:34  雨中遐想  阅读(16)  评论(0编辑  收藏  举报