全局异常处理的原理

Spring 全局异常处理

首先需要确定的是,并没有用到 动态代理 。

请求被全局异常处理的过程

DispatcherServlet.class

请求进入DispatcherServlet#doDispatch方法中,如果出现参数缺失,异常类型=NoResourceFoundException。

HandlerExceptionResolverComposite.class

异常信息一直传递到HandlerExceptionResolverComposite.class#resolveException中,此时会遍历resolvers进行处理,如下图:

ExceptionHandlerExceptionResolver.class

异常信息传递到ExceptionHandlerExceptionResolver.class#doResolveHandlerMethodException

java 复制代码
// 这里会寻找开发写的@ExceptionHandle(某种异常.class),异常的种类如果是exception的父类或者同样的类算作匹配并返回对应的异常处理方法
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
...
// 调用开发写的全局异常处理方法
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);

有关内容在启动时的处理

D:\JavaKFHJ\repository\org\springframework\spring-webmvc\6.1.6\spring-webmvc-6.1.6.jar!\org\springframework\web\servlet\config\annotation\WebMvcConfigurationSupport.class#handlerExceptionResolver注册一个名为"handlerExceptionResolver"的bean,如下:

java 复制代码
@Bean
	public HandlerExceptionResolver handlerExceptionResolver(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
		List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
		configureHandlerExceptionResolvers(exceptionResolvers);
		if (exceptionResolvers.isEmpty()) {
		  // 经过这一步处理后exceptionResolvers的长度变成了3
			addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
		}
		extendHandlerExceptionResolvers(exceptionResolvers);
		HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
		composite.setOrder(0);
		composite.setExceptionResolvers(exceptionResolvers);
		return composite;
	}

D:\JavaKFHJ\repository\org\springframework\spring-webmvc\6.1.6\spring-webmvc-6.1.6.jar!\org\springframework\web\servlet\config\annotation\WebMvcConfigurationSupport.class#addDefaultHandlerExceptionResolvers,在这个方法的代码中创建了3个HandlerExceptionResolver,如下:

java 复制代码
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());
	}

以exceptionHandlerResolver.afterPropertiesSet();作为调用入口,调用D:\JavaKFHJ\repository\org\springframework\spring-webmvc\6.1.6\spring-webmvc-6.1.6.jar!\org\springframework\web\servlet\mvc\method\annotation\ExceptionHandlerExceptionResolver.class#initExceptionHandlerAdviceCache,如下:

java 复制代码
private void initExceptionHandlerAdviceCache() {
		if (getApplicationContext() == null) {
			return;
		}

     // 这里的adviceBeans 会找到包含开发自己创建的全局异常处理器类
		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");
			}
		}
	}

D:\JavaKFHJ\repository\org\springframework\spring-web\6.1.6\spring-web-6.1.6.jar!\org\springframework\web\method\ControllerAdviceBean.class#findAnnotatedBeans会找到由@ControllerAdvice标注的类并返回,如下:

java 复制代码
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
		ListableBeanFactory beanFactory = context;
		if (context instanceof ConfigurableApplicationContext cac) {
			// Use internal BeanFactory for potential downcast to ConfigurableBeanFactory above
			// 从context中取出beanFactory
			beanFactory = cac.getBeanFactory();
		}
		List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
		// 取出所有bean的名字列表
		for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Object.class)) {
			if (!ScopedProxyUtils.isScopedTarget(name)) {
			// 如果这个bean上有@ControllerAdvice的注解
				ControllerAdvice controllerAdvice = beanFactory.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, beanFactory, controllerAdvice));
				}
			}
		}
		OrderComparator.sort(adviceBeans);
		return adviceBeans;
	}

找到了开发自定义的全局异常处理器,有了这个类,开发写的由@ExceptionHandler注解的对于不同类型异常的处理方法也算已知了

相关推荐
聪明的笨猪猪18 分钟前
Java 工程案例:AI 生成 @Async 异步线程池踩坑
java·经验分享·面试·ai编程
学长毕设34 分钟前
基于SpringBoot公务员考试交流平台-附源码
java·spring boot·后端
SL_staff3 小时前
私有化低代码:面向强监管行业的可控数字化实践
java·低代码
饮水桶_T3 小时前
JAVA IO流笔记
开发语言·数据结构·算法
三翼鸟数字化技术团队4 小时前
高性能存储引擎RocksDB:原理与Java生态实践
java
SL_staff4 小时前
JVS-APS有限产能建模的七个刚性支点:面向产线真实约束的技术实现解析
java·开源·全栈
特立独行的猫A4 小时前
少 3 个环节、少 1 次 ABI、少 3500 行代码:cj-tauri 与 Ionic/Capacitor 的鸿蒙桥接机制对比
java·后端·python
学长毕设4 小时前
基于SpringBoot的高校后勤报修管理系统-附源码
java·spring boot·后端
特立独行的猫A4 小时前
cj-tauri vs Rust/Tauri 同款播放器实测:编译 21 秒 vs 11 分钟,磁盘 12 MB vs 1.2 GB
java·后端·python
杨凯凡5 小时前
Spring AI RAG 实战:向量检索、租户过滤、引用溯源与防幻觉边界
java·人工智能·spring