错误处理机制
springboot的默认错误处理机制
在浏览器上默认返回的是一个错误页面, 下面所示
因为在浏览器中请求优先接收text/ html数据
在其他的客户端默认返回的是json字符串
因为在其他的客户端的接收参数是: accept:"*/*"
springboot错误处理机制原理
具体的我们可以参考源码ErrorMvcAutoConfiguration,
在这个自动配置类里面主要给给容器添加了下面重要的组件:
DefaultErrorAttributes
BasicErrorController
ErrorPageCustomizer
DefaultErrorViewResolver
BasicErrorController
@Controller
@RequestMapping ( "${server.error.path:${error.path:/error}}" )
public class BasicErrorController extends AbstractErrorController {
@RequestMapping ( produces = "text/html" )
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 ? new ModelAndView ( "error" , model) : modelAndView) ;
}
@RequestMapping
@ResponseBody
public ResponseEntity< Map< String, Object> > error ( HttpServletRequest request) {
Map< String, Object> body = getErrorAttributes ( request,
isIncludeStackTrace ( request, MediaType. ALL) ) ;
HttpStatus status = getStatus ( request) ;
return new ResponseEntity < Map< String, Object> > ( body, status) ;
}
点进resolveErrorView进行查看, 可以看出这个方法是得到所有的ErrorViewResolver
进行解析, 如果解析成功就返回回去, 否则就返回null
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;
}
ErrorPageCustomizer
里面就是在系统出现错误之后来到error请求
里面的getPath ( ) 就是从配置文件里面获取配置的值
如果取不出, 默认是/ error
@Value ( "${error.path:/error}" )
private String path = "/error" ;
private static class ErrorPageCustomizer implements ErrorPageRegistrar , Ordered {
private final ServerProperties properties;
protected ErrorPageCustomizer ( ServerProperties properties) {
this . properties = properties;
}
@Override
public void registerErrorPages ( ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage ( this . properties. getServletPrefix ( )
+ this . properties. getError ( ) . getPath ( ) ) ;
errorPageRegistry. addErrorPages ( errorPage) ;
}
@Override
public int getOrder ( ) {
return 0 ;
}
}
DefaultErrorViewResolver
public class DefaultErrorViewResolver implements ErrorViewResolver , Ordered {
private static final Map< Series, String> SERIES_VIEWS;
static {
Map< Series, String> views = new HashMap < Series, String> ( ) ;
views. put ( Series. CLIENT_ERROR, "4xx" ) ;
views. put ( Series. SERVER_ERROR, "5xx" ) ;
SERIES_VIEWS = Collections. unmodifiableMap ( views) ;
}
@Override
public ModelAndView resolveErrorView ( HttpServletRequest request, HttpStatus status,
Map< String, Object> model) {
ModelAndView modelAndView = resolve ( String. valueOf ( status) , 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 . resourceProperties. 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;
}
DefaultErrorAttributes
这个是进行页面共享信息的
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误
@Order ( Ordered. HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes
implements ErrorAttributes , HandlerExceptionResolver, Ordered {
@Override
public Map< String, Object> getErrorAttributes ( RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map< String, Object> errorAttributes = new LinkedHashMap < String, Object> ( ) ;
errorAttributes. put ( "timestamp" , new Date ( ) ) ;
addStatus ( errorAttributes, requestAttributes) ;
addErrorDetails ( errorAttributes, requestAttributes, includeStackTrace) ;
addPath ( errorAttributes, requestAttributes) ;
return errorAttributes;
}
private void addStatus ( Map< String, Object> errorAttributes,
RequestAttributes requestAttributes) {
Integer status = getAttribute ( requestAttributes,
"javax.servlet.error.status_code" ) ;
if ( status == null) {
errorAttributes. put ( "status" , 999 ) ;
errorAttributes. put ( "error" , "None" ) ;
return ;
}
errorAttributes. put ( "status" , status) ;
try {
errorAttributes. put ( "error" , HttpStatus. valueOf ( status) . getReasonPhrase ( ) ) ;
}
catch ( Exception ex) {
errorAttributes. put ( "error" , "Http Status " + status) ;
}
}
private void addErrorDetails ( Map< String, Object> errorAttributes,
RequestAttributes requestAttributes, boolean includeStackTrace) {
Throwable error = getError ( requestAttributes) ;
if ( error != null) {
while ( error instanceof ServletException && error. getCause ( ) != null) {
error = ( ( ServletException) error) . getCause ( ) ;
}
errorAttributes. put ( "exception" , error. getClass ( ) . getName ( ) ) ;
addErrorMessage ( errorAttributes, error) ;
if ( includeStackTrace) {
addStackTrace ( errorAttributes, error) ;
}
}
Object message = getAttribute ( requestAttributes, "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) ;
}
}
private void addErrorMessage ( Map< String, Object> errorAttributes, Throwable error) {
BindingResult result = extractBindingResult ( error) ;
if ( result == null) {
errorAttributes. put ( "message" , error. getMessage ( ) ) ;
return ;
}
if ( result. getErrorCount ( ) > 0 ) {
errorAttributes. put ( "errors" , result. getAllErrors ( ) ) ;
errorAttributes. put ( "message" ,
"Validation failed for object='" + result. getObjectName ( )
+ "'. Error count: " + result. getErrorCount ( ) ) ;
}
else {
errorAttributes. put ( "message" , "No errors" ) ;
}
}
是如何调用到这个类的呢?
在BasicErrorController控制器里面, 用来返回的数据都在model里面
model的获取是调用getErrorAttributes ( )
Map< String, Object> model = Collections. unmodifiableMap ( getErrorAttributes (
request, isIncludeStackTrace ( request, MediaType. TEXT_HTML) ) ) ;
进入到getErrorAttributes ( )
protected Map< String, Object> getErrorAttributes ( HttpServletRequest request,
boolean includeStackTrace) {
RequestAttributes requestAttributes = new ServletRequestAttributes ( request) ;
return this . errorAttributes. getErrorAttributes ( requestAttributes,
includeStackTrace) ;
}
然后是errorAttributes调用getErrorAttributes ( )
errorAttributes就是
private final ErrorAttributes errorAttributes;
它的实现类就是DefaultErrorAttributes
所以返回去的数据是通过DefaultErrorAttributes获取的
总的步骤是: 系统一出现4 xx或者5 xx之类的错误,
ErrorPageCustomizer就会起作用, 进行定制错误的响应规则,
然后就会来到/ error请求, 然后会被BasicErrorController
进行处理
springboot定制错误响应
定制错误页面
如果是有模板引擎的情况下:
就将错误页面命名为错误状态码. html放在
模板引擎文件夹里面的error文件夹下面就行, 如果发生对应的状态码就会
去到对应状态码的页面里面去
但是这样的话, 那么多的错误状态码, 我们不可能为每个错误都写一个页面
所以我们还可以使用4 xx和5 xx作为错误页面的文件名来匹配这种类型的所有错误
当然如果有精确的匹配, 肯定找精确匹配的状态码
没有模板引擎的情况下( 模板引擎找不到这个错误页面) :
那么就会到静态资源文件夹下面去找
上面两个都找不到的情况:
那么就是默认返回springboot的空白的错误页面显示
原理:
在BasicErrorController里面的errorHtml ( ) 里面有
return ( modelAndView == null ? new ModelAndView ( "error" , model) : modelAndView) ;
现在找不到就返回error视图, 这个视图是在容器里面的
在ErrorMvcAutoConfiguration里面的WhitelabelErrorViewConfiguration里面
它给容器加入了一个error的视图, 视图的内容是defaultErrorView;
@Configuration
@ConditionalOnProperty ( prefix = "server.error.whitelabel" , name = "enabled" , matchIfMissing = true )
@Conditional ( ErrorTemplateMissingCondition. class )
protected static class WhitelabelErrorViewConfiguration {
private final SpelView defaultErrorView = new SpelView (
"<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'>${timestamp}</div>"
+ "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
+ "<div>${message}</div></body></html>" ) ;
@Bean ( name = "error" )
@ConditionalOnMissingBean ( name = "error" )
public View defaultErrorView ( ) {
return this . defaultErrorView;
}
定制错误json数据
1.
我们可以定制自己的异常处理器来接收某种异常, 然后返回定制的json数据
但是这样没有自适应的效果, 就是不能浏览器返回的是页面,
其他客户端返回的是json数据,
这种处理方式返回的都是json数据了
package jane. test. myexception;
public class UserNotExistException extends RuntimeException
{
public UserNotExistException ( )
{
super ( "异常:用户不存在异常" ) ;
}
}
package jane. test. controller;
import jane. test. myexception. UserNotExistException;
import org. springframework. web. bind. annotation. ControllerAdvice;
import org. springframework. web. bind. annotation. ExceptionHandler;
import org. springframework. web. bind. annotation. ResponseBody;
import java. util. HashMap;
import java. util. Map;
@ControllerAdvice
public class MyExceptionHandler
{
@ResponseBody
@ExceptionHandler ( UserNotExistException. class )
public Map< String, Object> handlerException ( Exception e)
{
Map< String, Object> map = new HashMap < > ( ) ;
map. put ( "handlerCode" , "处理器说接收到用户不存在异常" ) ;
map. put ( "handlerMessage" , e. getMessage ( ) ) ;
return map;
}
}
2.
转发到/ error进行自适应响应效果处理
@ExceptionHandler ( UserNotExistException. class )
public String handlerException ( Exception e, HttpServletRequest request)
{
request. setAttribute ( "javax.servlet.error.status_code" , 500 ) ;
Map< String, Object> map = new HashMap < > ( ) ;
map. put ( "handlerCode" , "处理器说接收到用户不存在异常" ) ;
map. put ( "handlerMessage" , e. getMessage ( ) ) ;
return "forward:/error" ;
}
3.
上面是进行了自适应了, 但是我们自己定制的数据携带不出去,
现在我们想将自己定制的数据携带出去
错误出现以后, 就会来到/ error请求, 会被BasicErrorController进行处理
响应出去可以获取的数据是由getErrorAttributes得到的
这个方法是由AbstractErrorController抽象类定义的
AbstractErrorController implements ErrorController
而BasicErrorController就是实现了AbstractErrorController
所以
1. 我们要携带自己定制的数据, 就可以完全编写一个ErrorController的实现类
或者是编写AbstractErrorController的子类,放在容器中;
2.
我们也可以发现, 页面上能用的数据,或者是json返回能用的数据都是通过
errorAttributes. getErrorAttributes得到;
默认是调用容器中DefaultErrorAttributes. getErrorAttributes ( ) 进行数据处理的;
所以我们可以自定义ErrorAttributes
package jane. test. component;
import org. springframework. boot. autoconfigure. web. DefaultErrorAttributes;
import org. springframework. stereotype. Component;
import org. springframework. web. context. request. RequestAttributes;
import java. util. Map;
@Component
public class MyErrorAttributes extends DefaultErrorAttributes
{
@Override
public Map< String, Object> getErrorAttributes ( RequestAttributes requestAttributes, boolean includeStackTrace)
{
Map< String, Object> map = super . getErrorAttributes ( requestAttributes, includeStackTrace) ;
map. put ( "名字" , "jane" ) ;
Map< String, Object> ext = ( Map< String, Object> ) requestAttributes. getAttribute ( "ext" , 0 ) ;
map. put ( "ext" , ext) ;
return map;
}
}