Spring Boot全局异常处理,包括404等异常

2023-05-16

目录

全局异常处理实现方式

1.ErrorController方式

1.1.实现ErrorController接口

1.2.继承BasicErrorController类

2.@ControllerAdvice加上@ExceptionHandler方式

2.1.简单实现:

2.2.继承ResponseEntityExceptionHandler

2.3.实现ResponseBodyAdvice接口

3.HandlerExceptionResolver

3.1.ExceptionHandlerExceptionResolver

3.2.DefaultHandlerExceptionResolver

3.3.ResponseStatusExceptionResolver

3.4.自定义异常解析器

4.ResponseStatusException

注意:

附录:


Spring、Spring MVC、Spring Boot的全局异常处理/统一异常处理方式有很多种。

Spring Boot默认展示的是Whitelabel Error Page页面

全局异常处理实现方式

1.ErrorController方式

这种方式可以处理所有的异常信息,包括第2种方式捕获不到的400、401等等,可以拦截跳转到/error的异常,见另一篇博文Spring Boot项目跳转到/error接口_lzhfdxhxm的博客-CSDN博客

1.1.实现ErrorController接口

1.2.继承BasicErrorController类

@Controller
public class MyErrorController extends BasicErrorController {
 
    private static Log log = LogFactory.getLog(MyErrorController.class);
 
    public MyErrorController(ServerProperties serverProperties) {
        super(new DefaultErrorAttributes(), serverProperties.getError());
    }
 
    /**
     * 覆盖默认的Json响应
     */
    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		// 获取原始的错误信息
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
 
        
        Map<String, Object> result = new HashMap<>();
        String code = null;
        String message = null;
        Object data = null;
        
		// 设置自定义的错误信息,或者从ThreadLocal等获取错误信息
		
        result.put("code", code);
        result.put("message", message);
        result.put("data", data);
        cleanAllCache();
        return new ResponseEntity<Map<String, Object>>(result, status);
    }
 
 
}

 

2.@ControllerAdvice加上@ExceptionHandler方式

        可以处理的异常范围:进入controller之后的异常。在进入controller之前,比如执行filter过滤器时,是无法处理的,典型情况就是4xx异常,所以需要和其它全局异常来共同处理,比如第1种ErrorController。如果使用的是继承ResponseEntityExceptionHandler方式,那么ErrorController的处理结果也可能被beforeBodyWrite()拦截。

2.1.简单实现:

@ControllerAdvice
public class ResponseResultAdvice{

	private static Log log =LogFactory.getLog(ResponseResultAdvice.class);
	
	@ResponseBody
	@ExceptionHandler({ Exception.class })
	public ResponseResult<?> handleException(Exception e) {
		ResponseResult<?> responseResult = new ResponseResult<>();
		
		// 设置返回提示、打印日志、清除缓存、执行拦截器里配置的异常等等
		log.error("catch exception,", e);
			
		
		return ResponseResult;
	}	
}
public class ResponseResult<T> implements Serializable {
    private static final long serialVersionUID = -4594298719566683755L;
    /**
	 * 状态码
	 */
	protected String code;
	
	/**
	 * 返回信息
	 */
	protected String message;
    
	/**
	 * 返回报文体
	 */
	private T data;

	/**
	 * 分页对象。有页对象不为null
	 */
	private Page page;
// 省略

}

2.2.继承ResponseEntityExceptionHandler

可以像官方这样:

@ControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {

    @ResponseBody
    @ExceptionHandler(MyException.class)
    public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status);
    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        HttpStatus status = HttpStatus.resolve(code);
        return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
    }

}

2.3.实现ResponseBodyAdvice接口

通过beforeBodyWrite()来对返回结果进行一些统一处理,

通过@ControllerAdvice加上@ExceptionHandler来对异常进行统一处理

注意:如果有捕获到异常,那么会执行handleException(),执行beforeBodyWrite()


ResponseBodyAdvice源码:
  

/**
 * Allows customizing the response after the execution of an {@code @ResponseBody}
 * or a {@code ResponseEntity} controller method but before the body is written
 * with an {@code HttpMessageConverter}.
 *
 * <p>Implementations may be registered directly with
 * {@code RequestMappingHandlerAdapter} and {@code ExceptionHandlerExceptionResolver}
 * or more likely annotated with {@code @ControllerAdvice} in which case they
 * will be auto-detected by both.
 *
 * @author Rossen Stoyanchev
 * @since 4.1
 */
public interface ResponseBodyAdvice<T> {

}

@ControllerAdvice
public class WebResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    private static Log log = LogFactory.getLog(WebResponseBodyAdvice.class);
	
    /**
     * 对指定情况进行返回数据处理beforeBodyWrite
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    /**
     * 包装controller return数据类型为ResponseDTO对象以及返回token
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                  MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        if (log.isDebugEnabled()) {
            log.debug("进入response拦截器," + "body:" + body + ",MethodParameter:" + returnType + ",MediaType:" + selectedContentType + ",HttpMessageConverter:" + selectedConverterType);
        }

        if (!(body instanceof ResponseResult)) {
            return body;
        }
		
		// 对body进行处理,比如token刷新、返回code、message、data设置等等
        
		JSONObject jsonStr = (JSONObject) JSONObject.toJSON(body);
		if(log.isDebugEnabled()) {
			log.debug(body.getClass()+"转换结果:"+jsonStr.toJSONString());
		}
        return jsonStr;
    }

    @ResponseBody
	@ExceptionHandler({ Exception.class })
	public ResponseResult<?> handleException(Exception e) {
		ResponseResult<?> responseResult = new ResponseResult<>();
		
		// 设置返回提示、打印日志、清除缓存、执行拦截器里配置的异常等等
		log.error("catch exception,", e);
			
		
		return ResponseResult;
	}
}

注意:在使用ResponseBodyAdvice里的beforeBodyWrite的来包装返回数据的时候,要特别注意返回类型转换的问题。参考附录里的2

java.lang.ClassCastException: org.phan.message.Response cannot be cast to java.lang.String
@Configuration
public class CustomerWebMvcConfig implements WebMvcConfigurer {
 
	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
		// TODO Auto-generated method stub
		// WebMvcConfigurer.super.configureMessageConverters(converters);
		converters.add(gsonJsonHttpMessageConverter());// Gson和FastJson取其一,我这取Gson
		// converters.add(fastJsonHttpMessageConverter());
	}
	
	@Bean
	public GsonHttpMessageConverter gsonJsonHttpMessageConverter() {
		// 使用Gson
		return new GsonHttpMessageConverter();// Gson转换json的Converter
	}
 
	@Bean
	public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
		// 使用fastJson
		return new FastJsonHttpMessageConverter();// FastJson转换json的Converter
	}
 
}

3.HandlerExceptionResolver

主要作用是更改返回的ModelAndView指向的页面,如果想改变返回数据的值,那么可以通过自定义的HandlerExceptionResolver修改response里的数据。

整体上来说,这个主要是给MVC模式使用的,不是非常适合现在的REST模式。

public interface HandlerExceptionResolver {

	/**
	 * Try to resolve the given exception that got thrown during handler execution,
	 * returning a {@link ModelAndView} that represents a specific error page if appropriate.
	 * <p>The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty}
	 * to indicate that the exception has been resolved successfully but that no view
	 * should be rendered, for instance by setting a status code.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @param handler the executed handler, or {@code null} if none chosen at the
	 * time of the exception (for example, if multipart resolution failed)
	 * @param ex the exception that got thrown during handler execution
	 * @return a corresponding {@code ModelAndView} to forward to,
	 * or {@code null} for default processing in the resolution chain
	 */
	@Nullable
	ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}

以下是几个默认的和自定义的异常解析器

3.1.ExceptionHandlerExceptionResolver

这个是Spring 3.1引入的,在DispatcherServlet里默认使用的。

@ExceptionHandler默认使用的异常解析器

3.2.DefaultHandlerExceptionResolver

这个是Spring 3.0引入的,在DispatcherServlet里默认使用的。

用于处理HTTP Status Code对应的异常的,比如400,401等4xx、5xx异常

3.3.ResponseStatusExceptionResolver

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class MyResourceNotFoundException extends RuntimeException {
    public MyResourceNotFoundException() {
        super();
    }
    public MyResourceNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
    public MyResourceNotFoundException(String message) {
        super(message);
    }
    public MyResourceNotFoundException(Throwable cause) {
        super(cause);
    }
}

@ResponseStatus默认使用的异常解析器。可以参考附录3

3.4.自定义异常解析器

目前大多数的系统,实现Spring REST Service规范,是要求返回JSON或XML格式的数据,而不是页面,这样的话,使用Spring默认的HandlerExceptionResolver就无法实现我们的需求,需要自定义解析器。

自定义例子1:

@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {

    @Override
    protected ModelAndView doResolveException(
      HttpServletRequest request, 
      HttpServletResponse response, 
      Object handler, 
      Exception ex) {
        try {
            if (ex instanceof IllegalArgumentException) {
                return handleIllegalArgument(
                  (IllegalArgumentException) ex, response, handler);
            }
            ...
        } catch (Exception handlerException) {
            logger.warn("Handling of [" + ex.getClass().getName() + "] 
              resulted in Exception", handlerException);
        }
        return null;
    }

    private ModelAndView 
      handleIllegalArgument(IllegalArgumentException ex, HttpServletResponse response) 
      throws IOException {
        response.sendError(HttpServletResponse.SC_CONFLICT);
        String accept = request.getHeader(HttpHeaders.ACCEPT);
        ...
        return new ModelAndView();
    }
}

自定义例子2

/**
 * 
 * 类名称 : SgccExceptionResolver. <br>
 * 功能描述 : 全局异常拦截器,可在此做异常信息的判断及输出. <br>
 */
public class SgccExceptionResolver implements HandlerExceptionResolver {
​
    private Logger logger = Logger.getLogger(this.getClass());
​
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
            Exception ex) {
        logger.info("==============Exception Start 000000=============");
        if (ex instanceof BaseException) {
            logger.debug(ex, ex);
        }else {
            logger.error(ex, ex);
        }
        logger.info("==============Exception End 000000=============");
        if (NetworkUtil.isAjax(request)) {
            String msg = null;
            String code = null;
            String detail = null;
            if (ex instanceof BaseException) {
                msg = ((BaseException) ex).getErrorMsg();
                code = ((BaseException) ex).getErrorCode();
                detail = ((BaseException) ex).getMsgDetail();
            }else {
                FSTErrorCode fc = FSTErrorCode.SYS_ERROR_000000;
                msg = fc.getErrorMsg();
                code = fc.getErrorCode();
                detail = fc.getMsgDetail();
            }
            try {
                Map<String, Object> map = new HashMap<String, Object>();
                map.put("msg", msg);
                map.put("code", code);
                map.put("detail", detail);
                JSONObject json = JSONObject.fromObject(map);
                response.setContentType("text/html;charset=utf-8");
                response.getWriter().print(json);
            }catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }else {
            ModelAndView mv = new ModelAndView();
            mv.setViewName("error/error");
            mv.addObject("exception", ex.toString().replaceAll("\n", "<br/>"));
            return mv;
        }
    }    
    
}

4.ResponseStatusException

可以抛出ResponseStatusException异常,和@ResponseStatus一起使用

@GetMapping(value = "/{id}")
public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) {
    try {
        Foo resourceById = RestPreconditions.checkFound(service.findOne(id));

        eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
        return resourceById;
     }
    catch (MyResourceNotFoundException exc) {
         throw new ResponseStatusException(
           HttpStatus.NOT_FOUND, "Foo Not Found", exc);
    }
}

注意:

        在使用全局异常处理的时候,如果多种方式一起使用,那么要注意是否重复处理了,以及处理的顺序、范围。

        ErrorController方式和@ControllerAdvice加上@ExceptionHandler方式一起使用的话,@ExceptionHandler处理不到的异常,由ErrorController处理

附录:

        1.Spring Boot 官方文档异常处理章节:Spring Boot Reference Documentation

        2.ResponseBodyAdvice里Response转换异常问题 

使用 ResponseBodyAdvice 拦截Controller方法默认返回参数,统一处理返回值/响应体_记事本-CSDN博客_responsebodyadvice  这篇文章里的评论

rest - ControllerAdvice ResponseBodyAdvice failed to enclose a String response - Stack Overflow

        3.@ResponseStatus的作用_ITWANGBOIT的博客-CSDN博客

        4.Error Handling for REST with Spring | Baeldung

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring Boot全局异常处理,包括404等异常 的相关文章

随机推荐