最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring?MVC注解@ControllerAdvice的工作原理解讀

 更新時間:2025年08月25日 17:19:09   作者:安迪源文  
本文解析SpringMVC中@ControllerAdvice注解,通過結(jié)合@ExceptionHandler、@ModelAttribute、@InitBinder實現(xiàn)全局異常處理、數(shù)據(jù)模型和綁定配置,解析其通過容器初始化和緩存機制提取并應(yīng)用定義的原理

Spring MVC中,通過組合使用注解@ControllerAdvice和其他一些注解,我們可以為開發(fā)人員實現(xiàn)的控制器類做一些全局性的定制,具體來講,可作如下定制 :

  • 結(jié)合@ExceptionHandler使用 ==> 添加統(tǒng)一的異常處理控制器方法
  • 結(jié)合@ModelAttribute使用 ==> 使用共用方法添加渲染視圖的數(shù)據(jù)模型屬性
  • 結(jié)合@InitBinder使用 ==> 使用共用方法初始化控制器方法調(diào)用使用的數(shù)據(jù)綁定器

數(shù)據(jù)綁定器涉及到哪些參數(shù)/屬性需要/不需要綁定,設(shè)置數(shù)據(jù)類型轉(zhuǎn)換時使用的PropertyEditor,Formatter等。

那么,@ControllerAdvice的工作原理又是怎樣的呢 ?這篇文章,我們就一探究竟。

注解@ControllerAdvice是如何被發(fā)現(xiàn)的 ?

首先,容器啟動時,會定義類型為RequestMappingHandlerAdapterbean組件,這是DispatcherServlet用于執(zhí)行控制器方法的HandlerAdapter,它實現(xiàn)了接口InitializingBean,所以自身在初始化時其方法#afterPropertiesSet會被調(diào)用執(zhí)行。

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

       // 省略掉無關(guān)代碼 
		// ...
	}

從以上代碼可以看出,RequestMappingHandlerAdapter bean組件在自身初始化時調(diào)用了#initControllerAdviceCache,從這個方法的名字上就可以看出,這是一個ControllerAdvice相關(guān)的初始化函數(shù),而#initControllerAdviceCache具體又做了什么呢?

我們繼續(xù)來看 :

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

       // 找到所有使用了注解 @ControllerAdvice 的bean組件 
		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
        // 排序
		AnnotationAwareOrderComparator.sort(adviceBeans);


       // this. requestResponseBodyAdvice : 
       // 		用于記錄所有 @ControllerAdvice + RequestBodyAdvice/ResponseBodyAdvice bean 
       // this.modelAttributeAdviceCache : 
       // 		用于記錄所有 @ControllerAdvice bean組件中的 @ModuleAttribute 方法
       // this.initBinderAdviceCache : 
       // 	用于記錄所有 @ControllerAdvice bean組件中的 @InitBinder 方法
       // 用于臨時記錄所有 @ControllerAdvice + RequestResponseBodyAdvice bean
		List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

       // 遍歷每個使用了注解 @ControllerAdvice 的 bean 組件
		for (ControllerAdviceBean adviceBean : adviceBeans) {
			Class<?> beanType = adviceBean.getBeanType();
			if (beanType == null) {
				throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
			}
           // 獲取當(dāng)前  ControllerAdviceBean 中所有使用了 @ModelAttribute 注解的方法
			Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
			if (!attrMethods.isEmpty()) {
				this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
			}
            
           // 獲取當(dāng)前 ControllerAdviceBean 中所有使用了 @InitMethod 注解的方法
			Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
			if (!binderMethods.isEmpty()) {
				this.initBinderAdviceCache.put(adviceBean, binderMethods);
			}
           // 如果當(dāng)前 ControllerAdviceBean 繼承自 RequestBodyAdvice,將其登記到 requestResponseBodyAdviceBeans
			if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
				requestResponseBodyAdviceBeans.add(adviceBean);
			}
            
           // 如果當(dāng)前 ControllerAdviceBean 繼承自 ResponseBodyAdvice,將其登記到 requestResponseBodyAdviceBeans  
			if (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");
			}
		}
	}

從以上#initControllerAdviceCache方法的實現(xiàn)邏輯來看,它將容器中所有使用了注解@ControllerAdvicebean或者其方法都分門別類做了統(tǒng)計,記錄到了RequestMappingHandlerAdapter實例的三個屬性中 :

requestResponseBodyAdvice

  • 用于記錄所有@ControllerAdvice + RequestBodyAdvice/ResponseBodyAdvice bean組件

modelAttributeAdviceCache

  • 用于記錄所有 @ControllerAdvice bean組件中的 @ModuleAttribute 方法

initBinderAdviceCache

  • 用于記錄所有@ControllerAdvice bean組件中的 @InitBinder 方法

到此為止,我們知道,使用注解@ControllerAdvicebean中的信息被提取出來了,但是,這些信息又是怎么使用的呢 ?我們繼續(xù)來看。

@ControllerAdvice定義信息的使用

requestResponseBodyAdvice的使用

	/**
	 * Return the list of argument resolvers to use including built-in resolvers
	 * and custom resolvers provided via {@link #setCustomArgumentResolvers}.
	 */
	private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

		// ... 省略無關(guān)代碼        
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), 
			this.requestResponseBodyAdvice));
        // ... 省略無關(guān)代碼
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), 
			this.requestResponseBodyAdvice));		
        // ... 省略無關(guān)代碼
		resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), 
			this.requestResponseBodyAdvice));
		// ... 省略無關(guān)代碼

		return resolvers;
	}

#getDefaultArgumentResolvers方法用于準備RequestMappingHandlerAdapter執(zhí)行控制器方法過程中缺省使用的HandlerMethodArgumentResolver,從上面代碼可見,requestResponseBodyAdvice會被傳遞給RequestResponseBodyMethodProcessor/RequestPartMethodArgumentResolver/HttpEntityMethodProcessor這三個參數(shù)解析器,不難猜測,它們在工作時會使用到該requestResponseBodyAdvice,但具體怎么使用,為避免過深細節(jié)影響理解,本文我們不繼續(xù)展開。

方法#getDefaultArgumentResolvers也在RequestMappingHandlerAdapter初始化方法中被調(diào)用執(zhí)行,如下所示 :

	@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);
		}
	
        // 省略無關(guān)代碼
	}

modelAttributeAdviceCache的使用

	private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
		SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
		Class<?> handlerType = handlerMethod.getBeanType();
		Set<Method> methods = this.modelAttributeCache.get(handlerType);
		if (methods == null) {
			// 獲取當(dāng)前控制器類中使用了 @ModelAttribute 的方法
			methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
			this.modelAttributeCache.put(handlerType, methods);
		}
		List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
		// Global methods first
		// 遍歷@ControllerAdvice bean中所有使用了 @ModelAttribute 的方法,
		// 將其包裝成 InvocableHandlerMethod 放到 attrMethods
		// ********* 這里就是 modelAttributeAdviceCache 被使用到的地方了 ************
		this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
			if (clazz.isApplicableToBeanType(handlerType)) {
				Object bean = clazz.resolveBean();
				for (Method method : methodSet) {
					attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
				}
			}
		});
        
		// 遍歷當(dāng)前控制器類中中所有使用了 @ModelAttribute 的方法,
		// 也將其包裝成 InvocableHandlerMethod 放到 attrMethods        
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
		}
        
		// 此時  attrMethods 包含了兩類 InvocableHandlerMethod, 分別來自于 :
		// 1. @ControllerAdvice bean 中所有使用了 @ModelAttribute 的方法
		// 2. 當(dāng)前控制器類中中所有使用了 @ModelAttribute 的方法
		return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
	}
	
   // 從指定 bean 的方法 method ,其實是一個使用了注解 @ModelAttribute 的方法,
   // 構(gòu)造一個 InvocableHandlerMethod 對象
	private InvocableHandlerMethod createModelAttributeMethod(WebDataBinderFactory factory, 
			Object bean, Method method) {
		InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(bean, method);
		if (this.argumentResolvers != null) {
			attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}
		attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
		attrMethod.setDataBinderFactory(factory);
		return attrMethod;
	}	

從此方法可以看到,#getModelFactory方法使用到了modelAttributeAdviceCache,它會根據(jù)其中每個元素構(gòu)造成一個InvocableHandlerMethod,最終傳遞給要創(chuàng)建的ModelFactory對象。而#getModelFactory又在什么時候被使用呢 ? 它會在RequestMappingHandlerAdapter執(zhí)行一個控制器方法的準備過程中被調(diào)用,如下所示 :

	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
          // 構(gòu)造調(diào)用 handlerMethod 所要使用的數(shù)據(jù)綁定器工廠  
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
          // 構(gòu)造調(diào)用 handlerMethod 所要使用的數(shù)據(jù)模型工廠  
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
        // 省略無關(guān)代碼 ...            
  }

initBinderAdviceCache的使用

	private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
		Class<?> handlerType = handlerMethod.getBeanType();
		Set<Method> methods = this.initBinderCache.get(handlerType);
		if (methods == null) {
          // 獲取當(dāng)前控制器類中使用了 @InitBinder 的方法  
			methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
			this.initBinderCache.put(handlerType, methods);
		}
		List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
		// Global methods first
		// 遍歷@ControllerAdvice bean中所有使用了 @InitBinder 的方法,
		// 將其包裝成 InvocableHandlerMethod 放到 initBinderMethods
		// ********* 這里就是 initBinderAdviceCache 被使用到的地方了 ************        
		this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
			if (clazz.isApplicableToBeanType(handlerType)) {
				Object bean = clazz.resolveBean();
				for (Method method : methodSet) {
					initBinderMethods.add(createInitBinderMethod(bean, method));
				}
			}
		});
        
		// 遍歷當(dāng)前控制器類中所有使用了 @InitBinder 的方法,
		// 將其包裝成 InvocableHandlerMethod 放到 initBinderMethods        
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			initBinderMethods.add(createInitBinderMethod(bean, method));
		}
        
		// 此時  initBinderMethods 包含了兩類 InvocableHandlerMethod, 分別來自于 :
		// 1. @ControllerAdvice bean 中所有使用了 @InitBinder 的方法
		// 2. 當(dāng)前控制器類中中所有使用了 @InitBinder 的方法        
		return createDataBinderFactory(initBinderMethods);
	}
    
   // 從指定 bean 的方法 method ,其實是一個使用了注解 @InitBinder 的方法,
   // 構(gòu)造一個 InvocableHandlerMethod 對象
	private InvocableHandlerMethod createInitBinderMethod(Object bean, Method method) {
		InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method);
		if (this.initBinderArgumentResolvers != null) {
			binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
		}
		binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
		binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
		return binderMethod;
	}

	/**
	 * Template method to create a new InitBinderDataBinderFactory instance.
	 * <p>The default implementation creates a ServletRequestDataBinderFactory.
	 * This can be overridden for custom ServletRequestDataBinder subclasses.
	 * @param binderMethods {@code @InitBinder} methods
	 * @return the InitBinderDataBinderFactory instance to use
	 * @throws Exception in case of invalid state or arguments
	 */
	protected InitBinderDataBinderFactory createDataBinderFactory(
											List<InvocableHandlerMethod> binderMethods)
			throws Exception {

		return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
	}

從此方法可以看到,#getDataBinderFactory方法使用到了initBinderAdviceCache,它會根據(jù)其中每個元素構(gòu)造成一個InvocableHandlerMethod,最終傳遞給要創(chuàng)建的InitBinderDataBinderFactory對象。而#getDataBinderFactory又在什么時候被使用呢 ? 它會在RequestMappingHandlerAdapter執(zhí)行一個控制器方法的準備過程中被調(diào)用,如下所示 :

	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
          // 構(gòu)造調(diào)用 handlerMethod 所要使用的數(shù)據(jù)綁定器工廠  
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
          // 構(gòu)造調(diào)用 handlerMethod 所要使用的數(shù)據(jù)模型工廠  
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
        // 省略無關(guān)代碼 ...            
  }

到此為止,我們基本上可以看到,通過@ControllerAdvice注解的bean組件所定義的@ModelAttribute/@InitBinder方法,或者RequestBodyAdvice/ResponseBodyAdvice,是如何被RequestMappingHandlerAdapter提取和使用的了。雖然我們并未深入到更細微的組件研究它們最終的使用,不過結(jié)合這些組件命名以及這些更深一層的使用者組件的名稱,即便是猜測,相信你也不難理解猜到它們?nèi)绾伪皇褂昧恕?/p>

不知道你注意到?jīng)]有,關(guān)于@ControllerAdvice@ExceptionHandler這一組合,在上面提到的RequestMappingHandlerAdapter邏輯中,并未涉及到。那如果使用了這種組合,又會是怎樣一種工作機制呢 ?

事實上,@ControllerAdvice@ExceptionHandler這一組合所做的定義,會被ExceptionHandlerExceptionResolver消費使用。

不過關(guān)于ExceptionHandlerExceptionResolver我們會另外行文介紹,通過這篇文章中的例子,理解@ControllerAdvide的工作原理已經(jīng)不是問題了。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringBoot之RabbitMQ的使用方法

    SpringBoot之RabbitMQ的使用方法

    這篇文章主要介紹了SpringBoot之RabbitMQ的使用方法,詳細的介紹了2種模式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-12-12
  • SpringBoot?@DS注解實現(xiàn)多數(shù)據(jù)源配置以及問題解決辦法

    SpringBoot?@DS注解實現(xiàn)多數(shù)據(jù)源配置以及問題解決辦法

    這篇文章主要給大家介紹了關(guān)于SpringBoot?@DS注解實現(xiàn)多數(shù)據(jù)源配置以及問題解決辦法,所謂多數(shù)據(jù)源就是一個Java EE項目中采用了不同數(shù)據(jù)庫實例中的多個庫,或者是同一個數(shù)據(jù)庫實例中的多個不同庫,需要的朋友可以參考下
    2023-11-11
  • SpringCloud中的Seata基本介紹與安裝教程

    SpringCloud中的Seata基本介紹與安裝教程

    Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能和簡單易用的分布式事務(wù)服務(wù),這篇文章主要介紹了SpringCloud之Seata基本介紹與安裝,需要的朋友可以參考下
    2024-01-01
  • 在SpringBoot中如何使用HttpClient實現(xiàn)HTTP請求

    在SpringBoot中如何使用HttpClient實現(xiàn)HTTP請求

    本文介紹了如何使用Apache?HttpClient來訪問HTTP協(xié)議的資源,并提供了詳細的使用示例,包括GET和POST請求的無參和有參調(diào)用
    2025-02-02
  • Java實例域初始化方法及順序

    Java實例域初始化方法及順序

    這篇文章主要介紹了Java實例域初始化方法及順序,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-05-05
  • SpringBoot3集成MyBatis詳解

    SpringBoot3集成MyBatis詳解

    MyBatis是一款開源的持久層框架,它極大地簡化了與數(shù)據(jù)庫的交互流程,MyBatis更具靈活性,允許開發(fā)者直接使用SQL語句與數(shù)據(jù)庫進行交互,本文將詳細介紹在Spring Boot項目中如何集成MyBatis,以實現(xiàn)對數(shù)據(jù)庫的輕松訪問和操作,需要的朋友可以參考下
    2023-12-12
  • sql于navicat中能運行在mybatis中不能運行的解決方案

    sql于navicat中能運行在mybatis中不能運行的解決方案

    這篇文章主要介紹了sql于navicat中能運行在mybatis中不能運行的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • java實現(xiàn)文件上傳下載功能

    java實現(xiàn)文件上傳下載功能

    這篇文章主要介紹了java實現(xiàn)文件上傳下載功能,上傳單個或多個文件,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Java實現(xiàn)斷點下載服務(wù)端與客戶端的示例代碼

    Java實現(xiàn)斷點下載服務(wù)端與客戶端的示例代碼

    這篇文章主要為大家介紹了如何實現(xiàn)服務(wù)端(Spring Boot)與客戶端(Android)的斷點下載與下載續(xù)傳功能,文中的示例代碼講解詳細,需要的可以參考一下
    2022-08-08
  • mybatis Plus 多表聯(lián)合查詢的實現(xiàn)示例

    mybatis Plus 多表聯(lián)合查詢的實現(xiàn)示例

    這篇文章主要介紹了mybatis Plus 多表聯(lián)合查詢的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09

最新評論

宜宾县| 灌南县| 孝昌县| 安丘市| 秭归县| 寻甸| 咸阳市| 仁布县| 南木林县| 梧州市| 贡觉县| 庄浪县| 舒兰市| 淳安县| 怀集县| 樟树市| 岱山县| 和平区| 略阳县| 长沙市| 枣阳市| 南安市| 九台市| 伊吾县| 眉山市| 兰考县| 达拉特旗| 霍城县| 黄陵县| 偃师市| 本溪| 曲水县| 吉首市| 嵊州市| 通渭县| 镇安县| 晋州市| 广州市| 文昌市| 铜山县| 商河县|