Spring Boot系列(7)——自定义异常反馈
〇、原始的异常反馈
当出现4xx或5xx错误时,spring boot项目返回的原始异常反馈是如下风格。
一、指定异常页面
1.ErrorMvcAutoConfiguration
按照Spring Boot的惯例,默认的配置都在xxxAutoConfiguration类中。而异常处理则在ErrorMvcAutoConfiguration中。
错误请求会交由BasicErrorController处理,从类名可以看出这是一个错误处理控制器
@Controller @RequestMapping({"${server.error.path:${error.path:/error}}"}) public class BasicErrorController extends AbstractErrorController {
/**
* 该方法响应错误请求
* 其中resolvErrorView形成错误反馈页面的路径
*/ @RequestMapping( produces = {"text/html"} ) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = this.getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); return modelAndView != null ? modelAndView : new ModelAndView("error", model); }
}
2.错误反馈页面路径
3.总结
把错误反馈页面放在根目录 error文件下(使用templeaf模板时,在/templates/error下),并且页面文件名为状态码,则发送该类错误,就会返回此页面。
例如:发生 404 Http请求错误时,会返回 /error/404.html的页面 或 /error/4xx.html的页面。
二、定制反馈信息
1.编写异常处理方法
注意:客户端或服务器异常会首先被处理再返回,而处理过后再返回则Http状态码会变为200,所以要通过"javax.servlet.error.status_code"指定Http的错误状态码
通过以下代码,当发生UserNotExistException异常时,就来到此方法,我们就可以在其中添加自定义的反馈信息。
1 /* 2 * 增强的Controller,作用如下: 3 * 1.全局异常处理 4 * 2.全局数据绑定 5 * 3.全局数据预处理 6 */ 7 @ControllerAdvice 8 public class ExceptionController { 9 10 /* 11 * @ExceptionHandler( )处理特定的异常 12 * UserNotExistException为自定义异常 13 * 即当发生该异常时,就转到该方法处理 14 */ 15 @ExceptionHandler(UserNotExistException.class) 16 public String hanldException(Exception e, HttpServletRequest request){ 17 Map<String,Object> map = new HashMap<>(); 18 request.setAttribute("javax.servlet.error.status_code",404); 19 20 //转到“/error”请求处理控制器处理方法 21 return "forward:/error"; 22 } 23 }
2.错误反馈信息要写在哪?
回答该问题,我们可以参考本文开头图片中的Spring Boot默认返回的 错误反馈数据出处,如下:
BasicErrorController控制器中errorHtml( )方法是响应/error请求的方法,其中调用了getErrorAttributes( )方法,而默认的错误反馈数据正是来自getErrorAttributes( )方法。
1 //BasicErrorController类 2 @RequestMapping( 3 produces = {"text/html"} 4 ) 5 public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { 6 HttpStatus status = this.getStatus(request); 7 Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML))); 8 response.setStatus(status.value()); 9 ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); 10 return modelAndView != null ? modelAndView : new ModelAndView("error", model); 11 }
1 //DefaultErrorAttributes类 2 public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { 3 Map<String, Object> errorAttributes = new LinkedHashMap(); 4 errorAttributes.put("timestamp", new Date()); 5 errorAttributes.put("path", request.path()); 6 Throwable error = this.getError(request); 7 MergedAnnotation<ResponseStatus> responseStatusAnnotation = MergedAnnotations.from(error.getClass(), SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class); 8 HttpStatus errorStatus = this.determineHttpStatus(error, responseStatusAnnotation); 9 errorAttributes.put("status", errorStatus.value()); 10 errorAttributes.put("error", errorStatus.getReasonPhrase()); 11 errorAttributes.put("message", this.determineMessage(error, responseStatusAnnotation)); 12 errorAttributes.put("requestId", request.exchange().getRequest().getId()); 13 this.handleException(errorAttributes, this.determineException(error), includeStackTrace); 14 return errorAttributes; 15 }
3.自定义反馈信息
按照上面所述,我们可以通过继承DefaultErrorAttributes类,重写getErrorAttributes( )方法以加入自定义的错误反馈信息:
再在页面中通过templeaf的标签将值取出
1 import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; 2 3 4 //继承DefaultErrorAttributes类 5 @Component 6 public class MyErrorAttributes extends DefaultErrorAttributes { 7 8 //重写getErrorAttributes方法 9 @Override 10 public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { 11 Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace); 12 13 //加入自定义信息 14 errorAttributes.put("message","页面找不到"); 15 return errorAttributes; 16 } 17 }
//页面
<h1>错误码:404</h1>
<P>message:[[${message}]]</P>
3.各方法执行顺序
(1)异常处理器@ExceptionHandler(异常类) ;
(2)BasicErrorController的errorHtml( )方法(调用getErrorAttributes( )方法);
(3)重写的getErrorAttributes( )方法 ;
(4)返回BasicErrorController的errorHtml( )方法;
根据此执行顺序,我们可以把自定义反馈信息在异常处理器中编写,并放入request中。再在重写的getErrorAttributes( )方法 中通过request取出,如下:
1 /* 2 * 增强的Controller,作用如下: 3 * 1.全局异常处理 4 * 2.全局数据绑定 5 * 3.全局数据预处理 6 */ 7 @ControllerAdvice 8 public class ExceptionController { 9 10 /* 11 * @ExceptionHandler( )处理特定的异常 12 * UserNotExistException为自定义异常 13 * 即当发生该异常时,就转到该方法处理 14 */ 15 @ExceptionHandler(UserNotExistException.class) 16 public String hanldException(Exception e, HttpServletRequest request){ 17 System.out.println("ExceptionController的hanldException方法"); 18 Map<String,Object> map = new HashMap<>(); 19 request.setAttribute("javax.servlet.error.status_code",404); 20 //将自定义的反馈信息放入request 21 map.put("message","页面找不到!"); 22 request.setAttribute("ext",map); 23 //转到“/error”请求处理控制器处理方法 24 return "forward:/error"; 25 } 26 }
1 //继承DefaultErrorAttributes类 2 @Component 3 public class MyErrorAttributes extends DefaultErrorAttributes { 4 5 //重写getErrorAttributes方法 6 @Override 7 public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { 8 Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace); 9 System.out.println("MyErrorAttributes的getErrorAttributes方法"); 10 //从request取出自定义信息 11 errorAttributes.put("ext",webRequest.getAttribute("ext",0)); 12 return errorAttributes; 13 } 14 }