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. 【转】安卓开发经验分享:资源、UI、函数库、测试、构建一个都不能少

    本文由 ImportNew - 唐尤华 翻译自 gigavoice.如需转载本文,请先参见文章末尾处的转载要求. 除了高超的武艺,每位黑忍者还需要装备最好的武器.在软件开发的世界里,好的工具能让我们的 ...

  2. python打印图形

    i = 0 while i < 5: # print('*****') 效果与下行相同 print('*'*5) i+=1 print('\n\n') i = 1 while i < 6: ...

  3. 替代not in 和 in 的办法

    在程序中,我们经常会习惯性的使用in和not in,在访问量比较小的时候是可以的,但是一旦数据量大了,我们就推荐使用not exists或者外连接来代替了.如果要实现一张表有而另外一张表没有的数据时, ...

  4. python 线程事件

    与进程的事件相似 # 事件,模拟连接数据库 import time from threading import Event, Thread def wait(e): while 1: e.wait(1 ...

  5. rhel

    1.查看硬盘大小 df -h 2.查看内存大小 free -h 3.配置主键名称 vim /etc/hostname# 查看 hostnamehostname 4.挂载镜像 mkdir -p /med ...

  6. Egret学习-初次创建项目

    最近无聊,好久没有写游戏了,决定学习下egret,主要原因:egret是h5框架,相比android和iPhone或cocos2dx来说不需要安装可以直接运行. 下面进入正题,开始学习egret 简单 ...

  7. @ControllerAdvice实现优雅地处理异常

    @ControllerAdvice,是Spring3.2提供的新注解,它是一个Controller增强器,可对controller中被 @RequestMapping注解的方法加一些逻辑处理.最常用的 ...

  8. 编译游戏库allegro

    一个allegro依赖了大概十个库,还得自己一个个的去编译,然后复制粘贴 主要从两个网页学到的 第一个网页里有绝大多数的依赖库的编译方法 http://wiki.allegro.cc/index.ph ...

  9. 快速构建第三方api应用

    1.使用框架和扩展 详细请看composer.json "php": "^7.1.3", "laravel-admin-ext/config" ...

  10. Centos 7 下部署集群式阿波罗

    apollo工作原理 用户通过浏览器登录Portal管理界面 >> 通过Admin server对配置进行修改 >> 应用程序主动向config server配置注意:Port ...