前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot错误信息处理机制及原理

SpringBoot错误信息处理机制及原理

作者头像
石的三次方
发布2021-01-06 09:21:58
8460
发布2021-01-06 09:21:58
举报
文章被收录于专栏:石的三次方石的三次方

SpringBoot错误信息处理机制及原理

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

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

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

访问步骤:

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

源代码:

代码语言:javascript
复制
//错误信息的自动配置
public class ErrorMvcAutoConfiguration {
    //响应具体的错误信息
    @Bean
    public DefaultErrorAttributes errorAttributes() {
        return
    }
	//处理错误请求
    @Bean
    public BasicErrorController basicErrorController() {
        return 
    }
	//注册错误界面
    @Bean
    public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
        return 
    }

代码语言:javascript
复制
//注册错误界面,错误界面的路径为/error
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
    //服务器基本配置
    private final ServerProperties properties;
    
	public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
        //获取服务器配置中的错误路径/error
            ErrorPage 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";

代码语言:javascript
复制
//处理/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数据
    @RequestMapping
    public 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);
        }
    }

代码语言:javascript
复制
//添加错误信息
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处理请求
代码语言:javascript
复制
@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);
    }

代码语言:javascript
复制
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;
    }
}

代码语言:javascript
复制
public interface ErrorViewResolver {
    ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model);
}
  • 处理具体的视图跳转
代码语言:javascript
复制
//处理视图跳转
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请求的时候不适用浏览器默认请求

代码语言:javascript
复制
@RequestMapping
    public 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);
        }
    }

代码语言:javascript
复制
父类方法:
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
        WebRequest webRequest = new ServletWebRequest(request);
        //调用ErrorAttributes接口的getErrorAttributes方法,
        return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    }

代码语言:javascript
复制

添加错误信息
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)执行该方法需要处理什么异常,然后返回什么数据或者视图
代码语言:javascript
复制
	//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的值
代码语言:javascript
复制
@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添加默认的前缀
代码语言:javascript
复制
@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数据

代码语言:javascript
复制
@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自定义异常返回一个视图,拥有自适应效果
代码语言:javascript
复制
@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数据

代码语言:javascript
复制
{
    "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接口
代码语言:javascript
复制
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;
    }

    @Override
    public Throwable getError(WebRequest webRequest) {
        return null;
    }

  • 继承DefaultErrorAttributes的方法
代码语言:javascript
复制
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在配置类中将组件添加到容器中
代码语言:javascript
复制
@Bean
    public DefaultErrorAttributes getErrorHandler(){
        return new MyErrorHandler();
    }

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 石的三次方 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringBoot错误信息处理机制及原理
    • 1. SpringBoot处理错误请求的流程
      • 2. 响应一个视图
        • 3.响应一个json数据
          • 4.自定义异常返回自定义的异常数据
            • 4.1@ControllerAdvice注解
            • 4.2自定义异常JSON
            • 4.2自定义异常返回一个视图,拥有自适应效果
            • 4.3自定义错误信息
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档