共计 2398 个字符,预计需要花费 6 分钟才能阅读完成。
本周在看陈杰写的自定义异常的微信异常时,使用的是自定义异常状态码和信息,在出错时将他抛出,并用 @ExceptionHandler 注解定义一个全局异常处理器,根据异常的内容向前台发送状态码和信息, 处理异常的代码如下图:
// 处理微信登录的异常
@ExceptionHandler(value = WechatLoginException.class)
public String WechatLoginExceptionHandler(HttpServletRequest request, HttpServletResponse response, WechatLoginException e){
logger.error(“ 微信登录异常:—Host {} invokes url {} ERROR: {}”, request.getRemoteHost(), request.getRequestURL(), e.getMessage());
response.setStatus(e.getCode());
return e.getMessage();
}
在这里我看的时候有点疑惑,将状态码写入响应,而信息却直接返回了,询问陈杰,前台果然没有接受到 e.getMessage() 的信息, 我上网搜索了一下, 推荐他使用 response.sendError(code, message) 这个方法来返回异常的信息, 但是这么一试之后却遭到了奇怪的问题.
莫名的拦截器
项目配置了一个拦截器,专门用来对用户进行验证是否登录的,这个是前提. 在使用 response.setStatus() 方法时,前台能正确的接受到传入的状态码,而使用 response.sendError() 时,前台却接受到的一直是 401 用户未登录的状态码,打了断点进行调试, 分别在拦截器, 跑出异常的方法, 处理异常的方法打上断点, 测试使用 response.setStatus() 和 response.sendError() 方法来查看执行顺序, 结果让我感到惊奇: 使用 response.setStatus() 执行顺序:
使用 response.sendError() 执行顺序:
出现了令人惊奇两点:1.setStatus() 请求时没有经过拦截器 2.sendError() 在异常处理完毕后经过了一次拦截器查看注册拦截器配置, 解决了第一个问题的疑惑:
public void addInterceptors(InterceptorRegistry registry) {
// 添加拦截器,去除对登录的拦截
registry.addInterceptor(authInterceptor)
.excludePathPatterns(“/user/login”)
.excludePathPatterns(“/user/wechatLogin”);
}
这个异常是用户登录时抛出的,在注册时将登录路径给忽略了,因为我们只是拦截未登录的请求,而请求登录的请求不应该拦截,这是正确的,但第二点却怎么也不明白,本应忽略拦截的请求,为什么换了 sendError() 方法后,却在异常处理完毕后经过了异常拦截器?
springboot 的默认异常处理
对比两个方法的不同:setStatus() 只是改了一下状态吗, 而 sendError() 还有请求错误的意味,于是猜想是不是请求错误才会出现这种情况, 将方法直接改为 throw new RuntimeException()( 没有处理异常), 发现拦截器拦截的请求的 url 居然是一个 /error 的 url. 这个 /error 的 url 并未在项目中定义过任何的控制器中,也从未发起这样的请求,上网一查询, 原来这是 Spring Boot 提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异常内容. 但是我们的拦截器把这个请求拦截了 (并且这个请求没有携带正确的 cookie),所以直接就返回了 401 错误,response 中也没有我们定义的状态码和信息了.
json 还是 html
一切真相大白了,但忽然想到如果是浏览器发起的请求,服务器错误后 springboot 默认异常处理返回的是 html,但是如果像我们前后台分离的请求,返回就不应该是 html 而是 json 的错误信息了,这个要怎么区分呢?使用 google 插件发送请求,返回的 body 是这样的:
而用浏览器发起的请求返回的却是一个 html 页面:
<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id=’created’>Sat Apr 13 21:34:34 CST 2019</div><div>There was an unexpected error (type=Internal Server Error, status=500).</div><div>No message available</div></body></html>
仔细查看两者发起的请求不同, 在浏览器发起请求信息 requestheader 上发现了 Accept 字段:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
顿时就明白了, 在发送请求时 spring-boot 根据 Accept 字段来给你返回响应的内容, 例如 application/json 返回 json,text/html 返回 html,真是感叹 spring-boot 真是太周全了.
总结
spring-boot 好心帮你默认请求异常,但是却给你带来了麻烦,感觉还是自己理解的不够多,学习路还远着呢。