在一个web项目开发中,通常都会涉及到Html和Json请求。当出现异常的时候,我们需要根据请求类型返回不同的信息。如果是Json请求,那么就返回
String
或者ReponseEntity
类型;如果是html请求,就要返回ModelAndView
的错误页面。
我们当然可以对Controller
的每个接口方法抛出的异常单独处理。但这样做会导致大量的重复工作。Spring MVC为我们提供了@ControllerAdvice
和@ExceptionHandler`两个注解来实现全局的异常处理。关于这两个注解的用法可以参考这里。
这解决了大部分的问题,但是如果同一个Controller
中既有html又有json接口方法怎么办呢?我们当然可以拆分成两个Controller
,一个包含html接口,另一个包含json接口。但是这样做不够灵活,而且我更习惯根据业务逻辑来归类接口。有没有更好的方法呢?
其实在写异常处理方法时,我们可以将请求信息作为参数传入,并根据请求类型来返回不同的数据。
public class BaseController{
private Boolean isJson(HttpServletRequest request){
String header = request.getHeader("content-type");
return header != null && header.contains("json");
}
@Override
@ExceptionHandler(BaseException.class)
public Object handleBaseException(HttpServletRequest request, baseException e) {
if(isJson(request)) {
return ResponseUtils.restResponse(
e.getCode(),
e.getMessage(),
e.getStatus()
);
} else {
ModelAndView modelAndView = initModelAndView();
if (e.getCode().equalsIgnoreCase("login_first")) {
modelAndView.setViewName("redirect:/list");
}
if (e.getCode().equalsIgnoreCase("real_name_not_set")) {
modelAndView.setViewName("redirect:/account");
}else{
modelAndView.setViewName("/404");
}
modelAndView.addObject("exception", e);
return modelAndView;
}
}
}
这里我们写了一个BaseController
,并在Controller
中实现了异常捕获的逻辑。isJson()
通过判断请求的Content-Type
是否包含json字符串来判断该请求类型。当然,更好更合适的方式是通过包头中的Accept
中的信息类判断。需要注意的是handleBaseException()
方法返回了Object类型,这样我们就可以根据需要返回不同类型的数据了。以后只要Contrller
继承BaseController
就不用再考虑异常的问题了。
但是,如果异常是在进入接口方法之前被抛出的呢。比如404,406错误,根本不会执行接口方法,因此也无法被ExceptionHandler
捕获。这部分异常如何处理呢?
Spring Boot提供了一个统一的/error
地址用于所有未被捕获的异常抛出。默认设置下显示的是一个whitelabel error page
。
通过实现ErrorController,我们可以定制这个错误页面。
@Controller
public class MpErrorController extends BaseController implements ErrorController {
private static final String PATH = "/error";
@RequestMapping(value = PATH)
public Object error() throws Exception {
throw new BaseException();
}
@Override
public String getErrorPath() {
return PATH;
}
}
我们希望这个error页面也根据请求的类型做出不同的逻辑处理。因此,可以直接在error()
抛出BaseException异常,并且让这个Controller继承于BaseController。这样,被抛出的异常也会被handleBaseException()
捕获了。
至此,我们比较优雅的实现了全局的异常处理。所有的BaseException
异常处理逻辑都集中在handleBaseException()
方法中。