一.异常处理器的初始化
首先说异常解析器,我们知道springmvc在抛出异常的时候都会被异常解析器处理
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean
ExceptionHandlerExceptionResolver实现了InitializingBean接口,在spring容器装载的时候会进行初始化调用,InitializingBean接口如下
public interface InitializingBean {
/**
* Invoked by the containing {@code BeanFactory} after it has set all bean properties
* and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
* <p>This method allows the bean instance to perform validation of its overall
* configuration and final initialization when all bean properties have been set.
* @throws Exception in the event of misconfiguration (such as failure to set an
* essential property) or if initialization fails for any other reason
*/
void afterPropertiesSet() throws Exception;
}
initExceptionHandlerAdviceCache(); 首先调用initExceptionHandlerAdviceCache方法完成 @ControllerAdvice注解相关的初始化
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
initExceptionHandlerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
private void initExceptionHandlerAdviceCache() {
.....省略一些不重要的代码,只关注本次研究的重点
// 第一步获取容器中所有标注了@ControllerAdvice的bean,收集起来
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
// 遍历所有带@ControllerAdvice注解的bean
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);
}
//最后还会判断是否实现了ResponseBodyAdvice接口,也保存在集合里
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
}
找到容器中所有带@ControllerAdvice注解的bean
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
// 保存结果
List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
// BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)
// 获取容器中的所有 bean name
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) {
if (!ScopedProxyUtils.isScopedTarget(name)) {
// 这是核心,找到容器中所有带@ControllerAdvice注解的bean
ControllerAdvice controllerAdvice = context.findAnnotationOnBean(name, ControllerAdvice.class);
if (controllerAdvice != null) {
// Use the @ControllerAdvice annotation found by findAnnotationOnBean()
// in order to avoid a subsequent lookup of the same annotation.
adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice));
}
}
}
OrderComparator.sort(adviceBeans);
return adviceBeans;
}
ExceptionHandlerMethodResolver
/**
* A filter for selecting {@code @ExceptionHandler} methods.
*/
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
/**
* A constructor that finds {@link ExceptionHandler} methods in the given type.
* @param handlerType the type to introspect
*/
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
// 找到给定类型的bean中所有带@ExceptionHandler注解的方法
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
下面的方法是收集某个方法中,可以处理的异常集合
/**
* Extract exception mappings from the {@code @ExceptionHandler} annotation first,
* and then as a fallback from the method signature itself.
*/
@SuppressWarnings("unchecked")
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<>();
detectAnnotationExceptionMappings(method, result);
// 如果注解上没有指定异常,查看方法参数中有没有指定的异常也可以
if (result.isEmpty()) {
for (Class<?> paramType : method.getParameterTypes()) {
if (Throwable.class.isAssignableFrom(paramType)) {
result.add((Class<? extends Throwable>) paramType);
}
}
}
if (result.isEmpty()) {
throw new IllegalStateException("No exception types mapped to " + method);
}
return result;
}
// 获取方法上@ExceptionHandler注解的value属性,value表示当前方法的处理异常有哪些
private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
Assert.state(ann != null, "No ExceptionHandler annotation");
result.addAll(Arrays.asList(ann.value()));
}
将异常类型和处理的方法放到一个map中 mappedMethods属性
public class ExceptionHandlerMethodResolver {
/**
* A filter for selecting {@code @ExceptionHandler} methods.
*/
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);
/**
* A constructor that finds {@link ExceptionHandler} methods in the given type.
* @param handlerType the type to introspect
*/
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
}
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
Method oldMethod = this.mappedMethods.put(exceptionType, method);
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
exceptionType + "]: {" + oldMethod + ", " + method + "}");
}
}
mappedMethods中key存放的是异常类型,value存放的是处理这个异常的Method对象,
/**
* Whether the contained type has any exception mappings.
*/
public boolean hasExceptionMappings() {
return !this.mappedMethods.isEmpty();
}
最后将结果保存在 exceptionHandlerAdviceCachemap中,
private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
new LinkedHashMap<>();
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
总结: ExceptionHandlerExceptionResolver异常解析器在初始化的时候会去容器中查找所有标记@ControllerAdvice注解的bean,
并对这些bean中标记@ExceptionHandler注解的方法进行解析,解析的目的是为了想要区分什么样的异常类型用那个方法处理,
将分析的结果保存在 ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCachemap属性中,
exceptionHandlerAdviceCachemap的key是对应的标注@ControllerAdvice的bean,
value是ExceptionHandlerMethodResolver对象,ExceptionHandlerMethodResolver对象中封装了这个bean中所有标记了@ExceptionHandler的方法
ExceptionHandlerMethodResolver对象中含有mappedMethods属性,存放的就是异常类型和处理方法对应的映射关系
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
到这里异常处理器的初始化就已经结束了.接下来就是当控制器发生异常了,就会被异常处理器拦截,进入对应的方法
二.异常处理器捕捉异常
所有请求经过dispatchServlet进行处理,处理流程可以看调用链,当有异常的时候就会被异常处理器捕获,解析处理
doDispatch(request, response);
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
mv = processHandlerException(request, response, handler, exception);
....
最後会选择一个合适的异常处理器进行处理,这里有一个HandlerExceptionResolverComposite类,里面代理了很多类型的异常处理器,最后会选择一个异常处理器进行处理,就是ExceptionHandlerExceptionResolver
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (this.resolvers != null) {
for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (mav != null) {
return mav;
}
}
}
return null;
}
ExceptionHandlerExceptionResolver的继承结构如下
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean
ExceptionHandlerExceptionResolver--->AbstractHandlerMethodExceptionResolver
--->AbstractHandlerExceptionResolver---> HandlerExceptionResolver
最终调用ExceptionHandlerExceptionResolver的doResolveHandlerMethodException方法进行异常处理
/**
* Find an {@code @ExceptionHandler} method and invoke it to handle the raised exception.
*/
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
if (this.argumentResolvers != null) {
// 设置参数解析器
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
// 设置返回值解析器
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 封装请求
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
try {
.....
Throwable cause = exception.getCause();
if (cause != null) {
// Expose cause as provided argument as well
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
}
else {
// Otherwise, just the given exception as-is
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
}
}
catch (Throwable invocationEx) {
// Any other than the original exception (or its cause) is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) {
logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
}
// Continue with default processing of the original exception...
return null;
}
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
else {
ModelMap model = mavContainer.getModel();
HttpStatus status = mavContainer.getStatus();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
mav.setViewName(mavContainer.getViewName());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
}
getExceptionHandlerMethod方法做了什么?只做了一件事,根据异常的类型获取处理异常的method,将method对象包装成ServletInvocableHandlerMethod对象,ServletInvocableHandlerMethod对象,以供后来调用
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
// Local exception handler methods on the controller class itself.
// To be invoked through the proxy, even in case of an interface-based proxy.
handlerType = handlerMethod.getBeanType();
// 这里的逻辑是先从缓存中获取,获取不到在构建,构建之后在放入到缓存中,供下次使用
// 因为 ExceptionHandlerMethodResolver的构建是比较费时的
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
// 就是获取能够处理这个异常的方法method对象
Method method = resolver.resolveMethod(exception);
if (method != null) {
// 封装成ServletInvocableHandlerMethod返回
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
// For advice applicability check below (involving base packages, assignable types
// and annotation presence), use target class instead of interface-based proxy.
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
if (advice.isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
}
}
}
return null;
}
Method method = resolver.resolveMethod(exception);
ExceptionHandlerMethodResolver可以根据异常类型获取处理异常的method通过resolveMethod()方法
@Nullable
public Method resolveMethod(Exception exception) {
return resolveMethodByThrowable(exception);
}
/**
* Find a {@link Method} to handle the given Throwable.
* Use {@link ExceptionDepthComparator} if more than one match is found.
* @param exception the exception
* @return a Method to handle the exception, or {@code null} if none found
* @since 5.0
*/
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
Method method = resolveMethodByExceptionType(exception.getClass());
if (method == null) {
Throwable cause = exception.getCause();
if (cause != null) {
method = resolveMethodByExceptionType(cause.getClass());
}
}
return method;
}
// 核心方法 上面的两个方法最终都会调用到resolveMethodByExceptionType()
// resolveMethodByExceptionType用了一个二级缓存,如果存在就从缓存中直接取了
/**
* Find a {@link Method} to handle the given exception type. This can be
* useful if an {@link Exception} instance is not available (e.g. for tools).
* @param exceptionType the exception type
* @return a Method to handle the exception, or {@code null} if none found
*/
@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
method = getMappedMethod(exceptionType);
this.exceptionLookupCache.put(exceptionType, method);
}
return method;
}
// mappedMethods上一节说过存放的是异常类型和处理方法的映射,根据异常类型找到对应的处理方法
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
if (!matches.isEmpty()) {
matches.sort(new ExceptionDepthComparator(exceptionType));
return this.mappedMethods.get(matches.get(0));
}
else {
return null;
}
}
// ExceptionHandlerExceptionResolver对象中含有参数解析器和返回值解析器,至于何时初始化的,换个文章在讲
@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;
@Nullable
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
最终调用
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 调用到目标方法了
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 将返回值用返回值处理器进行处理返回
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
经过上面两节的分析,controller的所有异常都可以在我们的控制下进行处理,当出现异常时,捕获异常,给前端返回标准统一的消息。
@ControllerAdvice配合@ExceptionHandler可以捕获想要的异常
后续可以分析下 1.异常处理器如何装载的 2.ExceptionHandlerExceptionResolver是如何被选择的,那么多处理器 2.返回值处理器如何处理返回值的。是如何分辨返回的是视图还是json.