目录
一、异常简单使用
统一异常处理使用的是
@ControllerAdvice
+
@ExceptionHandler
来实现的,@ControllerAdvice 表示控制器通知类,
@ExceptionHandler
是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。
import com.example.demo.model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
return Result.fail(e.getMessage());
}
}
以上代码表示,如果代码出现Exception异常(包括Exception的子类), 就返回一个 Result的对象, Result 对象的设置参考 Result.fail(e.getMessage())
public static Result fail(String msg) {
Result result = new Result();
result.setStatus(ResultStatus.FAIL);
result.setErrorMessage(msg);
result.setData("");
return result;
}
针对不同的异常,返回不同的结果:
import com.example.demo.model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ResponseBody
@ControllerAdvice
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
return Result.fail(e.getMessage());
}
@ExceptionHandler
public Object handler(NullPointerException e) {
return Result.fail("发⽣NullPointerException:"+e.getMessage());
}
@ExceptionHandler
public Object handler(ArithmeticException e) {
return Result.fail("发⽣ArithmeticException:"+e.getMessage());
}
}
二、@ControllerAdvice 源码分析
统一数据返回和统一异常都是基于
@ControllerAdvice
注解来实现的,通过分析@ControllerAdvice 的源码,可以知道他们的执行流程。
点击
@ControllerAdvice
实现源码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] assignableTypes() default {};
Class<? extends Annotation>[] annotations() default {};
}
从上述源码可以看出
@ControllerAdvice
派生于 @Component 组件,这也就是为什么没有五大注解, ControllerAdvice
就生效的原因。
下面看看Spring是怎么实现的,还是从
DispatcherServlet
的代码开始分析。DispatcherServlet 对象在创建时会初始化一系列的对象:
public class DispatcherServlet extends FrameworkServlet {
//...
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further
strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
//...
}
对于
@ControllerAdvice
注解,重点关注
initHandlerAdapters(context) 和
initHandlerExceptionResolvers(context) 这两个方法:
- initHandlerAdapters(context)
initHandlerAdapters(context) 方法会取得所有实现了
HandlerAdapter
接口的bean并
保存起来,其中有一个类型为
RequestMappingHandlerAdapter
的 bean,这个bean就是
@RequestMapping
注解能起作用的关键,这个bean在应用启动过程中会获取所有被
@ControllerAdvice
注解标注 bean对象, 并做进一步处理,关键代码如下:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
//...
/**
* 添加ControllerAdvice bean的处理
*/
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
//获取所有所有被 @ControllerAdvice 注解标注的bean对象
List<ControllerAdviceBean> adviceBeans =
ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for
ControllerAdviceBean: " + adviceBean);
}
Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType,
MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
}
Set<Method> binderMethods =
MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(adviceBean, binderMethods);
}
if (RequestBodyAdvice.class.isAssignableFrom(beanType) ||
ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0,
requestResponseBodyAdviceBeans);
}
if (logger.isDebugEnabled()) {
int modelSize = this.modelAttributeAdviceCache.size();
int binderSize = this.initBinderAdviceCache.size();
int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount
== 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " + modelSize + "
@ModelAttribute, " + binderSize +
" @InitBinder, " + reqCount + " RequestBodyAdvice, " +
resCount + " ResponseBodyAdvice");
}
}
}
//...
}
这个方法在执行时会查找使用所有的
@ControllerAdvice
类,把
ResponseBodyAdvice
类放在容器中,当发生某个事件时,调用相应的 Advice方法,比如返回数据前调用统一数据封装。
- initHandlerExceptionResolvers(context)
接下来看
DispatcherServlet
的
initHandlerExceptionResolvers(context)
方法,这个方法会取得所有实现了 HandlerExceptionResolver
接口的bean并保存起来,其中就有一个类型为ExceptionHandlerExceptionResolver
的bean,这个bean在应用启动过程中会获
取所有被
@ControllerAdvice
注解标注的bean对象做进一步处理, 代码如下:
public class ExceptionHandlerExceptionResolver extends
AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean {
//...
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
// 获取所有所有被 @ControllerAdvice 注解标注的bean对象
List<ControllerAdviceBean> adviceBeans =
ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for
ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new
ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
if (logger.isDebugEnabled()) {
int handlerSize = this.exceptionHandlerAdviceCache.size();
int adviceSize = this.responseBodyAdvice.size();
if (handlerSize == 0 && adviceSize == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " +
handlerSize + " @ExceptionHandler, " + adviceSize + "
ResponseBodyAdvice");
}
}
}
//...
}
当Controller抛出异常时,
DispatcherServlet
通过 ExceptionHandlerExceptionResolver 来解析异常,而ExceptionHandlerExceptionResolver 又通过 ExceptionHandlerMethodResolver
来解析异常, ExceptionHandlerMethodResolver 最终解析异常找到适用的@ExceptionHandler标注
的方法是这里:
public class ExceptionHandlerMethodResolver {
//...
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList();
//根据异常类型, 查找匹配的异常处理⽅法
//⽐如NullPointerException会匹配两个异常处理⽅法:
//handler(Exception e) 和 handler(NullPointerException e)
for (Class<? extends Throwable> mappedException :
this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
//如果查找到多个匹配, 就进⾏排序, 找到最使⽤的⽅法. 排序的规则依据抛出异常相对于声明异常的深度
//⽐如抛出的是NullPointerException(继承于RuntimeException, RuntimeException⼜继承于Exception)
//相对于handler(NullPointerException e) 声明的NullPointerException深度为0,
//相对于handler(Exception e) 声明的Exception 深度 为2
//所以 handler(NullPointerException e)标注的⽅法会排在前⾯
if (!matches.isEmpty()) {
if (matches.size() > 1) {
matches.sort(new ExceptionDepthComparator(exceptionType));
}
return this.mappedMethods.get(matches.get(0));
}
else {
return NO_MATCHING_EXCEPTION_HANDLER_METHOD;
}
}
//...
}