一、@ControllerAdvice注解
1.1、简介
@ControllerAdvice注解主要用来实现一些全局性的功能,最常见的就是结合@ExceptionHandler注解实现全局异常的处理,这个也是我们这篇内容学习的重点。除了全局异常处理,还可以配合@InitBinder和@ModelAttribute两个注解使用,其中,@InitBinder注解主要用于请求中注册自定义参数的解析,从而达到自定义请求参数格式化的目的;@ModelAttribute注解主要用于表示被注解的方法会在执行目标Controller方法之前执行。
1.2、全局异常处理 用法
配合@ExceptionHandler注解实现全局的异常处理是@ControllerAdvice注解最常用的功能,我们这一节主要分析两个注解是如何配合实现全局异常处理的,首先我们它们是如何配合使用的:
/**
* 全局统一的异常处理方案。
* @author hsh
*
*/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理SysRunException异常
* @return
*/
@ExceptionHandler(SysBaseException.class)
@ResponseBody
Map<String,Object> handleException(SysBaseException e){
Map<String, Object> result = new HashMap<String, Object>();
result.put(QriverSysConstant.SUCCESS, QriverSysConstant.FLAG_FALSE);
result.put(QriverSysConstant.MSG, e.getErrorMsg());
return result;
}
/**
* 404 页面不存在拦截器
* @param request
* @param response
* @param e
* @return
*/
@ExceptionHandler(NoHandlerFoundException.class)
public ModelAndView handle404Exception(HttpServletRequest request, HttpServletResponse response, NoHandlerFoundException e){
ModelAndView mv = new ModelAndView();
mv.addObject("msg",e.getMessage());
mv.addObject("url",request.getRequestURL());
mv.setViewName("error/400");
return mv;
}
//……
}
在上述代码中,首先在类上使用@ControllerAdvice注解,表明该类用于全局处理(和直接在Controller中使用@ExceptionHandler注解相比,可以实现跨Controller处理),然后在异常处理的方法上再通过@ExceptionHandler注解,实现特定异常处理器的拦截。
二、原理探究
使用@ControllerAdvice+@ExceptionHandler注解实现异常处理的用法很简单,那么它们是如何生效的呢?我们下面逐步分析。
2.1、初始化
在项目启动阶段(我们这里使用了基于SpringBoot方式运行),SpringBoot的自动配置类就会被加载,这个过程中就会把ExceptionHandlerExceptionResolver进行初始化,过程如下:
首先,SpringBoot启动过程中,会加载自动配置类WebMvcAutoConfiguration,代码如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
//省略……
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
//省略……
}
}
在静态内部类WebMvcAutoConfigurationAdapter上,又通过通过@Import注解引入了EnableWebMvcConfiguration配置类,而ExceptionHandlerExceptionResolver的初始化就是在该配置类中进行的,如下所示:
EnableWebMvcConfiguration配置类,也是WebMvcAutoConfiguration类的内部类。
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
// 省略……
@Override
protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
if (this.mvcRegistrations != null && this.mvcRegistrations.getExceptionHandlerExceptionResolver() != null) {
return this.mvcRegistrations.getExceptionHandlerExceptionResolver();
}
return super.createExceptionHandlerExceptionResolver();
}
//增强ExceptionHandlerExceptionResolver 类,主要扩展日志能力
@Override
protected void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
super.extendHandlerExceptionResolvers(exceptionResolvers);
if (this.mvcProperties.isLogResolvedException()) {
for (HandlerExceptionResolver resolver : exceptionResolvers) {
if (resolver instanceof AbstractHandlerExceptionResolver) {
((AbstractHandlerExceptionResolver) resolver).setWarnLogCategory(resolver.getClass().getName());
}
}
}
}
}
在EnableWebMvcConfiguration 内部类中提供了createExceptionHandlerExceptionResolver()方法用于创建ExceptionHandlerExceptionResolver 对象,但是该方法是什么时候被调用的呢?其实是在祖先类WebMvcConfigurationSupport(EnableWebMvcConfiguration->DelegatingWebMvcConfiguration->WebMvcConfigurationSupport,)中实现初始化调用的,代码如下:
@Bean
public HandlerExceptionResolver handlerExceptionResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
上述方法被@Bean注解,所以在初始化时,会创建HandlerExceptionResolver 对象,这里实际上创建的是HandlerExceptionResolverComposite 对象,用于组合使用各种HandlerExceptionResolver对象。其中configureHandlerExceptionResolvers()和extendHandlerExceptionResolvers()两个方法都是空方法,主要用于扩展,而addDefaultHandlerExceptionResolvers()方法用于创建各种HandlerExceptionResolver实例,具体实现如下:
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
ContentNegotiationManager mvcContentNegotiationManager) {
ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
exceptionHandlerResolver.setMessageConverters(getMessageConverters());
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
exceptionHandlerResolver.setResponseBodyAdvice(
Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
if (this.applicationContext != null) {
exceptionHandlerResolver.setApplicationContext(this.applicationContext);
}
exceptionHandlerResolver.afterPropertiesSet();
exceptionResolvers.add(exceptionHandlerResolver);
ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
responseStatusResolver.setMessageSource(this.applicationContext);
exceptionResolvers.add(responseStatusResolver);
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
在添加异常处理器的过程中,默认会创建ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver 和DefaultHandlerExceptionResolver三个。其中创建ExceptionHandlerExceptionResolver异常处理器时,又会执行afterPropertiesSet()方法(实现InitializingBean接口),该方法用来完成ExceptionHandlerExceptionResolver实例的初始化,这里其实主要就是用来初始化@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() {
if (getApplicationContext() == null) {
return;
}
//查找@ControllerAdvice注解的实例
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");
}
}
}
在initExceptionHandlerAdviceCache()方法中,查找@ControllerAdvice注解的实例集合,并进行迭代,在迭代过程中,会根据beanType创建ExceptionHandlerMethodResolver对象。创建对象的过程中,会检测被@ExceptionHandler注解的方法,并把其添加到mappedMethods变量中,供后续使用,具体实现如下:
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 + "}");
}
}
至此,经过上述的过程,@ControllerAdvice+@ExceptionHandler注解就被初始化了。
2.2、运行机制
经过前面的过程,已经完成了初始化。那么,当出现异常时,异常处理器又是如何工作的呢?我们接下来一探究竟。
为了一探究竟,我们从DispatcherServlet类的doDispatch()方法开始进行分析,该方法是SpringMVC进行请求处理的入口(该方法在doService()方法中被调用)。为了分析异常处理器是如何生效的,我们这里把其中相关的代码片段截取了出来,如下所示:
//DispatcherServlet类
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//省略……
Exception dispatchException = null;
try {
}catch (Exception ex) {
dispatchException = ex;
}catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
//省略……
}
在上述方法中,通过catch代码块会捕获异常,并赋值给dispatchException对象,当然如果没有异常发生,该对象为null,然后再调用processDispatchResult()方法继续执行相关逻辑(无论是否有异常都会执行)。
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
在processDispatchResult()方法中,首先判断是否发生了异常,即exception 是否为null,如果没有发生异常,直接执行后续的页面渲染等操作,否则就会执行异常处理逻辑:首先判断exception异常类型,如果是ModelAndViewDefiningException类型的异常,直接获取对应ModelAndView对象,然后交由后续的页面渲染逻辑等进行处理,否则就通过processHandlerException()方法进一步进行异常逻辑处理。
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
else if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}
在processHandlerException()方法中,就使用到了前面初始化时创建的HandlerExceptionResolver实例了,默认使用了HandlerExceptionResolverComposite对象,即通过HandlerExceptionResolverComposite类的resolveException()方法进行异常处理,实现如下:
@Override
@Nullable
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;
}
因为HandlerExceptionResolverComposite就是为了组合各种异常处理器,所以在上述方法中,实际上还是迭代各个异常处理器的resolveException()方法进行处理,只要解析到第一个符合要求的ModelAndView 对象就直接返回。这里默认使用了ExceptionHandlerExceptionResolver实例对象,即通过该对象的resolveException()方法进行异常处理(实际在抽象类AbstractHandlerExceptionResolver中定义),具体实现如下:
//AbstractHandlerExceptionResolver类
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
// Print debug message when warn logger is not enabled.
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
}
// Explicitly configured warn logger in logException method.
logException(ex, request);
}
return result;
}
else {
return null;
}
}
在resolveException()方法中,又通过调用doResolveException()方法进行异常处理,该方法定义在AbstractHandlerMethodExceptionResolver类中,其中又调用了doResolveHandlerMethodException()方法,该方法定义在ExceptionHandlerExceptionResolver类中,实现如下:
//AbstractHandlerMethodExceptionResolver类
@Override
@Nullable
protected final ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}
//ExceptionHandlerExceptionResolver类
@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 {
if (logger.isDebugEnabled()) {
logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
}
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) {
if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) {
logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
}
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;
}
}
在doResolveHandlerMethodException()方法中,通过exceptionHandlerMethod的invokeAndHandle()方法就会调用到定义注解@ExceptionHandler的方法,然后根据mavContainer相关信息生成对应ModelAndView并进行返回,最后回到processDispatchResult()方法继续后续逻辑的执行。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68732.html