spring mvc 异常处理
spring 统一处理异常的3中方式:
- 使用
@ExceptionHandler
注解方法 - 实现
HandlerExceptionResolver
接口 - 使用
@controlleradvice
类
使用 @ExceptionHandler 注解
如果使用注解注释方法进行统一的异常处理的话,异常处理方法必须与抛出异常的方法在同一个controller
代码:
public abstract class ResponseExceptionResolver {
/**
* 这是定义的处理异常的方法
* [@param](https://my.oschina.net/u/2303379) ex
* [@return](https://my.oschina.net/u/556800)
*/
@ExceptionHandler(BusinessException.class)
public String resolver(BusinessException ex) {
return null;
}
采用这种方式代码会很冗余,每个controller
都要针对的对可能抛出的相同的异常做相同的处理,不过可以定义抽象的BaseController
,对抛出的自定义异常在BaseController
中做统一的异常处理。具体的资源controller
可以继承BaseController
,也能做到统一的异常解决,还能减少重复代码。
实现 HandlerExceptionResolver 接口
代码:
@Component
public class ResponseExceptionResolver implements HandlerExceptionResolver{
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
return null;
}
}
这种方法也可以进行全局的异常控制,它要实现resolveException(..)
方法,而且返回值就只能是ModelAndView
类型的,在实现方法中如果想要是简单json
格式或者html
格式需要先构造一个ModelAndView
出来,包装错误信息,返回到一个错误的页面,显示错误信息,这样的话比较麻烦。
使用 @controlleradvice 注解
在上面第一种方法说到采用注解@ExceptionHandler
的方式需要跟想要处理异常的handleMethod
在同一个controller
中,不过可以使用@controlleradvice + @ExceptionHandler
的方式定义全局异常处理,在spring 4.3
以上版本可以使用 @RestControllerAdvice + @ExceptionHandler
两个注解,前者会帮你把异常处理后的返回值对象自动封装成json
格式
- @RestControllerAdvice 注解定义(相当于
@ControllerAdvice + @ResponseBody
)@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody public @interface RestControllerAdvice { ... }
代码:
@RestControllerAdvice
public class ResponseExceptionResolver {
@ExceptionHandler(BusinessException.class)
public GenericResponse<String> resolver(BusinessException ex) {
return new GenericResponse<>(ex.getCode(), ex.getMessage(), "");
}
}
spring mvc 异常类注册源码解析
DispatcherServlet类装载:
public class DispatcherServlet extends FrameworkServlet {
...
// 重点:这是当前类目录下的一个property文件,在下面静态代码块中被使用
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
...
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
// 重点:这里装配资源文件
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
...
}
可以看到DispatcherServlet
在类加载的时候会执行静态代码块的内容,加载当前类同级目录的一个DispatcherServlet.properties
文件保存在defaultStrategies
静态常量中
在查看DispatcherServlet
的继承关系后,在servlet
容器启动后经过几轮的方法调用后会执行DispatcherServlet
类的initStrategies(..)
方法(web.xml在web容易启动后依次的调用顺序是<context-param>
--> <listener>
--> <filter>
--> <servlet>
),至于容器启动后怎么调用的到DispatcherServlet
的initStrategies(..)
方法的,自行看源码调用次序
这是DispatcherServlet
的继承关系:
DispatcherServlet
初始化HandlerExceptionResolvers
的过程:
public class DispatcherServlet extends FrameworkServlet {
...
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
// 重点:注册 HandlerExceptionResolvers
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
...
private void initHandlerExceptionResolvers(ApplicationContext context) {
... 省略代码
// 上面一段省略的代码的意思大概是在application上下文配置文件中查找HandlerExceptionResolvers配置
...
// 重点:如果没有找到,那就去上文配置的那个DispatcherServlet.properties资源文件中去找到默认的配置进行注册
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) {
// 重点:这个方法里面便是通过反射包名初始化默认配置然后进行注册
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
}
}
}
...
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
// 重点:这里通过类的全限定名去资源文件找
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<T>(classNames.length);
for (String className : classNames) {
try {
// 重点:找到之后就初始化了
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
...
}
catch (LinkageError err) {
...
}
}
return strategies;
}
else {
return new LinkedList<T>();
}
}
...
}
DispatcherServlet.properties文件中HandlerExceptionResolver
默认加载的实现类:
...
org.springframework.web.servlet.HandlerExceptionResolver=
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
...
这里便注册了默认的HandlerExceptionResolver
关于spring自己对于HandlerExceptionResolver
的实现类详解可以参照:
http://www.cnblogs.com/fangjian0423/p/springMVC-exception-analysis.html
文章参考: