SpringBoot中的异常捕获

SpringBoot中的异常捕获

1. 返回一个默认的错误页面

在这里插入图片描述

2. 使用其他工具访问(PostMan)

在这里插入图片描述

3. 浏览器请求头

在这里插入图片描述

4. 原理:

可以参照ErrorMvcAutoConfiguration;SpringBoot的默认自动配置;

给容器添加了以下重要组件
​ 1. DefaultErrorAttributes

// 帮我们共享页面信息
@Override
@Deprecated
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
    Map<String, Object> errorAttributes = new LinkedHashMap<>();
    errorAttributes.put("timestamp", new Date());
    addStatus(errorAttributes, webRequest);
    addErrorDetails(errorAttributes, webRequest, includeStackTrace);
    addPath(errorAttributes, webRequest);
    return errorAttributes;
}

​ 2. BasicErrorController ;处理默认的/erroe请求

@Controller 
// 默认取server.error中的值,没有的话就取error.path中的值没有的话就默认是/error
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
    
    // TEXT_HTML_VALUE = text/html,产生html的数据
    // 就是通过浏览器发送的请求头来区分
    @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, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
        // 去哪个页面作为错误页面,包含页面地址的页面内容
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}

	@RequestMapping // 产生json数据
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		HttpStatus status = getStatus(request);
		if (status == HttpStatus.NO_CONTENT) {
			return new ResponseEntity<>(status);
		}
		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
	}
}

​ 3. ErrorPageCustomizer

@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
	ErrorPage errorPage = new ErrorPage(	  
        this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
 	errorPageRegistry.addErrorPages(errorPage);
}

/**
 * Path of the error controller.
 */
@Value("${error.path:/error}")
private String path = "/error"; // 系统出现错误发后来到error请求进行处理;(web.xml中注册的错误页面的规则)

​ 4. DefaultErrorViewResolver

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);
	}
    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, 
                                         Map<String, Object> model) {
        // 获取具体返回到页面的ModelAndView
        ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
        // 如果没有找到具体的ModelAndView,并且在SERIES_VIEWS中包含所返回的4开头或5开头的状态码就去取默认的4xx或5xx的页面
        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) {
        // 默认SpringBoot会去找一个页面;errer/404.html
        String errorViewName = "error/" + viewName;
        // 模板引擎可以解析到这个页面就使用模板引擎进行解析
        TemplateAvailabilityProvider provider =
            this.templateAvailabilityProviders.getProvider(errorViewName,this.applicationContext);
        if (provider != null) {
            // 模板引擎可用的情况下直接返回ModelAndView指定的视图地址
            return new ModelAndView(errorViewName, model);
        }
        // 模板引擎不可用时。就到静态资源文件夹下去找ErrorViewName对应的页面errer/404.html
        return resolveResource(errorViewName, model);
    }
    private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
        // 循环静态资源下的文件,如果存在这个页岩就进行返回,没有则返回null
        // classpath:/META-INF/resources/
        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;
    }
}

​ 5. 步骤:

​ 1. 一但系统出现4xx或5xx之类的错误;ErrorPageCustomizer就会生效(定制错误页面的响应规则);就会来到/error请求;就会被BasicErrorController进行处理

​ 2. 响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的

protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,Map<String, Object> model) {
    // 通过所有的ErrorViewResolver得到所有的ModelAndView
    for (ErrorViewResolver resolver : this.errorViewResolvers) {
        ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
        if (modelAndView != null) {
            return modelAndView;
        }
    }
    return null;
}
  1. 如何定制错误页面

    1. **在有模板引擎的情况下;**error/状态码;【将错误页面命名为 错误状态码.html 放在模板引擎文件夹下的error文件夹】,发生此状态码的错误就会来到对应的页面;

    如果没有精确匹配的错误码页面,我们还可以使用4xx和5xx来定义以4开头的和以5开头的状态码来匹配;精确匹配优先;(优先寻找精确状态码的html)

    页面可以获取的信息:

    • timestamp:当前时间

    • status:状态码

    • exception:异常类类名

    • message:异常消息

    • errors:JSR3.3数据校验的错误都在这

    • path:访问的路径

    1. 没有模板引擎的情况下(就是模板引擎下找不到这个error页面),去静态资源下找;

    2. 以上都没有error页面那就来到SprignBoot的默认错误页面;

  2. 可体定制错误json数据

    1. 自定义异常处理类&返回json数据,没有自适应效果,只能返回json,对前后端分离友好;
// 声明是异常处理的控制类,@RestControllerAdvice1包含了@ControllerAdvice和@ResponseBody
@ControllerAdvice
public class CustomExceptionResolver {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomExceptionResolver.class);

    // 声明要捕捉的异常类
    @ExceptionHandler(value = {Exception.class})
    // 返回json数据
    @ResponseBody
    public Object handle(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        BaseException customException = null;
        if (ex instanceof NoHandlerFoundException) {
            customException = new BaseException(BaseCodeEnum.NOT_EXIST.getKey(), BaseCodeEnum.NOT_EXIST.getValue());
        } else if (ex instanceof HttpRequestMethodNotSupportedException) {
            customException = new BaseException(BaseCodeEnum.PARAMETER_RRROR.getKey(), "不支持此HttpMethod");
        } else if (ex instanceof TypeMismatchException || ex instanceof MissingServletRequestParameterException) {
            customException = new BaseException(BaseCodeEnum.PARAMETER_RRROR.getKey(), BaseCodeEnum.PARAMETER_RRROR.getValue());
        } else if(ex instanceof MethodArgumentNotValidException){
            customException = new BaseException(BaseCodeEnum.PARAMETER_RRROR.getKey(), "参数格式验证失败");
        } else if (ex instanceof BaseException) {
            customException = (BaseException) ex;
        } else {
            //未定义异常返回特定异常
            customException = new BaseException(BaseCodeEnum.FAIL.getKey(), "服务器异常,请稍后再试");
        }
       
        LOGGER.error("CustomExceptionResolver:{}", StringUtils.getEx(ex));
        return Response.error(customException.getCode(), customException.getMessage());
    }
}
/**
 * AbstractErrorController类中获取错误状态码
 */
String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);

使用Spring的方式去捕捉异常

  1. 创建全局异常处理类
// 此注解是实现全局异常处理
@ControllerAdvice
//网页code码比如404会在tomcat跳出,如果想在程序捕获提高程序权限
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomExceptionResolver {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomExceptionResolver.class);
	// 指定你要捕捉的异常类型
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Object handle(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        BaseException customException = null;
        if (ex instanceof NoHandlerFoundException) {
            customException = new BaseException(BaseCodeEnum.NOT_EXIST.getKey(), BaseCodeEnum.NOT_EXIST.getValue());
        } else if (ex instanceof HttpRequestMethodNotSupportedException) {
            customException = new BaseException(BaseCodeEnum.PARAMETER_RRROR.getKey(), "不支持此HttpMethod");
        } else if (ex instanceof TypeMismatchException || ex instanceof MissingServletRequestParameterException) {
            customException = new BaseException(BaseCodeEnum.PARAMETER_RRROR.getKey(), BaseCodeEnum.PARAMETER_RRROR.getValue());
        } else if (ex instanceof BaseException) {
            customException = (BaseException) ex;
        } else {
            //未定义异常返回特定异常
            customException = new BaseException(BaseCodeEnum.FAIL.getKey(), "服务器异常,请稍后再试");
        }
        // 打印异常信息
        LOGGER.error("CustomExceptionResolver:{}", StringUtils.getEx(ex));
        // 返回页面JSON串
        return Response.error(customException.getCode(), customException.getMessage(), RequestIDUtils.getRequestID());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值