SpringBoot错误信息处理机制

★ 在一个web项目中,总需要对一些错误进行界面或者json数据返回,已实现更好的用户体验,SpringBoot中提供了对于错误处理的自动配置

ErrorMvcAutoConfiguration这个类存放了所有关于错误信息的自动配置。

1. SpringBoot处理错误请求的流程

访问步骤:

  • 首先客户端访问了错误界面。例:404或者500
  • SpringBoot注册错误请求/error。通过ErrorPageCustomizer组件实现
  • 通过BasicErrorController处理/error,对错误信息进行了自适应处理,浏览器会响应一个界面,其他端会响应一个json数据
  • 如果响应一个界面,通过DefaultErrorViewResolver类来进行具体的解析。可以通过模板引擎解析也可以解析静态资源文件,如果两者都不存在则直接返回默认的错误JSON或者错误View
  • 通过DefaultErrorAttributes来添加具体的错误信息

源代码

//错误信息的自动配置
public class ErrorMvcAutoConfiguration {//响应具体的错误信息@Beanpublic DefaultErrorAttributes errorAttributes() {return}//处理错误请求@Beanpublic BasicErrorController basicErrorController() {return }//注册错误界面@Beanpublic ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {return }
//注册错误界面,错误界面的路径为/error
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {//服务器基本配置private final ServerProperties properties;public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {//获取服务器配置中的错误路径/errorErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));//注册错误界面errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});}  //this.properties.getError()
public class ServerProperties{//错误信息的配置文件private final ErrorProperties error = new ErrorProperties();
}//getPath
public class ErrorProperties {@Value("${error.path:/error}")private String path = "/error";
//处理/error请求,从配置文件中取出请求的路径
@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 = 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);}//其他客户端行为处理,返回一个JSON数据@RequestMappingpublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {HttpStatus status = this.getStatus(request);if (status == HttpStatus.NO_CONTENT) {return new ResponseEntity(status);} else {Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));return new ResponseEntity(body, status);}}
//添加错误信息
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {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;}

2. 响应一个视图

步骤:

  • 客户端出现错误
  • SpringBoot创建错误请求/error
  • BasicErrorController处理请求
@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,调用父类AbstractErrorController的方法ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);return modelAndView != null ? modelAndView : new ModelAndView("error", model);}
public abstract class AbstractErrorController{private final List<ErrorViewResolver> errorViewResolvers;protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {Iterator var5 = this.errorViewResolvers.iterator();//遍历所有的错误视图解析器ModelAndView modelAndView;do {if (!var5.hasNext()) {return null;}ErrorViewResolver resolver = (ErrorViewResolver)var5.next();//调用视图解析器的方法,modelAndView = resolver.resolveErrorView(request, status, model);} while(modelAndView == null);return modelAndView;}
}
public interface ErrorViewResolver {ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model);
}

  • 处理具体的视图跳转
//处理视图跳转
public DefaultErrorViewResolver{public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {//将状态码作为视图名称传入解析ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);}return modelAndView;}
//视图名称为error文件夹下的400.html等状态码文件private ModelAndView resolve(String viewName, Map<String, Object> model) {String errorViewName = "error/" + viewName;//是否存在模板引擎进行解析TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);//存在则返回解析以后的数据,不存在调用resolveResource方法进行解析return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);}  //如果静态资源文件中存在,返回静态文件下的,如果不存在返回SpringBoot默认的private ModelAndView resolveResource(String viewName, Map<String, Object> model) {String[] var3 = this.resourceProperties.getStaticLocations();int var4 = var3.length;for(int var5 = 0; var5 < var4; ++var5) {String location = var3[var5];try {Resource resource = this.applicationContext.getResource(location);resource = resource.createRelative(viewName + ".html");if (resource.exists()) {return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);}} catch (Exception var8) {}}return null;}

应用:

  • 在模板引擎文件下创建error文件夹,里面放置各种状态码的视图文件,模板引擎会解析
  • 在静态资源下常见error文件夹,里面放置各种状态码的视图文件,模板引擎不会解析
  • 如果没有状态码文件,则返回springBoot默认界面视图

3.响应一个json数据

BasicErrorController处理/error请求的时候不适用浏览器默认请求

@RequestMappingpublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {HttpStatus status = this.getStatus(request);if (status == HttpStatus.NO_CONTENT) {return new ResponseEntity(status);} else {//调用父类的方法获取所有的错误属性Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));return new ResponseEntity(body, status);}}
父类方法:
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {WebRequest webRequest = new ServletWebRequest(request);//调用ErrorAttributes接口的getErrorAttributes方法,return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);}
添加错误信息
public class DefaultErrorAttributes{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;}

返回的json数据有:

  • status
  • error
  • exception
  • message
  • trace
  • path

可以通过模板引擎获取这些值

4.自定义异常返回自定义的异常数据

4.1@ControllerAdvice注解

★ SpringMVC提供的注解,可以用来定义全局异常,全局数据绑定,全局数据预处理

@ControllerAdivice定义全局的异常处理

  • 通过@ExceptionHandler(XXXException.class)执行该方法需要处理什么异常,然后返回什么数据或者视图
//json数据返回 ,处理自定义用户不存在异常@ResponseBody@ExceptionHandler(UserException.class)public Map<String,String> userExceptionMethod(UserException us){Map<String,String> map = new HashMap<>();map.put("message",us.getMessage());return map ;}

@ControllerAdvice定义全局数据

  • 通过@ModelAttribute(Name="key")定义全局数据的key
  • 默认方法的返回值的名称作为键
  • Controller中通过Model获取对应的key的值
@ControllerAdvice
public MyConfig{@ModelAttribute(name = "key")public Map<String,String> defineAttr(){Map<String,String> map = new HashMap<>();map.put("message","幻听");map.put("update","许嵩");return map ;}@Controller
public UserController{@GetMapping("/hello")public Map<String, Object> hello(Model model){Map<String, Object> asMap = model.asMap();System.out.println(asMap);//{key={message='上山',update='左手一式太极拳'}}return asMap ;}
}

@ControllerAdvice处理预处理数据(当需要添加的实体,属性名字相同的时候)

  • Controller的参数中添加ModelAttribute作为属性赋值的前缀
  • ControllerAdvice修饰的类中,结合InitBinder来绑定对应的属性(该属性为ModelAttribite的value值
  • @InitBinder修饰的方法中通过WebDataBinder添加默认的前缀
@Getter@Setter
public class Book {private String name ;private int age ;@Getter@Setter
public class Music {private String name ;private String author ;//这种方式的处理,spring无法判断Name属性给哪个bean赋值,所以需要通过别名的方式来进行赋值
@PostMapping("book")public String book(Book book , Music music){System.out.println(book);System.out.println(music);return "404" ;}//使用以下的方式
@PostMapping("/book")public String book(@ModelAttribute("b")Book book , @ModelAttribute("m")Music music){System.out.println(book);System.out.println(music);return "404" ;}public MyCOnfiguration{@InitBinder("b")public void b(WebDataBinder webDataBinder){webDataBinder.setFieldDefaultPrefix("b.");}@InitBinder("m")public void m(WebDataBinder webDataBinder){webDataBinder.setFieldDefaultPrefix("m.");}
}

4.2自定义异常JSON

浏览器和其他客户端都只能获取json数据

@ControllerAdvice
public class MyExceptionHandler {//处理UserException异常@ResponseBody@ExceptionHandler(UserException.class)public Map<String,String> userExceptionMethod(UserException us){Map<String,String> map = new HashMap<>();map.put("message",us.getMessage());map.put("status","500");return map ;}

4.2自定义异常返回一个视图,拥有自适应效果

@ExceptionHandler(UserException.class)public String allException(UserException e,HttpServletRequest request){Map<String,String> map = new HashMap<>();map.put("message",e.getMessage());map.put("load","下山");request.setAttribute("myMessage",map);//设置状态码,SpringBoot通过java.servlet.error.status_code来设置状态码request.setAttribute("javax.servlet.error.status_code",400);return "forward:/error" ;}

当抛出UserException异常的时候,来到这个异常处理器,给这个请求中添加了数据,再转发到这个error请求中,交给ErrorPageCustomizer处理,由于设置了请求状态码400则返回的视图为400或4XX视图,或者直接返回一个JSON数据

{"timestamp": "2020-02-19T04:17:43.394+0000","status": 400,"error": "Bad Request","message": "用户名不存在异常","path": "/crud/user/login"
}

  • 不足:JSON数据中没有显示我们自己定义的错误信息

4.3自定义错误信息

★ 前面提到SpringBoot对错误信息的定义存在于DefaultErrorAttributes类的getErrorAttributes中,我们可以直接继承这个类,或者实现ErrorAttributes接口,然后将我们自己实现的错误处理器添加到容器中即可。

继承DefaultErrorAttributes和实现ErrorAttributes接口的区别是,继承以后仍然可以使用SpringBoot默认的错误信息,我们仅仅对该错误信息进行了增强;实现了ErrorAttributes接口,完全自定义错误信息

  • 实现ErrorAttributes接口
public class MyErrorHandler implements ErrorAttributes {public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {Map<String, Object> errorAttributes = new LinkedHashMap();errorAttributes.put("timestamp", new Date());errorAttributes.put("status",500);errorAttributes.put("message","用户不存在异常");return errorAttributes;}@Overridepublic Throwable getError(WebRequest webRequest) {return null;}

  • 继承DefaultErrorAttributes的方法
public class MyErrorHandler extends DefaultErrorAttributes {public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {//调用父类方法,直接在默认错误信息的基础上添加Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);errorAttributes.put("timestamp", new Date());errorAttributes.put("message","用户不存在异常");return errorAttributes;}}

  • 将定义的错误信息器添加到容器中

    • 通过@Component组件直接将MyErrorHandler组件添加到容器中
    • 通过@Bean在配置类中将组件添加到容器中
@Beanpublic DefaultErrorAttributes getErrorHandler(){return new MyErrorHandler();}

  • 下面解决上一节中没有出现我们自定义的异常信息
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);errorAttributes.put("timestamp", new Date());errorAttributes.put("message","用户不存在异常");//指定从哪个作用域中取值webRequest.getAttribute("myMessage", RequestAttributes.SCOPE_REQUEST);return errorAttributes;

将在异常处理器中定义的错误信息取出,然后添加到错误信息中。

springboot返回modelandview 找不到视图_SpringBoot错误处理机制及原理相关推荐

  1. modelandview找不到视图_当一个测试工程师准备找工作,需要准备什么?

    三四月份,正是挺多公司开放招聘的时候,个人经历了一些大厂或小厂的面试也总结了一些信息,罗列一下权当抛砖引玉,希望能够稍微帮到一些准备或者正在找工作的朋友. 前期准备 关于投简历 需要意识到:一旦开始投 ...

  2. springboot返回404错误页面

    springboot返回404错误页面 1.HandlerInterceptor 拦截器的使用 实现HandlerInterceptor 接口,或者继承重写了HandlerInterceptor 接口 ...

  3. SpringMVC的数据响应-页面跳转-返回ModelAndView形式1(应用)

    在Controller中方法返回ModelAndView对象,并且设置视图名称 @RequestMapping(value="/quick2") public ModelAndVi ...

  4. 解决spring-boot-maven-plugin插件打包,springboot启动时报找不到主main问题

    解决spring-boot-maven-plugin插件打包,springboot启动时报找不到主main问题 参考文章: (1)解决spring-boot-maven-plugin插件打包,spri ...

  5. springboot返回date类型的数据会慢8个小时解决方案

    springboot返回date类型的数据会慢8个小时解决方案 解决方案 之前开发一个项目,返回的数据类型为date类,但是实际返回接口数据总会比实际时间慢8个小时,下面为实体类 @Data publ ...

  6. springboot启动 lombok 找不到符号

    springboot启动 lombok 找不到符号 找了好多方法都没有解决,发现自己使用的是java14,改为java8后正常运行.

  7. 启动SpringBoot项目时,报程序包不存在或者找不到符号的错误

    启动SpringBoot项目时,报程序包不存在或者找不到符号的错误 我们使用idea新建SpringBoot项目时有时候会遇到这样的错误: 当启动主启动类时控制台报错,如下所示: Error:(3, ...

  8. springboot返回html页面原理,SpringBoot返回html页面

    一般Controller返回数据或页面,今天谈一下返回页面的场景. 一.不使用template 1. controller中定义对应的访问路由及返回的页面(使用Controller,不要使用RestC ...

  9. boot返回码规范 spring_三种自定义SpringBoot返回的状态码

    关于如何自定义SpringBoot返回的状态码 关于HttpStatus 在SpringBoot中关于状态码有一个枚举类型,如下. public enum HttpStatus { CONTINUE( ...

最新文章

  1. git错误fatal: remote origin already exists.
  2. U3D assetbundle加载
  3. 【Python】一文掌握Conda软件安装:虚拟环境、软件通道、加速solving、跨服务器迁移...
  4. C语言实现djikstra算法(附完整源码)
  5. js jquery Ajax同步
  6. Linux中的存储设备管理
  7. 狗狗币暴涨暴跌?数据分析师带你走进它的前世今生!
  8. Docker+FastDFS+SpringBoot 快速搭建分布式文件服务器
  9. python自动化框架pytest pdf_Python自动化测试框架-pytest,python
  10. 没了 IDE,你的 Java 项目还能跑起来吗?
  11. 使用CrossOver安装第三方软件
  12. mysql db for python_Python使用MySQLdb for Python操作数据库教程
  13. 主流操作系统及其特点
  14. 什么是驱动程序签名,驱动程序如何获取数字签名?
  15. android 下载目录,android – FileProvider – 从下载目录中打开文件
  16. IRQL深入解析--IRQL级别
  17. 情感预测SHINE: Signed Heterogeneous Information Network Embedding for Sentiment Link Prediction引介
  18. 十二平均律的数学描述
  19. 超强的苹果官网滚动文字特效实现
  20. 在《王者荣耀》来聊聊游戏的帧同步

热门文章

  1. 拓扑排序(完整案列及C语言完整代码实现)
  2. 关于静态联编和动态联编
  3. telnet本机端口不通原因_【Academic】ssh端口转发实战复习 之 R
  4. spark的python开发安装方式_PyCharm搭建Spark开发环境的实现步骤
  5. 开发者们看过来,8ms开发工具平台给大家送福利了!只要你来,肯定有你感兴趣的,3.6-3.10日,只要在8ms平台上创建项目,就有机会白嫖彩屏开发板哦
  6. ESP32彩屏成为HMI这条GAI最靓的仔--8月27日启明云端携手乐鑫为你共述ESP32时下最IN进阶玩法--以简驭繁,AI语音、彩屏尽显锋芒
  7. 未来计算机的缺陷,新技术将计算机芯片缺陷变为优势
  8. 打响汽车信息安全战,百度Apollo构建最高等级安全防护盾牌
  9. mysql 线上加索引_MySQL加索引都经历了什么?
  10. 图解python专业教程_图解Python视频教程(基础篇)课程