第三十一讲-ControllerAdvice之@ExceptionHandler

第三十一讲-ControllerAdvice之@ExceptionHandler

本将我们来看一下ControllerAdvice的第四种增强方式:通过在ControllerAdvice类中添加@ExceptionHandler的方法来达到全局处理异常的目的。

我们希望不为每一个控制器创建一个异常处理方法,而是使用同一个异常处理方法作为通用的异常处理方法,这里呢,我们可以使用@ControllerAdvice配合@ExceptionHandler一起使用,如下面的示例代码:

public class A31 {
    public static void main(String[] args) throws NoSuchMethodException {
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();

    //        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
    //        resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
    //        resolver.afterPropertiesSet();

        // 从spring容器中获取ExceptionHandlerExceptionResolver异常处理解析器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        ExceptionHandlerExceptionResolver resolver = context.getBean(ExceptionHandlerExceptionResolver.class);

        HandlerMethod handlerMethod = new HandlerMethod(new Controller5(), Controller5.class.getMethod("foo"));
        Exception e = new Exception("e1");
        resolver.resolveException(request, response, handlerMethod, e);
        System.out.println(new String(response.getContentAsByteArray(), StandardCharsets.UTF_8));
    }

    static class Controller5 {
        public void foo() {

        }
    }
}


@Configuration
public class WebConfig {
    @ControllerAdvice
    static class MyControllerAdvice {
        @ExceptionHandler
        @ResponseBody
        public Map<String, Object> handle(Exception e) {
            return Map.of("error", e.getMessage());
        }
    

    @Bean
    public ExceptionHandlerExceptionResolver resolver() {
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        resolver.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
        return resolver;
    }
}

运行结果如下:

19:42:51.463 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - ControllerAdvice beans: 1 @ExceptionHandler, 0 ResponseBodyAdvice
19:42:51.526 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Using @ExceptionHandler com.cherry.a31.WebConfig$MyControllerAdvice#handle(Exception)
19:42:51.547 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Using 'application/json', given [*/*] and supported [application/json, application/*+json]
19:42:51.548 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Writing [{error=e1}]
19:42:51.568 [main] DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Resolved [java.lang.Exception: e1]

{"error":"e1"}

我们成功定义了一个全局异常处理器。

此时的异常处理器运行流程是怎么样的呢?我们来分析一下:

  • 我们在处理异常的时候,会优先在其对应的Controller中去找异常处理方法(@ExceptionHandler注解标记的方法)。

  • 如果Controller中没有被@ExceptionHandler标注的方法。异常处理器就会找到所有由@ControllerAdvice所标注的类中所有由@ExceptionHandler注解标注的方法作为异常处理的方法,并且是全局的控制器异常处理方法!

那异常处理器中是在什么时候找到由@ControllerAdvice类中标注的@ExceptionHandler异常处理方法的呢?其实还是在afterPropertiesSet中找到的:

	@Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBodyAdvice beans
        // 初始化从Spring容器中获取由2ControllerAdvice注解标注的类中由@ExceptionHandler注解标注的异常方法!
		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);
		}
	}

我们可以看一下initExceptionHandlerAdviceCache这个方法:

private void initExceptionHandlerAdviceCache() {
    if (getApplicationContext() == null) {
        return;
    }

	// 从容器中获取由@ConrtollerAdvice注解标注的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);
        }
        
        // 在Bean上找到哪些方法中标注了@ExceptionHandler,并将这些方法记录下来存储到异常处理解析器ExceptionHandlerMethodResolver中
        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");
        }
    }
}

当然,除了ExceptionHandlerExceptionResover中的afterPropertesSet方法中添加了对异常处理,常见的参数解析器,返回值处理器,其实HandlerAdapter中的afterPropertesSet方法也加入了了异常处理器,常见的参数解析器,返回值处理器。我们可以看一下下面的代码:

// RequestMappingHandlerAdapter.java

@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    initControllerAdviceCache();

    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.initBinderArgumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}
posted @   LilyFlower  阅读(4)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示