1. 分析
异常处理对于系统的稳定性非常重要,如果系统出现了unchecked exception那么会直接导致系统崩溃,但是,没有项目经验的我们,在处理问题的时候应该如何来处理这些异常呢?我想若依项目肯定能给大家带来一些思路上的启发。
2. GlobalExceptionHandler
该类所在的目录为:\ruoyi-framework\src\main\java\com\ruoyi\framework\web\exception
framework表示的就是框架层面的代码,这里其实使用的异常处理方式就是使用了SpringMVC中全局异常处理机制。
@RestControllerAdvice
public class GlobalExceptionHandler
{
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 权限校验失败 如果请求为ajax返回json,普通请求跳转页面
*/
@ExceptionHandler(AuthorizationException.class)
public Object handleAuthorizationException(HttpServletRequest request, AuthorizationException e)
{
log.error(e.getMessage(), e);
if (ServletUtils.isAjaxRequest(request))
{
return AjaxResult.error(PermissionUtils.getMsg(e.getMessage()));
}
else
{
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error/unauth");
return modelAndView;
}
}
/**
* 请求方式不支持
*/
@ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
public AjaxResult handleException(HttpRequestMethodNotSupportedException e)
{
log.error(e.getMessage(), e);
return AjaxResult.error("不支持' " + e.getMethod() + "'请求");
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public AjaxResult notFount(RuntimeException e)
{
log.error("运行时异常:", e);
return AjaxResult.error("运行时异常:" + e.getMessage());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e)
{
log.error(e.getMessage(), e);
return AjaxResult.error("服务器错误,请联系管理员");
}
/**
* 业务异常
*/
@ExceptionHandler(BusinessException.class)
public Object businessException(HttpServletRequest request, BusinessException e)
{
log.error(e.getMessage(), e);
if (ServletUtils.isAjaxRequest(request))
{
return AjaxResult.error(e.getMessage());
}
else
{
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("errorMessage", e.getMessage());
modelAndView.setViewName("error/business");
return modelAndView;
}
}
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public AjaxResult validatedBindException(BindException e)
{
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return AjaxResult.error(message);
}
/**
* 演示模式异常
*/
@ExceptionHandler(DemoModeException.class)
public AjaxResult demoModeException(DemoModeException e)
{
return AjaxResult.error("演示模式,不允许操作");
}
}
3. 处理异常的几种方式
3.1 springMVC默认处理的异常
spring在一些异常的时候会默认来处理,处理方式就是更改http的状态码,然后显示我们熟悉的白页
下面介绍的就是spring默认会处理的一些异常
Spring异常 | HTTP状态码 |
---|---|
BindException | 400 - Bad Request |
HttpMediaTypeNotAcceptableException | 406 - Not Acceptable |
HttpMediaTypeNotSupportedException | 415 - Unsupported Media Type |
HttpMessageNotReadableException | 400 - Bad Request |
HttpMessageNotWritableException | 500 - Internal Server Error |
HttpRequestMethodNotSupportedException | 405 - Method Not Allowed |
MethodArgumentNotValidException | 400 - Bad Request |
MissingServletRequestParameterException | 400 - Bad Request |
MissingServletRequestPartException | 400 - Bad Request |
NoSuchRequestHandlingMethodException | 404 - Not Found |
TypeMismatchException | 400 - Bad Request |
ConversionNotSupportedException | 500 - Internal Server Error |
上表中的异常会由Spring自身抛出,如果DispatcherServlet处理过程中或执行校验时出现问题时则直接返回。例如,如果DispatcherServlet无法找到适合处理请求的控制器方法,那么将会抛出NoSuchRequestHandlingMethodException异常,最终的结果就是产生404状态码的响应(Not Found)。
3.2 使用@ResponseStatus处理自定义异常
参考文章: Spring @ResponseStatus
@ResponseStatus声明在方法、类上, Spring3.0开始才有的, 三个属性其中 HttpStatus类型的 value 和 code是一个含义, 默认值就是 服务器 500错误的 HttpStatus.
方式一:标注在@RequestMapping上
@Controller
@RequestMapping("/simple")
public class SimpleController {
@RequestMapping("/demo2")
@ResponseBody
@ResponseStatus(code = HttpStatus.OK)
public String demo2(){
return "hello world";
}
}
这个原理就是通过response.setStatus()的方式将code给它,其中执行过程的细节可以参考我们上面给的参考文章
方式二:标注在@ControllerAdvice上
@ControllerAdvice
@ResponseStatus
public class MyControllerAdvice {
@ExceptionHandler({ArithmeticException.class})
public ModelAndView fix(Exception e){
Map map=new HashMap();
map.put("ex",e.getMessage());
return new ModelAndView("error",map);
}
}
这样标注了以后,其实也是修改一下状态码,原本出现异常以后状态码应该是4开头或5开头的错误码,但是因为我们正常处理,那么就会使用默认的200,为了让异常码于异常对应起来,那么就是用这个注解修改一下状态码。
但是,不要在ResponseStatus中写reason,如果写了就会默认使用tomcat的状态界面,非常不好看
方式三:在自定义异常上添加
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR,reason = "not an error , just info")
public class MyException extends RuntimeException {
public MyException() {
}
public MyException(String message) {
super(message);
}
}
因为写了reason,那么一旦报出这个异常以后,就会展示这种界面
总结一下
- 不管哪种方式,@ReponseStatus最后都是通过response.setStatus或response.sendError来处理
- 如果只是为了返回状态码,建议只使用 @ResponseStatus(code=xxxx)这样来设置响应状态码;
- 如果抛出异常呢,不建议@ControllerAdvice里面的 @ResponseStatus和 自定义异常上的 @ResponseStatus一起使用,两个一起使用肯定是一个生效,而且是 @ControllerAdvice中的@ResponseStatus生效
3.3 使用try-catch的方式捕获异常
使用try-catch是java中处理异常的方式,这种方式是最容易想到的方式,但是由于这种方式非常难维护,我们还是用其他方式处理
3.4 使用@ExceptionHandler处理自定义异常
@Controller
@RequestMapping("test/exception")
public class ExceptionTestController {
private Logger logger = LoggerFactory.getLogger(ExceptionTestController.class);
@Autowired
private OrgService service;
@RequestMapping(value = "orgs", method = RequestMethod.GET)
@ResponseBody
public List getOrgs () {
List orgs = service.getOrgs();
return orgs;
}
// NullOrgException是我们自定义的运行时异常
@ExceptionHandler(NullOrgException.class)
public String handleNullOrgException() {
return "无组织机构相关数据!";
}
}
上面代码我们在handleNullOrgException()方法上添加了@ExceptionHandler注解,当程序抛出NullOrgException异常时,将会委托该方法来处理。它的返回值是String,你也可以改为其它的返回类型以满足应用程序需要。对于用@ExceptionHandler注解标注的方法来说,它能处理同一个控制器(Controller)中所有处理器方法所抛出的异常。
由于该注解只能写在方法上,而且处理的异常仅仅来源你写的这个类或方法中,因此很难复用。
为了避免在多个控制器中编写重复的@ExceptionHandler注解方法,我们会创建一个基础的控制器类,所有控制器类要扩展这个类,从而 继承 通用的@ExceptionHandler方法。
使用@ControllerAdvice注解处理所有异常
前面我们说使用@ExceptionHandler注解只能处理一个控制器(Controller)中所有处理器方法所抛出的异常,那么有没有一种方法不用集成就能够处理所有控制器中处理器方法所抛出的异常呢?
Spring3.2已经帮我们完成了这样一个设想。在Spring 3.2之后,为这类问题引入了一个新的解决方案:控制器通知。
控制器通知(controller advice)是指任意带有@ControllerAdvice注解的类。
这个类会包含一个或多个如下类型的方法:
- @ExceptionHandler注解标注的方法;
- @InitBinder注解标注的方法;
- @ModelAttribute注解标注的方法。
在带有@ControllerAdvice注解的类中,上述的这些方法会运用到整个应用程序所有控制器中带有@RequestMapping注解的方法上。@ControllerAdvice注解本身已经使用了@Component,因此@ControllerAdvice注解所标注的类将会自动被组件扫描获取到,和有@Component注解的类一样。@ControllerAdvice最为实用的一个场景就是将所有的@ExceptionHandler方法收集到一个类中,这样所有控制器的异常就能在一个地方进行统一处理。例如,我们想将NullOrgException的处理方法用到整个应用程序的所有控制器上。如下的程序清单展现的AppWideExceptionHandler就能完成这一任务,这是一个带有@ControllerAdvice注解的类。下面代码使用@ControllerAdvice,为所有的控制器处理异常:
@ControllerAdvice
// 定义控制器类
public class AppWideException {
// 定义异常处理方法
@ExceptionHandler(NullOrgException.class)
public String handleNullOrgException() {
return "无组织机构相关数据!";
}
}
看到这里,我们也应该明白了若依处理全局异常的方式,就是我们介绍的最后一种方式。