SpringBoot错误信息处理机制

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

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

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

访问步骤:

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

源代码

  1. //错误信息的自动配置
  2. public class ErrorMvcAutoConfiguration {
  3. //响应具体的错误信息
  4. @Bean
  5. public DefaultErrorAttributes errorAttributes() {
  6. return
  7. }
  8. //处理错误请求
  9. @Bean
  10. public BasicErrorController basicErrorController() {
  11. return
  12. }
  13. //注册错误界面
  14. @Bean
  15. public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
  16. return
  17. }
  1. //注册错误界面,错误界面的路径为/error
  2. private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
  3. //服务器基本配置
  4. private final ServerProperties properties;
  5. public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
  6. //获取服务器配置中的错误路径/error
  7. ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
  8. //注册错误界面
  9. errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
  10. }
  11. //this.properties.getError()
  12. public class ServerProperties{
  13. //错误信息的配置文件
  14. private final ErrorProperties error = new ErrorProperties();
  15. }
  16. //getPath
  17. public class ErrorProperties {
  18. @Value("${error.path:/error}")
  19. private String path = "/error";
  1. //处理/error请求,从配置文件中取出请求的路径
  2. @RequestMapping({"${server.error.path:${error.path:/error}}"})
  3. public class BasicErrorController extends AbstractErrorController {
  4. //浏览器行为,通过请求头来判断,浏览器返回一个视图
  5. @RequestMapping(
  6. produces = {"text/html"}
  7. public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  8. HttpStatus status = this.getStatus(request);
  9. Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  10. response.setStatus(status.value());
  11. ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
  12. return modelAndView != null ? modelAndView : new ModelAndView("error", model);
  13. }
  14. //其他客户端行为处理,返回一个JSON数据
  15. @RequestMapping
  16. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  17. HttpStatus status = this.getStatus(request);
  18. if (status == HttpStatus.NO_CONTENT) {
  19. return new ResponseEntity(status);
  20. } else {
  21. Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
  22. return new ResponseEntity(body, status);
  23. }
  24. }
  1. //添加错误信息
  2. public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
  3. public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
  4. Map<String, Object> errorAttributes = new LinkedHashMap();
  5. errorAttributes.put("timestamp", new Date());
  6. this.addStatus(errorAttributes, webRequest);
  7. this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
  8. this.addPath(errorAttributes, webRequest);
  9. return errorAttributes;
  10. }

2. 响应一个视图

步骤:

  • 客户端出现错误
  • SpringBoot创建错误请求/error
  • BasicErrorController处理请求
  1. @RequestMapping(
  2. produces = {"text/html"}
  3. )
  4. public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  5. HttpStatus status = this.getStatus(request);
  6. Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  7. response.setStatus(status.value());
  8. //解析错误界面,返回一个ModelAndView,调用父类AbstractErrorController的方法
  9. ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
  10. return modelAndView != null ? modelAndView : new ModelAndView("error", model);
  11. }
  1. public abstract class AbstractErrorController{
  2. private final List<ErrorViewResolver> errorViewResolvers;
  3. protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
  4. Iterator var5 = this.errorViewResolvers.iterator();
  5. //遍历所有的错误视图解析器
  6. ModelAndView modelAndView;
  7. do {
  8. if (!var5.hasNext()) {
  9. return null;
  10. }
  11. ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
  12. //调用视图解析器的方法,
  13. modelAndView = resolver.resolveErrorView(request, status, model);
  14. } while(modelAndView == null);
  15. return modelAndView;
  16. }
  17. }
  1. public interface ErrorViewResolver {
  2. ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model);
  3. }
  • 处理具体的视图跳转
  1. //处理视图跳转
  2. public DefaultErrorViewResolver{
  3. public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
  4. //将状态码作为视图名称传入解析
  5. ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
  6. if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
  7. modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
  8. }
  9. return modelAndView;
  10. }
  11. //视图名称为error文件夹下的400.html等状态码文件
  12. private ModelAndView resolve(String viewName, Map<String, Object> model) {
  13. String errorViewName = "error/" + viewName;
  14. //是否存在模板引擎进行解析
  15. TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
  16. //存在则返回解析以后的数据,不存在调用resolveResource方法进行解析
  17. return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
  18. }
  19. //如果静态资源文件中存在,返回静态文件下的,如果不存在返回SpringBoot默认的
  20. private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
  21. String[] var3 = this.resourceProperties.getStaticLocations();
  22. int var4 = var3.length;
  23. for(int var5 = 0; var5 < var4; ++var5) {
  24. String location = var3[var5];
  25. try {
  26. Resource resource = this.applicationContext.getResource(location);
  27. resource = resource.createRelative(viewName + ".html");
  28. if (resource.exists()) {
  29. return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
  30. }
  31. } catch (Exception var8) {
  32. }
  33. }
  34. return null;
  35. }

应用:

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

3.响应一个json数据

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

  1. @RequestMapping
  2. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  3. HttpStatus status = this.getStatus(request);
  4. if (status == HttpStatus.NO_CONTENT) {
  5. return new ResponseEntity(status);
  6. } else {
  7. //调用父类的方法获取所有的错误属性
  8. Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
  9. return new ResponseEntity(body, status);
  10. }
  11. }
  1. 父类方法:
  2. protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
  3. WebRequest webRequest = new ServletWebRequest(request);
  4. //调用ErrorAttributes接口的getErrorAttributes方法,
  5. return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
  6. }
  1. 添加错误信息
  2. public class DefaultErrorAttributes{
  3. public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
  4. Map<String, Object> errorAttributes = new LinkedHashMap();
  5. errorAttributes.put("timestamp", new Date());
  6. this.addStatus(errorAttributes, webRequest);
  7. this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
  8. this.addPath(errorAttributes, webRequest);
  9. return errorAttributes;
  10. }

返回的json数据有:

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

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

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

4.1@ControllerAdvice注解

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

@ControllerAdivice定义全局的异常处理

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

@ControllerAdvice定义全局数据

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

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

  • Controller的参数中添加ModelAttribute作为属性赋值的前缀
  • ControllerAdvice修饰的类中,结合InitBinder来绑定对应的属性(该属性为ModelAttribite的value值
  • @InitBinder修饰的方法中通过WebDataBinder添加默认的前缀
  1. @Getter@Setter
  2. public class Book {
  3. private String name ;
  4. private int age ;
  5. @Getter@Setter
  6. public class Music {
  7. private String name ;
  8. private String author ;
  9. //这种方式的处理,spring无法判断Name属性给哪个bean赋值,所以需要通过别名的方式来进行赋值
  10. @PostMapping("book")
  11. public String book(Book book , Music music){
  12. System.out.println(book);
  13. System.out.println(music);
  14. return "404" ;
  15. }
  16. //使用以下的方式
  17. @PostMapping("/book")
  18. public String book(@ModelAttribute("b")Book book , @ModelAttribute("m")Music music){
  19. System.out.println(book);
  20. System.out.println(music);
  21. return "404" ;
  22. }
  23. public MyCOnfiguration{
  24. @InitBinder("b")
  25. public void b(WebDataBinder webDataBinder){
  26. webDataBinder.setFieldDefaultPrefix("b.");
  27. }
  28. @InitBinder("m")
  29. public void m(WebDataBinder webDataBinder){
  30. webDataBinder.setFieldDefaultPrefix("m.");
  31. }
  32. }

4.2自定义异常JSON

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

  1. @ControllerAdvice
  2. public class MyExceptionHandler {
  3. //处理UserException异常
  4. @ResponseBody
  5. @ExceptionHandler(UserException.class)
  6. public Map<String,String> userExceptionMethod(UserException us){
  7. Map<String,String> map = new HashMap<>();
  8. map.put("message",us.getMessage());
  9. map.put("status","500");
  10. return map ;
  11. }

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

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

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

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

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

4.3自定义错误信息

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

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

  • 实现ErrorAttributes接口
  1. public class MyErrorHandler implements ErrorAttributes {
  2. public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
  3. Map<String, Object> errorAttributes = new LinkedHashMap();
  4. errorAttributes.put("timestamp", new Date());
  5. errorAttributes.put("status",500);
  6. errorAttributes.put("message","用户不存在异常");
  7. return errorAttributes;
  8. }
  9. @Override
  10. public Throwable getError(WebRequest webRequest) {
  11. return null;
  12. }
  • 继承DefaultErrorAttributes的方法
  1. public class MyErrorHandler extends DefaultErrorAttributes {
  2. public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
  3. //调用父类方法,直接在默认错误信息的基础上添加
  4. Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);
  5. errorAttributes.put("timestamp", new Date());
  6. errorAttributes.put("message","用户不存在异常");
  7. return errorAttributes;
  8. }
  9. }
  • 将定义的错误信息器添加到容器中

    • 通过@Component组件直接将MyErrorHandler组件添加到容器中
    • 通过@Bean在配置类中将组件添加到容器中
  1. @Bean
  2. public DefaultErrorAttributes getErrorHandler(){
  3. return new MyErrorHandler();
  4. }
  • 下面解决上一节中没有出现我们自定义的异常信息
  1. Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest,includeStackTrace);
  2. errorAttributes.put("timestamp", new Date());
  3. errorAttributes.put("message","用户不存在异常");
  4. //指定从哪个作用域中取值
  5. webRequest.getAttribute("myMessage", RequestAttributes.SCOPE_REQUEST);
  6. return errorAttributes;

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

SpingBoot错误信息处理及原理的更多相关文章

  1. ubuontu16.04安装Opencv库引发的find_package()错误信息处理及其简单使用

    在安装完Opencv库之后,打算测试一下Opencv库是否成功安装.下面是用的例子对应的.cpp代码以及对应的CMakeLists.txt代码: .cpp文件: #include <stdio. ...

  2. ThinkPHP错误信息处理

    index.php入口文件中打开APP_DEBUG// 开启调试模式define('APP_DEBUG', TRUE); // 开启Trace信息 'SHOW_PAGE_TRACE' =>tru ...

  3. PHP开发环境正确的错误信息处理

    正确记录配置 php.ini display_errors = On error_reporting = E_ALL log_errors = On error_log = F:/data/php/e ...

  4. [译]Javascript中的错误信息处理(Error handling)

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

  5. centos mysql错误信息处理

    mysql_secure_installation 提示错误:Enter current password for root (enter for none):ERROR 1045 (28000): ...

  6. 详解EBS接口开发之供应商导入(补充)--错误信息处理

    check reject details on records of AP_SUPPLIER_INT SELECT s.parent_table,s.reject_lookup_code,S.LAST ...

  7. HTTP 错误 500.23 - Internal Server Error 检测到在集成的托管管道模式下不适用的 ASP.NET 设置。

    检测到在集成的托管管道模式下不适用的ASP.NET设置的解决方法(非简单设置为[经典]模式). - CatcherX 2014-03-11 11:03 27628人阅读 评论(2) 收藏 举报  分类 ...

  8. php部分---文件上传:错误处理、 客户端和服务器端的限制

    1.客户端页面 <!---客户端的配置 1.表单页面 2.表单发送方式为post 3.表单form中添加enctype="multipart/form-data" ----- ...

  9. WDCP LNMPA和LNMP 504 Gateway time-out错误的解决方法

    Nginx的特点是处理静态很给力,Apache的特点是处理动态很稳定,两者结合起来便是LNMPA,nginx处理前端,apache处理后端,这样处理静态会很快,处理动态会很稳定.当我以为安装完成以后便 ...

随机推荐

  1. 【转】C#虚方法virtual详解

    转:https://www.cnblogs.com/zhaoshujie/p/10502404.html 在C++.Java等众多OOP语言里都可以看到virtual的身影,而C#作为一个完全面向对象 ...

  2. creator 2.0版本对于preloadScene函数获取加载进度

    有时候,当我们场景上挂载的资源过多时,我们使用cc.director.loadScene切换场景时会等一段时间才会切换过去,这对游戏的体验是相当不好的.所以我们可以使用cc.director.prel ...

  3. String字符串,输入一串字符判断其中数字,字母,其他的字符的个数

    public class StringClassTest { public static void main(String[] args) { //遍历字符串 String str = "H ...

  4. CTO说|非容器化应用怎么玩多云?Kubernetes不管我们管啊

    Kubernetes已经成为容器编排系统的事实标准,是现在主流的跨云容器化应用操作系统. 但是,Kubernetes的目标并不是容器本身,而是承载其上的应用,本质上是为了解决(容器化)应用上云这个难题 ...

  5. 源码分析Kafka 消息拉取流程

    目录 1.KafkaConsumer poll 详解 2.Fetcher 类详解 本节重点讨论 Kafka 的消息拉起流程. @(本节目录) 1.KafkaConsumer poll 详解 消息拉起主 ...

  6. Akka Java 中文文档

    Akka Java 中文文档 Introduction What is Akka? | 什么是Akka? Why Akka? | 为什么选择Akka? Getting Started | Akka入门 ...

  7. SVN打patch,某Java文件提示svn:mime-type = application/octet-stream的问题

    在使用SVN合版本时发现某文件有冲突,正常冲突文件是可以编辑修改的,但是该文件无法编辑,我只好选择后续修改选项,问题好诡异啊!!!在解决完其他冲突后,我选择了在eclipse开发工具内将修改的代码调整 ...

  8. JSON解析值富文本

    解析前端传递的JSON数据中可能如下 { "result": "<input value="Test" type="button&qu ...

  9. 【WPF学习】第十章 WPF布局示例

    前几章用了相当大的篇幅研究有关WPF布局容器的复杂内容.在掌握了这些基础知识后,就可以研究几个完整的布局示例.通过研究完整的布局示例,可更好的理解各种WPF布局概念在实际窗口中的工作方式. 一.列设置 ...

  10. 【WPF学习】第十三章 理解路由事件

    每个.NET开发人员都熟悉“事件”的思想——当有意义的事情发生时,由对象(如WPF元素)发送的用于通知代码的消息.WPF通过事件路由(event routing)的概念增强了.NET事件模型.事件路由 ...