8. 在SpringMVC框架中统一处理异常
在SpringMVC框架中提供了统一处理异常的机制(当然,在SpringBoot框架中也可以直接使用),使得每种异常只需要被处理1次即可,即使某种异常在多种请求中都会出现,也不需要反复处理!其核心是开发人员调用了可能抛出异常的方法时,在控制器中,直接将异常再次抛出,则SpringMVC在调用控制器的方法时,就会捕获到对应的异常对象,并且,如果开发人员定义了统一处理异常的方法,则SpringMVC框架就会自动调用该方法来处理异常!
关于统一处理异常的方法:
默认情况下,该方法只能作用于当前控制器类中的相关请求,例如,将该方法写在UserController中,只能作用了UserController 中处理的各个请求,如果在其它控制器的方法执行过程中出现了异常,是不会被处理的!关于这个问题,可选择的解决方案有2种:
将处理异常的方法写在控制器类的基类中,各控制器类都继承自该基类即可;
将处理异常的方法定义在任意类中,并在这个类的声明之前添加@ControllerAdvice或@RestControllerAdvice注解,各控制器类不需要继承自该类;
统一处理异常的方法必须添加@ExceptionHandler注解;
应该使用public权限;
返回值的类型,可参考处理请求的方法的返回值的设计原则;
方法名称可以自定义;
方法的参数列表至少需要添加异常类型的参数,用于表示被框架捕获的异常对象,关于参数的异常类型,要求能够表示任何将被处理的异常;方法的参数列表中还可以添加其它参数,但是,只能添加SpringMVC框架允许的几种参数,例如HttpServletRequest、HttpServletResponse等,不能像处理请求的方法那样随意!
可以在项目的cn.tedu.straw.portal.controller包中创建GlobalExceptionHandler类,用于统一处理异常,在类的声明之前添加@RestControllerAdvice,使得该类中处理异常的方法能作用于整个项目,并在这个类中添加方法来处理异常:
package cn.tedu.straw.portal.controller; import cn.tedu.straw.portal.service.ex.ClassDisabledException; import cn.tedu.straw.portal.service.ex.InsertException; import cn.tedu.straw.portal.service.ex.InviteCodeException; import cn.tedu.straw.portal.service.ex.PhoneDuplicateException; import cn.tedu.straw.portal.vo.R; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler public R handleException(Throwable e) { if (e instanceof InviteCodeException) { return R.failure(12, e); } else if (e instanceof ClassDisabledException) { return R.failure(13, e); } else if (e instanceof PhoneDuplicateException) { return R.failure(14, e); } else if (e instanceof InsertException) { return R.failure(15, e); } else { return R.failure(9999, e); } } }
为了便于统一管理错误代号,并增加代码的可读性,应该将这些错误代号声明为静态常量,同时,为了便于声明和管理这些静态常量,可以在R
类中使用静态内部接口来声明:
package cn.tedu.straw.portal.vo; import lombok.Data; import lombok.experimental.Accessors; @Data @Accessors(chain=true) public class R { private Integer state; private String message; public static R ok() { return new R().setState(State.OK); } public static R failure(Integer state, String message) { return new R().setState(state).setMessage(message); } public static R failure(Integer state, Throwable e) { return failure(state, e.getMessage()); } public static interface State { int OK = 0; int ERR_INVITE_CODE = 4001; int ERR_CLASS_DISABLED = 4002; int ERR_PHONE_DUPLICATE = 4003; int ERR_INSERT = 4004; int ERR_UNKNOWN = 9999; } }
然后,在处理异常时,错误代号就使用这些静态常量:
package cn.tedu.straw.portal.controller; import cn.tedu.straw.portal.service.ex.ClassDisabledException; import cn.tedu.straw.portal.service.ex.InsertException; import cn.tedu.straw.portal.service.ex.InviteCodeException; import cn.tedu.straw.portal.service.ex.PhoneDuplicateException; import cn.tedu.straw.portal.vo.R; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler public R handleException(Throwable e) { if (e instanceof InviteCodeException) { return R.failure(R.State.ERR_INVITE_CODE, e); } else if (e instanceof ClassDisabledException) { return R.failure(R.State.ERR_CLASS_DISABLED, e); } else if (e instanceof PhoneDuplicateException) { return R.failure(R.State.ERR_PHONE_DUPLICATE, e); } else if (e instanceof InsertException) { return R.failure(R.State.ERR_INSERT, e); } else { return R.failure(R.State.ERR_UNKNOWN, e); } } }