十、SpringBoot错误处理原理&定制错误页面

一、SpringBoot默认的错误处理机制

默认效果:

  1. 使用浏览器访问时会跳转到错误页面
    在这里插入图片描述

  2. 如果是其他设备访问会返回json数据在这里插入图片描述

原理:
对于错误处理的自动配置在spring-boot-autoconfigure-2.0.6.RELEASE.jarorg.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration中配置

ErrorMvcAutoConfiguration.class给容器中添加了以下组件:

	@Bean
	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
	public DefaultErrorAttributes errorAttributes() {
		return new DefaultErrorAttributes();
	}

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

	@Bean
	public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
		return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
	}
	
	@Bean
	@ConditionalOnBean(DispatcherServlet.class)
	@ConditionalOnMissingBean(ErrorViewResolver.class)
	DefaultErrorViewResolver conventionErrorViewResolver() {
		return new DefaultErrorViewResolver(this.applicationContext, this.resources);
	}

1、DefaultErrorAttributes :帮我们在页面共享信息
页面能获取的信息:
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误在这里

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

2、BasicErrorController :处理默认/error请求

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

	//产生html类型的页面,浏览器发送的请求头中的Accept为text/html,所以浏览器发送的请求来到这个方法处理
	@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);
	}

	//产生json数据,其他客户端发送的请求头中的Accept为/*,所以其他客户端发送的请求来到这个方法处理
	@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);
	}

3、ErrorPageCustomizer

	//系统出现错误以后来到error请求进行处理
	@Value("${error.path:/error}")
	private String path = "/error";

4、DefaultErrorViewResolver

	@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) {
		//默认SpringBoot可以去找到一个界面,error/404
		String errorViewName = "error/" + viewName;
		//模板引擎可以解析这个地址就用模板引擎解析
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
				this.applicationContext);
		if (provider != null) {
			//模板引擎可用的情况下返回到errorViewName指定的设置地址
			return new ModelAndView(errorViewName, model);
		}
		//模板引擎如果不可用,就在静态资源文件夹下找errorViewName对应的页面
		return resolveResource(errorViewName, model);
	}

步骤:

一旦系统出现4xx或5xx之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则)就会来到/error请求;然后就会被BasicErrorController处理;
两种方式处理:
1、响应页面:去哪个界面是由DefaultErrorViewResoulver解析得到的。
2、返回json数据:

二、如何定制错误响应:

1、如何定制错误的页面

  1. 有模板引擎的情况下,在template下创建error/状态码;[将错误页面命名为 错误状态码.html 放在error文件夹下] 发生此状态错误就会来到对应的页面。
    我们可以使用4xx或者5xx作为错误页面的文件名来匹配这种类型的所有错误,有精确文件,精确文件优先。
  2. 没有模板引擎的情况下(template下找不到错误页面),静态资源文件下找
  3. 以上都没有错误页面,就是默认来到springboot默认的提示界面

2、如何定制错误的json数据

  1. 没有自适应浏览器访问和其他终端访问效果,即不管通过浏览器或是其他客户端访问返回的都是json数据

import java.util.HashMap;
import java.util.Map;
 
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
 
@ControllerAdvice
public class MyExceptionHandler {
	
	@ExceptionHandler(MyException.class)//捕获自定义异常
	@ResponseBody
	public Map<String,Object> handlerException(Exception e) {//出现异常时会将异常传递过来
		
		Map<String,Object> map = new HashMap<String,Object>();
		map.put("code", "自定义状态码");
		map.put("msg", e.getMessage());
		return map;
		
	}
}
  1. 转发到/error进行自适应响应效果处理,但是在返回的json数据中无法获得我们自定义的信息

@ExceptionHandler(MyException.class)
	public String handlerException(Exception e,HttpServletRequest request) {
		Map<String,Object> map = new HashMap<String,Object>();
		map.put("code", "自定义状态码");
		map.put("msg", e.getMessage());
		//设置错误状态码,一定要设置,否者就不会进入到自定义页面中
		request.setAttribute("javax.servlet.error.status_code", 404);
		//将自己的异常信息加入到request
		request.setAttribute("extMap", map);
		//转发到/error
		return "forward:/error";
	}
  1. 响应是自适应的,可以通过ErrorAttributes改变需要返回的内容
    实现方式:既然页面和json的错误信息都是通过getErrorAttributes(HttpServletRequest, boolean)获取,那么我们就写一个类继承DefaultErrorAttributes(上面提到的组件一),重写getErrorAttributes()方法就可以啦

import java.util.Map;
 
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
 
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
 
 	//返回的map就是页面和json能获取的所有字段
	@Override
	public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
		Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
		Map<String, Object> map = (Map<String, Object>) 
		//我们的异常处理器携带的数据
		webRequest.getAttribute("extMap", 0);//0标识从request中获取
		errorAttributes.put("message", "用户出错啦");
		errorAttributes.put("extMap",map);
		return errorAttributes;
	}
 

版权声明:本文为weixin_53107227原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。