Spring Boot 学习,错误页面

本文详细解析了SpringBoot中错误处理的流程,包括错误页面的定制、错误JSON数据的自适应响应以及如何通过自定义ErrorController和ErrorAttributes来增强错误处理能力。

浏览器默认返回的错误页面
在这里插入图片描述
浏览器发送请求的请求头:
在这里插入图片描述
其他客户端会返回json字符串
其他客户端请求的请求头中的Accept:"*/*",没有优先接收HTML数据。这就是区别
在这里插入图片描述
在这里插入图片描述

Spring Boot 默认的错误处理的自动配置在ErrorMvcAutoConfiguration类中

步骤:

  1. 出现了4xx或者5xx的错误,ErrorPageCustomizer就会生效(定制错误的响应规则)
  2. 来到/error请求,被BasicErrorController处理
  3. 响应页面
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
			Map<String, Object> model) {
	for (ErrorViewResolver resolver : this.errorViewResolvers) {// ErrorViewResolver  异常视图解析求
		ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
		if (modelAndView != null) {
			return modelAndView;
		}
	}
	return null;
}
  1. DefaultErrorAttributes解析
  1. DefaultErrorAttributes
    a. 在页面共享信息
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
    Map<String, Object> errorAttributes = new LinkedHashMap();
    errorAttributes.put("timestamp", new Date());
    this.addStatus(errorAttributes, webRequest);
    this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
    this.addPath(errorAttributes, webRequest);
    return errorAttributes;
}

b.
2. BasicErrorController
处理默认的/error请求

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
...
	// public static final String TEXT_HTML_VALUE = "text/html"; // 产生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, isIncludeStackTrace(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) { // 产生json数据,其他客户端发送的请求处理方法
		Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<>(body, status);
	}
  1. ErrorPageCustomizer
    系统出现错误以后,来到/error请求进行处理(相当于在web.xml中配置了异常页面的映射)
@Value("${error.path:/error}")
private String path = "/error";
  1. 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/4xx(或5xx)页面
	String errorViewName = "error/" + viewName;
	// 如果模板引擎可以解析就是要引擎解析
	TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
			this.applicationContext);
	if (provider != null) {
		// 模板引擎可用的情况下返回到errorViewName指定的视图地址error/4xx(或5xx).html
		return new ModelAndView(errorViewName, model);
	}
	// 如果模板引擎不可用,在静态资源文件夹下找errorViewName指定的视图地址
	return resolveResource(errorViewName, model);
}

定制错误响应

定制错误页面

1. 有模板引擎
  1. 在error目录下创建状态码对应的页面
    将错误页面命名 错误状态码.html,放在模板引擎文件夹下的error目录下,发生此状态码的错误,自动匹配当前错误页面
    如:templates/error/404.html
    在这里插入图片描述
    在这里插入图片描述
  2. 以4或者5开始的错误太多,每个状态码都对应一个页面,造成重复工作:可以使用4xx.html和5xx.html来匹配4xx或5xx错误。
    如果有精确的状态码页面(如404.html),优先使用404.html
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
	private static final Map<Series, String> SERIES_VIEWS;
	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);
	}
	...
}

在这里插入图片描述
在这里插入图片描述
页面能获取的信息:

key解释
timestamp时间戳
status状态码
error错误提示
exception异常对象
message异常消息
errorsJSR3030数据校验信息
<h1>状态码:[[${status}]]</h1>
<h2>时间:[[${#dates.format(timestamp, 'yyyy-MM-dd')}]]</h2>

在这里插入图片描述
在这里插入图片描述

2. 没有模板引擎

模板引擎找不到当前错误页面,默认在静态资源文件夹下找,但是获取不到状态码等信息
在这里插入图片描述
在这里插入图片描述
如果模板引擎和静态资源文件夹都没有,就使用默认的页面
在这里插入图片描述

定制错误的json数据

  1. 没有自适应
    自定义一个异常作测试
public class UserNotExistException extends RuntimeException{
    public UserNotExistException() {
        super("用户不存在!");
    }
}

在访问hello请求的时候做测试

@RequestMapping("/hello")
@ResponseBody
public String hello(@RequestParam("username") String username) {
    if ("aaa".equals(username)) {
        throw new UserNotExistException();
    }
    return "hello World!!";
}

编写异常处理器

@ControllerAdvice   // 异常处理器
public class MyExceptionHandler {
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class) // 要处理的异常(所有异常就是Exception.class)
    public Map<String, Object> handlerException(Exception e) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", "user.notExist");
        map.put("massage", e.getMessage());
        return map;
    }
}

在这里插入图片描述
现在的Json字符串就是自己定制的。但是这样做的话页面也是出现了定制的json数据
在这里插入图片描述
没有达到自适应的结果

  1. 自适应但是没有跳转到自己定制的错误页面

修改异常处理

@ExceptionHandler(UserNotExistException.class) // 要处理的异常(所有异常就是Exception.class)
public String handlerException(Exception e) {
    Map<String, Object> map = new HashMap<>();
    map.put("code", "user.notExist");
    map.put("massage", e.getMessage());
    // 转发到/error进行自适应响应效果处理
    return "forward:/error";
}

浏览器
在这里插入图片描述
其他客户端
在这里插入图片描述
3. 自适应效果

继续修改异常处理器

@ExceptionHandler(UserNotExistException.class) // 要处理的异常(所有异常就是Exception.class)
public String handlerException(Exception e, HttpServletRequest request) {
     Map<String, Object> map = new HashMap<>();
     // 设置错误状态码,4xx或5xx;如果不设置错误状态码,就不会跳转到自己定制的错误页面
     // Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
     request.setAttribute("javax.servlet.error.status_code", 500);
     map.put("code", "user.notExist");
     map.put("massage", e.getMessage());
     // 转发到/error进行自适应响应效果处理
     return "forward:/error";
 }

浏览器
在这里插入图片描述
其他客户端
在这里插入图片描述
4. 将定制的json数据返回
出现错误后,会发送/error请求,被BaseErrorController处理,响应的数据是由getErrorAttributes方法得到,是AbstractErrorController规定的方法,而AbstractErrorController是一个ErrorController。当自定义得有ErrorController的时候,系统就不会使用默认的BaseErrorController。
a. 可以编写ErrorController的实现类(或者AbstractErrorController的子类),放在容器中:
b. 不管是返回的页面还是Json都是使用getErrorAttributes获取的,而getErrorAttributes是通过ErrorAttributes调用的。

@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) // 容器中没有ErrorAttributes就添加一个默认的ErrorAttributes
public DefaultErrorAttributes errorAttributes() {
	return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}

定制自己的ErrorAttributes

@Component  // 给容器中加入自己的错误属性
// 为了简单,继承DefaultErrorAttributes
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        map.put("company", "caesar");
        return map;
    }
}

在这里插入图片描述
c. 添加上异常处理器中携带的数据

异常处理器

@ControllerAdvice   // 异常处理器
public class MyExceptionHandler {
    @ExceptionHandler(UserNotExistException.class) // 要处理的异常(所有异常就是Exception.class)
    public String handlerException(Exception e, HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        // 传入错误状态码,4xx或5xx
        // Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        request.setAttribute("javax.servlet.error.status_code", 500);
        map.put("code", "user.notExist");
        map.put("massage", "用户错误!!!!");
        // 将自定的数据添加到request域中
        request.setAttribute("ext", map);
        // 转发到/error进行自适应响应效果处理
        return "forward:/error";
    }
}

自定义的错误属性

@Component  // 给容器中加入自己的错误属性
// 为了简单,继承DefaultErrorAttributes
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        // 最后返回给页面或Json中的值
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        map.put("company", "caesar");
        // 异常处理器携带的值
        Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
        map.put("ext", ext);
        return map;
    }
}

在这里插入图片描述
在这里插入图片描述
异常对象为空,是应为在生成数据的时候有一个判断,如果是默认的空参构造创建的DefaultErrorAttributes对象,includeException 是false,false返回的error

 private final boolean includeException;
    public DefaultErrorAttributes() {
        this(false);
    }
    public DefaultErrorAttributes(boolean includeException) {
        this.includeException = includeException;
    }
    ...
    private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace) {
        Throwable error = this.getError(webRequest);
        if (error != null) {
            while(true) {
                if (!(error instanceof ServletException) || error.getCause() == null) {
                    if (this.includeException) {
                        errorAttributes.put("exception", error.getClass().getName());
                    }
                    this.addErrorMessage(errorAttributes, error);
                    if (includeStackTrace) {
                        this.addStackTrace(errorAttributes, error);
                    }
                    break;
                }
                error = ((ServletException)error).getCause();
            }
        }

        Object message = this.getAttribute(webRequest, "javax.servlet.error.message");
        if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null) && !(error instanceof BindingResult)) {
            errorAttributes.put("message", StringUtils.isEmpty(message) ? "No message available" : message);
        }

    }
    ...
}

文笔简陋,理解不透彻。自己做的笔记,有不正确的地方希望指出!!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值