进入Controller层的:
此部分可以借助Spring提供的全局异常处理机制来处理
以及进入Controller之前的:
如Filter中的异常,此部分异常无法到达Controller层,因此Spring提供的全局异常处理机制无法捕获。此部分处理有两种方式:网上大部分处理的方式为,在Filter中抛出异常的地方,重定向到指定的一个Controller层去,这样就可以借助Spring的全局异常处理器(@ControllerAdvice)来进行处理,此处不再赘述。主要谈的是第二种,即实现 ErrorController 接口的异常处理。
借助Spring提供的注解 @ControllerAdvice 就能很轻松地实现,此处不多说。直接上现成实例,处理部分自定义异常外,几乎可直接使用
- import lombok.extern.slf4j.Slf4j;
- import org.apache.shiro.ShiroException;
- import org.apache.shiro.authz.AuthorizationException;
- import org.apache.shiro.authz.UnauthorizedException;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.converter.HttpMessageNotReadableException;
- import org.springframework.validation.BindException;
- import org.springframework.validation.BindingResult;
- import org.springframework.validation.FieldError;
- import org.springframework.web.HttpRequestMethodNotSupportedException;
- import org.springframework.web.bind.MissingServletRequestParameterException;
- import org.springframework.web.bind.annotation.*;
- import org.springframework.web.method.HandlerMethod;
- import org.springframework.web.multipart.support.MissingServletRequestPartException;
- import org.springframework.web.servlet.ModelAndView;
- import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
-
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.PrintWriter;
- import java.io.StringWriter;
- import java.util.*;
-
- /**
- * 系统全局异常处理
- *
- */
- @Slf4j
- @ControllerAdvice
- public class GlobalExceptionHandler {
- public static final String DEFAULT_ERROR_CODE = "E";
- private static final String DEFAULT_ERROR_MSG = "业务繁忙,请稍后再试";
-
- // 自定义的校验注解
- private final static Set
CUSTOMER_VALID_ANNOTATION = new HashSet<>(); -
- /**
- * 参数校验异常处理
- *
- * 主要拦截 使用@Valid或@Validated注解对参数校验后的字段的统一处理
- * 通过{@linkplain BindingResult}拿到错误信息,错了完成统一返回
- *
- * @param ex
- * @return
- */
- @ExceptionHandler(BindException.class)
- @ResponseBody
- @ResponseStatus(value = HttpStatus.OK)
- public ResultVO> handleException(BindException ex) {
- log.error("请求参数错误", ex);
- BindingResult bindingResult = ex.getBindingResult();
- StringBuilder msg = new StringBuilder();
- for (FieldError fieldError : bindingResult.getFieldErrors()) {
- String annName = fieldError.getCode();
- if (!CUSTOMER_VALID_ANNOTATION.contains(annName)) {
- msg.append("[").append(fieldError.getField()).append("]");
- }
- msg.append(fieldError.getDefaultMessage()).append(" ");
- }
- return ResultVO.failed(ResultCode.P00002, msg.toString());
- }
-
- /**
- * 处理Servlet异常
- *
- * @param ex
- * @return
- */
- @ExceptionHandler(ServletException.class)
- @ResponseBody
- @ResponseStatus(value = HttpStatus.OK)
- public ResultVO> handleServletException(ServletException ex) {
- log.error("请求方式异常", ex);
- // 文件为空
- if (ex instanceof MissingServletRequestPartException) {
- MissingServletRequestPartException e = (MissingServletRequestPartException) ex;
- String message = String.format("[%s]参数不能为空", e.getRequestPartName());
- return ResultVO.failed(ResultCode.P00000, message);
- }
- // 请求方式异常
- if (ex instanceof HttpRequestMethodNotSupportedException) {
- HttpRequestMethodNotSupportedException e = (HttpRequestMethodNotSupportedException) ex;
- List
supportMethods = Arrays.asList(Optional.ofNullable(e.getSupportedMethods()).orElse(new String[0])); - String message = String.format("不支持[%s]请求方式,仅支持%s", e.getMethod(), supportMethods);
- return ResultVO.failed(ResultCode.B00000, message);
- }
-
- // 参数错误
- if (ex instanceof MissingServletRequestParameterException) {
- MissingServletRequestParameterException e = (MissingServletRequestParameterException) ex;
- String message = String.format("[%s{%s}]不能为空", e.getParameterName(), e.getParameterType());
- return ResultVO.failed(ResultCode.B00000, message);
- }
- return ResultVO.failed(ResultCode.E, DEFAULT_ERROR_MSG);
- }
-
- /**
- * shiro异常处理
- */
- @ExceptionHandler(ShiroException.class)
- @ResponseBody
- @ResponseStatus(value = HttpStatus.OK)
- public ResultVO> handleShiroException(ShiroException ex) {
- String message;
- if (ex instanceof UnauthorizedException) {
- message = "无权限操作";
- log.error("无权限操作 - {}", message, ex);
- } else if (ex instanceof AuthorizationException) {
- message = "无权限操作";
- log.error("权限认证异常 - {}", message, ex);
- } else {
- message = ex.getMessage();
- log.error("权限认证异常 - {}", message, ex);
- }
- return ResultVO.failed(ResultCode.U00004, message);
- }
-
- /**
- * 参数格式异常处理
- *
- * @param ex
- * @return
- */
- @ExceptionHandler(HttpMessageNotReadableException.class)
- @ResponseBody
- @ResponseStatus(value = HttpStatus.OK)
- public ResultVO> handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) {
- String message = ex.getMessage();
- log.error("参数格式错误:{}", message, ex);
- return ResultVO.failed(ResultCode.P00004);
- }
-
- /**
- * 参数格式异常处理
- *
- * @param ex
- * @return
- */
- @ExceptionHandler({IllegalStateException.class})
- @ResponseBody
- @ResponseStatus(value = HttpStatus.OK)
- public ResultVO> handleIllegalStateException(IllegalStateException ex) {
- Throwable cause;
- String message = null;
- if ((cause = ex.getCause()) != null) {
- message = cause.getMessage();
- }
- message = message == null ? ex.getMessage() : message;
- log.error("请求异常:{}", message, ex);
- return ResultVO.failed(ResultCode.B00000, message);
- }
-
- /**
- * 业务异常处理
- *
- * @param ex
- * @return
- */
- @ExceptionHandler({BusinessException.class, PlatformException.class})
- @ResponseBody
- @ResponseStatus(value = HttpStatus.OK)
- public ResultVO> handleHttpMessageNotReadableException(PlatformException ex) {
- String message = ex.getMessage();
- log.error("参数格式错误:{}", message, ex);
- return ResultVO.failed(ex.getResultCode(), message);
- }
-
-
- /**
- * 其他全局异常处理
- *
- * @param request
- * @param response
- * @param ex
- * @param handle
- * @return
- */
- @ExceptionHandler(Throwable.class)
- @ResponseStatus(value = HttpStatus.OK)
- public ModelAndView handleException(HttpServletRequest request, HttpServletResponse response, Throwable ex, HandlerMethod handle) {
- ModelAndView modelAndView;
- if (handle.getBean().getClass().isAnnotationPresent(RestController.class) || handle.hasMethodAnnotation(ResponseBody.class)) {
- modelAndView = new ModelAndView(new MappingJackson2JsonView());
- this.handleSpecialException(ex, handle, modelAndView);
- modelAndView.addObject("data", null);
- } else {
- modelAndView = new ModelAndView();
- modelAndView.setViewName("error/error");
- this.handleSpecialException(ex, handle, modelAndView);
- PrintWriter writer = new PrintWriter(new StringWriter());
- ex.printStackTrace(writer);
- }
- return modelAndView;
- }
-
- private void handleSpecialException(Throwable e, HandlerMethod handle, ModelAndView modelAndView) {
- log.error("全局异常", e);
- modelAndView.addObject("code", DEFAULT_ERROR_CODE);
- modelAndView.addObject("message", DEFAULT_ERROR_MSG);
- }
- }
此部分为重头戏,因为在网上很少找到资料,或者找到的都是只言片语,要么无法使用,要么不全面。
SPringBoot官方已提供了一种处理机制,参考 org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
,采用@Bean的方式注入:
SpringBoot官方实现示例
package org.springframework.boot.autoconfigure.web.servlet.error; @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) // Load before the main WebMvcAutoConfiguration so that the error View is available @AutoConfigureBefore(WebMvcAutoConfiguration.class) @EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class }) public class ErrorMvcAutoConfiguration { private final ServerProperties serverProperties; public ErrorMvcAutoConfiguration(ServerProperties serverProperties) { this.serverProperties = serverProperties; } @Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); } @Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvidererrorViewResolvers) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), errorViewResolvers.orderedStream().collect(Collectors.toList())); } //......
}
org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController,为什么采用这种方式,是因为要注入额外的3个属性:
- org.springframework.boot.web.servlet.error.ErrorAttributes errorAttributes,
- org.springframework.beans.factory.ObjectProvider
errorViewResolvers; - org.springframework.boot.autoconfigure.web.ServerProperties serverProperties
- //注入serverProperties的目的是为了获得ErrorProperties
- org.springframework.boot.autoconfigure.web.ErrorProperties errorProperties = serverProperties.getError()
这就是为什么不能直接使用 @Conponent 只能的注入了,因为 ErrorProperties 及 ErrorAttributes 无法使用有参构造或@Autowired注入。
说清楚了。参考官方的注入BasicErrorController的方式,注入我们自定义的Bean
第一步:定义继承自BasicErrorController的实现处理类(为了复用别人已经实现了的code,也可以实现实现接口 org.springframework.boot.web.servlet.error.ErrorController)
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.boot.autoconfigure.web.ErrorProperties;
- import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
- import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
- import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
- import org.springframework.boot.web.error.ErrorAttributeOptions;
- import org.springframework.boot.web.servlet.error.ErrorAttributes;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.MediaType;
- import org.springframework.http.ResponseEntity;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.context.request.ServletWebRequest;
- import org.springframework.web.context.request.WebRequest;
- import org.springframework.web.servlet.ModelAndView;
- import xin.cosmos.basic.define.ResultVO;
- import xin.cosmos.basic.exception.BusinessException;
- import xin.cosmos.basic.exception.PlatformException;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.util.HashMap;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
-
- /**
- * 过滤器/servlet异常处理
- *
- * 主要处理经过过滤器但尚未到达controller的异常
- *
- * {@linkplain GlobalExceptionHandler} 全局异常处理没法处理过虑器中抛出的异常
- * 和执行顺序有关:
- *
- * filter -> interceptor -> controllerAdvice -> aspect -> controller
- *
- * 当controller返回异常时,也会按照controller -> aspect -> controllerAdvice -> interceptor -> filter来依次抛出
- *
- * 注意:此方法不能直接在类上使用{@linkplain org.springframework.stereotype.Controller}
- *
- * 或 {@linkplain org.springframework.web.bind.annotation.RestController} 标注,
- *
- * 原因是{@linkplain ErrorProperties}和{@linkplain ErrorAttributes}无法注入。
- *
- * 采用如同SpringBoot注入{@linkplain BasicErrorController}的注入方式一样,采用{@linkplain org.springframework.context.annotation.Bean}的方式注入。
- *
- * 可参考{@linkplain ErrorMvcAutoConfiguration}
- *
- * 该类在{@linkplain xin.cosmos.basic.config.ServletErrorConfiguration}中注入spring容器
- */
- @Slf4j
- @RequestMapping("${server.error.path:${error.path:/error}}")
- public class ServletErrorHandler extends BasicErrorController {
- private static final String CODE_NAME = "code";
- private static final String MSG_NAME = "message";
- private static final String DATA_NAME = "data";
-
- public ServletErrorHandler(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List
errorViewResolvers) { - super(errorAttributes, errorProperties, errorViewResolvers);
- }
-
- @Override
- public ResponseEntity
- HttpStatus status = getStatus(request);
- if (status == HttpStatus.NO_CONTENT) {
- return new ResponseEntity<>(status);
- }
- WebRequest webRequest = new ServletWebRequest(request);
- final Map
body = getErrorAttributes(request, getAllErrorAttributeOptions()); - log.error("Request Path: {}, Servlet Error: {}", body.get("path"), body.get("trace"));
-
- // 异常错误处理
- ResultVO
- Map
errorMap = new LinkedHashMap<>(); - String exception = (String) body.get("exception");
- if (PlatformException.class.getTypeName().equals(exception) || BusinessException.class.getTypeName().equals(exception)) {
- errorMap.put(CODE_NAME, defaultError.getCode());
- errorMap.put(MSG_NAME, body.get("message"));
- errorMap.put(DATA_NAME, defaultError.getData());
- return new ResponseEntity<>(errorMap, HttpStatus.OK);
- }
- errorMap.put(CODE_NAME, defaultError.getCode());
- errorMap.put(MSG_NAME, defaultError.getMessage());
- errorMap.put(DATA_NAME, defaultError.getData());
- return new ResponseEntity<>(errorMap, HttpStatus.OK);
- }
-
- @Override
- public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
- HttpStatus status = getStatus(request);
- Map
model = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)); - Map
map = new HashMap<>(); - map.put("title", status.getReasonPhrase() + "page");
- map.put("code", model.get("status"));
- map.put("message", model.get("error"));
- response.setStatus(status.value());
- log.error("{}", model);
- return new ModelAndView("error/error", map, HttpStatus.OK);
- }
-
- ErrorAttributeOptions getAllErrorAttributeOptions() {
- return ErrorAttributeOptions.of(ErrorAttributeOptions.Include.EXCEPTION, ErrorAttributeOptions.Include.MESSAGE, ErrorAttributeOptions.Include.BINDING_ERRORS, ErrorAttributeOptions.Include.STACK_TRACE);
- }
- }
第二步:采用@Bean的方式将定义的类注入Spring容器(此处参考SpringBoot的实现方式,避免踩坑)
- import org.springframework.beans.factory.ObjectProvider;
- import org.springframework.boot.autoconfigure.AutoConfigureBefore;
- import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
- import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
- import org.springframework.boot.autoconfigure.web.ServerProperties;
- import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
- import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
- import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
- import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
- import org.springframework.boot.context.properties.EnableConfigurationProperties;
- import org.springframework.boot.web.servlet.error.ErrorAttributes;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.web.servlet.DispatcherServlet;
- import xin.cosmos.basic.handler.ServletErrorHandler;
-
- import javax.servlet.Servlet;
- import java.util.stream.Collectors;
-
- /**
- * 自定义Servlet异常处理配置类
- * 注入Bean的形式参考{@linkplain ErrorMvcAutoConfiguration}
- */
- @Configuration(proxyBeanMethods = false)
- @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
- @ConditionalOnClass({Servlet.class, DispatcherServlet.class})
- // 在主WebMvcAutoConfiguration之前加载,以便错误视图可用
- @AutoConfigureBefore(WebMvcAutoConfiguration.class)
- @EnableConfigurationProperties({ServerProperties.class, WebMvcProperties.class})
- public class ServletErrorConfiguration {
-
- private final ServerProperties serverProperties;
-
- public ServletErrorConfiguration(ServerProperties serverProperties) {
- this.serverProperties = serverProperties;
- }
-
- /**
- * Servlet自定义异常处理器
- * @param errorAttributes
- * @param errorViewResolvers
- * @return
- */
- @Bean
- public ServletErrorHandler servletFilterErrorController(
- ErrorAttributes errorAttributes,
- ObjectProvider
errorViewResolvers) { - return new ServletErrorHandler(errorAttributes,
- this.serverProperties.getError(),
- errorViewResolvers.orderedStream().collect(Collectors.toList()));
- }
- }
至此,已大功告成。如有不足之处,希望评论区留言。