目录
全局异常处理实现方式
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(使用前将#替换为@)