1、springboot默认错误处理机制
spring boot默认异常处理机制用浏览器请求时返回错误页面,其他应用请求时返回json数据。如下
编写控制器
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
 * 测试
 *
 * @Author: Mr_li
 * @CreateDate: 2019-05-02 12:05
 * @Version: 1.0
 */
@Controller
public class TestController {
    @RequestMapping("/say")
    public String sayHi() {
        return "hi";
    }
}浏览器请求

postman请求

页面能获取的信息:
timestamp:时间戳 
status:状态码 
error:错误提示 
exception:异常对象 
message:异常消息 
errors:JSR303数据校验的错误都在这里2、springboot默认错误处理机制的实现
关键点在BasicErrorController这个类中

查看其实现
/*
 * Copyright 2012-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.boot.autoconfigure.web.servlet.error;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
 * Basic global error {@link Controller}, rendering {@link ErrorAttributes}. More specific
 * errors can be handled either using Spring MVC abstractions (e.g.
 * {@code @ExceptionHandler}) or by adding servlet
 * {@link AbstractServletWebServerFactory#setErrorPages server error pages}.
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @author Michael Stummvoll
 * @author Stephane Nicoll
 * @see ErrorAttributes
 * @see ErrorProperties
 */
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
  private final ErrorProperties errorProperties;
  /**
   * Create a new {@link BasicErrorController} instance.
   * @param errorAttributes the error attributes
   * @param errorProperties configuration properties
   */
  public BasicErrorController(ErrorAttributes errorAttributes,
      ErrorProperties errorProperties) {
    this(errorAttributes, errorProperties, Collections.emptyList());
  }
  /**
   * Create a new {@link BasicErrorController} instance.
   * @param errorAttributes the error attributes
   * @param errorProperties configuration properties
   * @param errorViewResolvers error view resolvers
   */
  public BasicErrorController(ErrorAttributes errorAttributes,
      ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
    super(errorAttributes, errorViewResolvers);
    Assert.notNull(errorProperties, "ErrorProperties must not be null");
    this.errorProperties = errorProperties;
  }
  @Override
  public String getErrorPath() {
    return this.errorProperties.getPath();
  }
        /**请求头为MediaType.TEXT_HTML_VALUE的执行这个返回页面*/
  @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
  public ModelAndView errorHtml(HttpServletRequest request,
      HttpServletResponse response) {
    HttpStatus status = getStatus(request);
    Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
        request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
    response.setStatus(status.value());
    ModelAndView modelAndView = resolveErrorView(request, response, status, model);
    return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
  }
        /**其他请求头返回obj*/
  @RequestMapping
  public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    Map<String, Object> body = getErrorAttributes(request,
        isIncludeStackTrace(request, MediaType.ALL));
    HttpStatus status = getStatus(request);
    return new ResponseEntity<>(body, status);
  }
  /**
   * Determine if the stacktrace attribute should be included.
   * @param request the source request
   * @param produces the media type produced (or {@code MediaType.ALL})
   * @return if the stacktrace attribute should be included
   */
  protected boolean isIncludeStackTrace(HttpServletRequest request,
      MediaType produces) {
    IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
    if (include == IncludeStacktrace.ALWAYS) {
      return true;
    }
    if (include == IncludeStacktrace.ON_TRACE_PARAM) {
      return getTraceParameter(request);
    }
    return false;
  }
  /**
   * Provide access to the error properties.
   * @return the error properties
   */
  protected ErrorProperties getErrorProperties() {
    return this.errorProperties;
  }
}发现它本身就是一个控制器,这个类是默认处理/error请求的。那么响应页面的时候是怎么找到页面的呢?
这里有一个关键类 DefaultErrorViewResolver

/*
 * Copyright 2012-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.boot.autoconfigure.web.servlet.error;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
/**
 * Default {@link ErrorViewResolver} implementation that attempts to resolve error views
 * using well known conventions. Will search for templates and static assets under
 * {@code '/error'} using the {@link HttpStatus status code} and the
 * {@link HttpStatus#series() status series}.
 * <p>
 * For example, an {@code HTTP 404} will search (in the specific order):
 * <ul>
 * <li>{@code '/<templates>/error/404.<ext>'}</li>
 * <li>{@code '/<static>/error/404.html'}</li>
 * <li>{@code '/<templates>/error/4xx.<ext>'}</li>
 * <li>{@code '/<static>/error/4xx.html'}</li>
 * </ul>
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @since 1.4.0
 */
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
  private static final Map<Series, String> SERIES_VIEWS;
  static {
    Map<Series, String> views = new EnumMap<>(Series.class);
    views.put(Series.CLIENT_ERROR, "4xx");
    views.put(Series.SERVER_ERROR, "5xx");
    SERIES_VIEWS = Collections.unmodifiableMap(views);
  }
  private ApplicationContext applicationContext;
  private final ResourceProperties resourceProperties;
  private final TemplateAvailabilityProviders templateAvailabilityProviders;
  private int order = Ordered.LOWEST_PRECEDENCE;
  /**
   * Create a new {@link DefaultErrorViewResolver} instance.
   * @param applicationContext the source application context
   * @param resourceProperties resource properties
   */
  public DefaultErrorViewResolver(ApplicationContext applicationContext,
      ResourceProperties resourceProperties) {
    Assert.notNull(applicationContext, "ApplicationContext must not be null");
    Assert.notNull(resourceProperties, "ResourceProperties must not be null");
    this.applicationContext = applicationContext;
    this.resourceProperties = resourceProperties;
    this.templateAvailabilityProviders = new TemplateAvailabilityProviders(
        applicationContext);
  }
  DefaultErrorViewResolver(ApplicationContext applicationContext,
      ResourceProperties resourceProperties,
      TemplateAvailabilityProviders templateAvailabilityProviders) {
    Assert.notNull(applicationContext, "ApplicationContext must not be null");
    Assert.notNull(resourceProperties, "ResourceProperties must not be null");
    this.applicationContext = applicationContext;
    this.resourceProperties = resourceProperties;
    this.templateAvailabilityProviders = templateAvailabilityProviders;
  }
  @Override
  public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
      Map<String, Object> model) {
    ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
    if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
      modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
    }
    return modelAndView;
  }
  private ModelAndView resolve(String viewName, Map<String, Object> model) {
                //默认去error下找且viewName作为视图名
    String errorViewName = "error/" + viewName;
    TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
        .getProvider(errorViewName, this.applicationContext);
    if (provider != null) {
                        //模板引擎可用的时候返回配置的,默认为error/状态码.html。
      return new ModelAndView(errorViewName, model);
    }
                //返回默认的errorViewName下的
    return resolveResource(errorViewName, model);
  }
  private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
    for (String location : this.resourceProperties.getStaticLocations()) {
      try {
        Resource resource = this.applicationContext.getResource(location);
        resource = resource.createRelative(viewName + ".html");
        if (resource.exists()) {
          return new ModelAndView(new HtmlResourceView(resource), model);
        }
      }
      catch (Exception ex) {
      }
    }
    return null;
  }
  @Override
  public int getOrder() {
    return this.order;
  }
  public void setOrder(int order) {
    this.order = order;
  }
  /**
   * {@link View} backed by an HTML resource.
   */
  private static class HtmlResourceView implements View {
    private Resource resource;
    HtmlResourceView(Resource resource) {
      this.resource = resource;
    }
    @Override
    public String getContentType() {
      return MediaType.TEXT_HTML_VALUE;
    }
    @Override
    public void render(Map<String, ?> model, HttpServletRequest request,
        HttpServletResponse response) throws Exception {
      response.setContentType(getContentType());
      FileCopyUtils.copy(this.resource.getInputStream(),
          response.getOutputStream());
    }
  }
}一但系统出现4xx或者5xx之类的错误;
ErrorPageCustomizer就会生效(定制错误的响应规则);
就会来到/error请求;
就会被BasicErrorController处理。
3、异常页面查找
创建一下目录

页面定义以及查找规则(默认)
如上图定义一个404的异常界面。访问一个不存在的页面(触发404)。查找顺序如下
1)有模板引擎的情况下;templates/error/状态码

将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的error文件夹下,发生此状态码的错误就会来到 对应的页面。
2)没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找(也就是static文件夹); static/error/状态码

3)以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面
4、全局自定义异常处理
import com.example.demo.common.UserNotExistException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.HashMap;
import java.util.Map;
/**
 * 全局异常捕获
 *
 * @Author: Mr_li
 * @CreateDate: 2019-05-02 15:07
 * @Version: 1.0
 */
@ControllerAdvice
public class MyExceptionHandler {
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public Map<String, Object> handleException(Exception e) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", "not_exist");
        map.put("message", e.getMessage());
        return map;
    }
}@ControllerAdvice是controller的一个辅助类,最常用的就是作为全局异常处理的切面类
@ControllerAdvice可以指定扫描范围
@ControllerAdvice约定了几种可行的返回值,如果是直接返回model类的话,需要使用@ResponseBody进行json转换
返回String,表示跳到某个view
返回modelAndView
返回model + @ResponseBody (不加@ResponseBody)会抛出异常(would dispatch back to the current handler URL [/test1] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
创建自定义异常
import lombok.Data;
/**
 * 自定义异常
 *
 * @Author: Mr_li
 * @CreateDate: 2019-05-02 13:33
 * @Version: 1.0
 */
@Data
public class UserNotExistException extends RuntimeException {
    private String message;
    public UserNotExistException(String message) {
        super(message);
        this.message = message;
    }
}创建控制器
import com.example.demo.common.UserNotExistException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.HashMap;
import java.util.Map;
/**
 * 测试
 *
 * @Author: Mr_li
 * @CreateDate: 2019-05-02 12:05
 * @Version: 1.0
 */
@Controller
public class TestController {
    @RequestMapping(value = "/test1")
    public void testException() {
        throw new UserNotExistException("用户不存在");
    }
}执行结果










