06-HandlerAdapter

HandlerAdapter

  • HandlerAdapter 的作用是封裝 HandlerMapping 所獲取到的 handler ,並調用 handler 處理清除,返回 ModelAndView 對象。

一、HandlerAdapter

1.1 HandlerAdapter設計思想?

  • HandlerAdapter 是處理器適配器,它採用適配器模式來適配不同類型的 handler 請求處理器,這裏首先思考爲什麼要這樣設計,在 DispatchServlet 中,DispatchServlet直接獲取到 handler 之後處理請求不行嗎?爲什麼還需將 handler 包裝成 HandlerAdapter 之後再進入處理請求的邏輯。這裏是爲了解耦,試想,假設沒有 HandlerAdapter ,並且我們有三種 handler類型,那麼三種類型來處理請求的方式各不一樣,代碼很可能是如下形式:
if(handler instanceof Servlet){
    (Servlet)handler.service();
}else if(handler instanceof HandlerMethod){
    (ServletInvocableHandlerMethod)handler.invokeAndHandle();
}else if (handler instanceof Controller){
    ((Controller) handler).handleRequest();
}
  • 那當我們後續增加一種類型的時候呢?勢必這段邏輯是需要修改的,違反開閉原則(對擴展開發,對修改關閉);反之有了HandlerAdapter之後,不管什麼處理器,都需要實現 HandlerAdapter 的接口標準,DispatchServlet只需要找到支持 handler 的
    HandlerAdapter就行了,然後調用 HandlerAdapter 中定義好的接口方法即可,擴展也是沒有任何問題,這也是使用適配器模式的優勢。

1.2 接口定義

  • HandlerAdapter是一個接口,
public interface HandlerAdapter {

	//HandlerAdapter 是否支持該handler
	boolean supports(Object handler);

	//HandlerAdapter 處理請求,返回一個ModelAndView
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
    
    //和緩存有關,不支持的話可以簡單返回-1
	long getLastModified(HttpServletRequest request, Object handler);
}

1.3 初始化 HandlerAdapter

  • 前面文章分析過,在 DispatcherServlet 的初始化策略的時候會初始化 HandlerAdapter,如下:
	protected void initStrategies(ApplicationContext context) {
		//省略 ... 
		initHandlerAdapters(context);
		//省略 ... 
	}
  • initHandlerAdapters : 方法會首先判斷是否探測全部的HandlerAdapter,如果是,則則找出全部實現類,反之則找出指定實現類,如果沒找到就加載默認策略。
private void initHandlerAdapters(ApplicationContext context) {
		this.handlerAdapters = null;
        //1.如果檢測全部的HandlerAdapters,就找出全部匹配的
		if (this.detectAllHandlerAdapters) {
			// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerAdapter> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerAdapters = new ArrayList<>(matchingBeans.values());
				// We keep HandlerAdapters in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerAdapters);
			}
		}
		else {
		    //2.如果不檢測全部的,就找出指定的
			try {
				HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
				this.handlerAdapters = Collections.singletonList(ha);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerAdapter later.
			}
		}

		//3.如果麼有找到,就加載默認的
		if (this.handlerAdapters == null) {
			this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
		}
	}
  • PS:
 detectAllHandlerAdapters 對應一個配置,默認爲true,加載全部 HandlerAdapter 類型的實例

1.4 獲取 HandlerAdapter

  • DispatcherServlet 在 doDispatch 方法中處理請求時獲取 HandlerAdapter,通過 getHandlerAdapter 方法獲取:
	//DispatcherServlet#getHandlerAdapter
	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		//1.遍歷初始化好的策略屬性handlerAdapters
		for (HandlerAdapter ha : this.handlerAdapters) {
		    //2.找到支持目標處理器的則返回
			if (ha.supports(handler)) {
				return ha;
			}
		}
		//3.沒有則拋出異常
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}
  • 代碼流程很清晰,從策略屬性 handlerAdapters 集合中遍歷,尋找能夠支持 handler 處理器的適配器,尋找到了就返回,最後也是由這個Adapter適配器去負責處理目標方法,加鎖我們新增了一種 handler 處理器類型,那麼針對該類的寫一種適配器實現就行了,對於原本的代碼沒有任何影響。

二、HandlerAdapter 繼承體系

  • 下面是HandlerAdapter的子類繼承關係

在這裏插入圖片描述

2.1 AbstractHandlerMethodAdapter

  • 抽象類 , HandlerAdapter的抽象實現,支持 HandlerMethod

2.2 SimpleControllerHandlerAdapter

  • 簡單控制器處理器適配器,適配 Controller 類型的 handler。這裏的控制器的實現是一個簡單的控制器接口的實現,代碼比較簡單,核心方法如下。
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

		return ((Controller) handler).handleRequest(request, response);
	}

2.3 HttpRequestHandlerAdapter

  • HTTP請求處理器適配器,適配 HttpRequestHandler 類型的處理器。它簡單的將HTTP請求對象和響應對象傳遞給HTTP請求處理器的實現,不需要返回值,它主要應用於基於HTTP的遠程調用的實現上,代碼也比較簡單,核心代碼如下:
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}

2.4 SimpleServletHandlerAdapter

  • 適配 Servlet 類型的 handler
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		((Servlet) handler).service(request, response);
		return null;
	}

2.5 AnnotationMethodHandlerAdapter

  • 已經被標記 @Deprecated ,就不關注了;

2.6 RequestMappingHandlerAdapter

  • 非抽象類,繼承自AbstractHandlerMethodAdapter , AbstractHandlerMethodAdapter的擴展、支持 @RequestMapping 的HandlerMethod , 雖然寫在最後,但卻是最需要關注的一個實現類;

三、RequestMappingHandlerAdapter 源碼分析

3.1 AbstractHandlerMethodAdapter

  • AbstractHandlerMethodAdapter 是接口的抽象骨架實現,定義了部分方法,代碼不多,註釋如下:
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {

    //最大數值代表最低優先級
	private int order = Ordered.LOWEST_PRECEDENCE;

    //構造方法
	public AbstractHandlerMethodAdapter() {
		// no restriction of HTTP methods by default
		super(false);
	}

    //設置優先級
	public void setOrder(int order) { this.order = order; }
	@Override
	public int getOrder() { return this.order;	}

    //判斷適配器是否支持對應的Handler處理器方法
	@Override
	public final boolean supports(Object handler) {
		return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
	}

	//子類重寫,判斷是否支持對應的處理器方法
	protected abstract boolean supportsInternal(HandlerMethod handlerMethod);

    //處理請求,注意參數的handler期望是一個HandlerMethod
	@Override
	@Nullable
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		return handleInternal(request, response, (HandlerMethod) handler);
	}

    //子類重寫,處理的核心方法
	@Nullable
	protected abstract ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;

	@Override
	public final long getLastModified(HttpServletRequest request, Object handler) {
		return getLastModifiedInternal(request, (HandlerMethod) handler);
	}

    //子類重寫,getLastModifiedInternal
	protected abstract long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod);
}
  • 查看 AbstractHandlerMethodAdapter 可知子類需要實現兩個關鍵的模板方法:supportsInternal 和 handleInternal ;
  • RequestMappingHandlerAdapter 是 HandlerAdapter 最複雜的一個子類,也是最需要關注分析的子類,源碼也較多,從 AbstractHandlerMethodAdapter 出發,我們關注幾個需要重寫的三個方法(都標記了子類重寫),在 RequestMappingHandlerAdapter 中 supportsInternal 和 getLastModifiedInternal 都是簡單實現,前者返回true,後者返回-1。因此主要分析 handleInternal 方法。

3.2 supportsInternal

	@Override
	protected boolean supportsInternal(HandlerMethod handlerMethod) {
		return true;
	}
  • supportsInternal 返回true表示,只要處理器類型是 HandlerMethod類型,RequestMappingHandlerAdapter 就能夠支持;

3.3 handleInternal

  • handleInternal 是 RequestMappingHandlerAdapter 處理請求的注意邏輯,大體分爲下面三步:
1.參數解析(解析參數,比較複雜)
2.使用處理器處理請求  (反射調用handleMethod )
3.處理返回值,(將不同類型的返回值統一處理成ModelAndView)
  • RequestMappingHandlerAdapter#handleInternal,我們先從整體上來看,方法的作用就是處理請求,然後返回一個 ModelAndView。
    //RequestMappingHandlerAdapter#handleInternal
    @Override
	protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
		//1.校驗請求方法檢查是否支持
		checkRequest(request);

		// Execute invokeHandlerMethod in synchronized block if required.
		// 需要對 session 進行同步處理
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// No synchronization on session demanded at all...
			//不需要對 session 進行同步處理就直接對 HandlerMethod 進行處理
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}
        
        // 請求頭沒有Cache-Control,直接返回,包含則需要處理
		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
		     
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}
        //返回模型示圖
		return mav;
	}
  • handleInternal 主要做了兩部分處理:一個是判斷當前是否對session進行同步處理,如果需要,則對其調用進行加鎖,反之直接調用;另一個是判斷請求頭中是否包含Cache-Control請求頭,如果不包含則設置其Cache立即失效。對於 HandlerMethod 的具體處理是在 invokeHandlerMethod 方法中進行的;

3.4 invokeHandlerMethod

  • invokeHandlerMethod 是處理請求的主體邏輯,對於 HandlerMethod 的調用處理:
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
		    //1.獲取全局的 InitBinder 和局部的 InitBinder,用於參數綁定, 這些方法用於參數綁定
		    //注意全局是在 @ControllerAdvice 類中聲明的 ,局部的是目標處理類中聲明的,比如@Controller中聲明的,
		    //具體可以參考4.2示例和第五大點初始化部分代碼解析
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			
			//2.獲取容器中全局的 ModelAttribute 和局部的 ModelAttribute,這些配置的方法將會在目標方法調用之前進行調用
			//注意全局是在 @ControllerAdvice 類中聲明的,局部的是目標處理類中聲明的,比如@Controller中聲明的,
			//具體可以參考4.2示例和第五大點初始化部分代碼解析
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

            //3.將 handlerMethod 封裝爲一個 ServletInvocableHandlerMethod 對象, 該對象用於對當前request的整體調用流程進行了封裝
			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			if (this.argumentResolvers != null) {
			   //4.設置當前容器中配置的所有 ArgumentResolver 參數解析器
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
			    //5.設置當前容器中配置的所有 ReturnValueHandler 返回值處理器
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			
			//6.將 WebDataBinderFactory 設置到 ServletInvocableHandlerMethod 中
			invocableMethod.setDataBinderFactory(binderFactory);
			
			//7.設置 ParameterNameDiscoverer ,用於發現參數的名稱
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			
			//8.initModel() 方法調用前面獲取到的 @ModelAttribute 標註的方法,使@ModelAttribute標註的方法能夠在目標Handler之前調用
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);


            //9.獲取當前的AsyncWebRequest,判斷目標 handler 的返回值是否爲 WebAsyncTask 或 DefferredResult
            //如果是二者之一則當前請求的處理是異步的。當前請求會將Controller中封裝的業務邏輯放到一個線程池中
            //進行調用,待該調用有返回結果之後再返回到response中。
            
            //異步的優點在於解放請求分發的線程,從而處理更多的請求,只有目標任務完成後纔會回來將該異步任務結果返回
			AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
			asyncWebRequest.setTimeout(this.asyncRequestTimeout);


            //10.封裝異步任務的線程池,request 和 interceptors 到 WebAsyncManager中
			WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
			asyncManager.setTaskExecutor(this.taskExecutor);
			asyncManager.setAsyncWebRequest(asyncWebRequest);
			asyncManager.registerCallableInterceptors(this.callableInterceptors);
			asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
            
            //11.判斷當前請求是否有異步任務結果,如果有結果則對異步任務結果進行封裝
			if (asyncManager.hasConcurrentResult()) {
				Object result = asyncManager.getConcurrentResult();
				mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
				asyncManager.clearConcurrentResult();
				 
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}

            //12.對請求參數進行處理,調用目標 HandlerMethod,並且將返回值封裝爲一個ModelAndView對象
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

            //13.對封裝的 ModelAndView 進行處理,判斷當前請求是否進行了重定向,如果進行了重定向,
            //還會判斷是否需要將 FlashAttributes 封裝到新的請求中
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
		    //14.調用request destruction callbacks和對SessionAttributes進行處理
			webRequest.requestCompleted();
		}
	}
  • invokeHandlerMethod 的大體處理流程包括 :
1.獲取 @InitBinder註解的的參數綁定轉換器
2.獲取當前容器中使用@ModelAttribute標註但沒有使用 @RequestMapping 標註的方法,並且在調用目標方法之前調用這些方法,參考4.2小結和第五點初始化部分代碼解析
3.判斷目標 handler 返回值是否使用了 WebAsyncTask 或 DefferredResult 封裝,如果封裝了,則按照異步任務的方式進行執行;
4.處理請求參數,調用目標方法和處理返回值。
  • 這裏面的代碼註釋大部分是參考了參考文章[4]的

3.5 getDataBinderFactory

  • getDataBinderFactory 處理標註@InitBinder的方法,用於參數綁定
	private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
		Class<?> handlerType = handlerMethod.getBeanType();
		//1.緩存中獲取當前 handler 所需要的 InitBinder 方法,第一次緩存是沒有的,這個緩存在Bean的生命周
		//期不會初始化,只會在第一次訪問的時候初始化好,後續使用就直接用緩存
		Set<Method> methods = this.initBinderCache.get(handlerType);
		if (methods == null) {
		    //2.緩存未找到,就掃描 handlerType 類型的Bean去尋找註解了 @InitBinder 的方法,因此此類方法需
		    //要在目標處理類裏面聲明,參考 4.1 
			methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
			this.initBinderCache.put(handlerType, methods);
		}
		
		//2.得到全部的 initBinderMethods 方法,先添加全局的,全局的是 @ControllerAdvice 類裏面去找,不過這裏全
		//局的已經初始化好在 initBinderAdviceCache 屬性裏面,只需要遍歷處理,至於全局的初始化時機參考第五點的分析
		List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
		// Global methods first
		this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
			if (clazz.isApplicableToBeanType(handlerType)) {
				Object bean = clazz.resolveBean();
				for (Method method : methodSet) {
					initBinderMethods.add(createInitBinderMethod(bean, method));
				}
			}
		});
		
		//3.再添加 HandlerMethod 所在Bean中的 InitBinder 方法,即局部的 InitBinder
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			initBinderMethods.add(createInitBinderMethod(bean, method));
		}
		//4.將 InitBinder 封裝到 InitBinderDataBinderFactory 中
		return createDataBinderFactory(initBinderMethods);
	}
  • getDataBinderFactory 方法主要用於參數綁定相關,內部會獲取全局和局部兩種類型的 InitBinder ,全局類型的 InitBinder 需要在類上使用@ControllerAdvice進行標註,並且聲明方法上使用 @InitBinder 進行標註;局部的是當前 handler 所在類中的使用 @InitBinder 標註的方法(比如Controller中的)。這兩種InitBinder都會執行,只不過全局類型的InitBinder會先於局部類型的InitBinder執行。

  • 參數綁定這塊代碼還是有點晦澀的,爲此在後面搭配了一個簡單的示例,在 4.1 的自定義參數綁定,結合調試看的會更清晰點。

3.6 getModelFactory

  • getModelFactory方法會獲取 @ModelAttribute 標註的方法,此類方法會在目標方法之前執行; (關於 @ModelAttribute 的方法可以參考4.2中的示例)
	private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
		SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
		Class<?> handlerType = handlerMethod.getBeanType();
		//1.和獲取綁定方法套路一樣,先從緩存獲取,第一次沒有緩存,後續都可以使用緩存加快速度
		Set<Method> methods = this.modelAttributeCache.get(handlerType);
		if (methods == null) {
			methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
			this.modelAttributeCache.put(handlerType, methods);
		}
		List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
		//2.先處理全局的
		// Global methods first
		this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
			if (clazz.isApplicableToBeanType(handlerType)) {
				Object bean = clazz.resolveBean();
				for (Method method : methodSet) {
					attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
				}
			}
		});
		
		//3.再到目標Bean處理局部的 	 
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
		}
		return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
	}
  • 和前面的 InitBinder 類似,全局的在 @ControllerAdvice 中,局部的是處理器所在的類中,全部找出包裝之後返回。

四、示例

4.1 自定義參數綁定

  • 自定義一個局部的參數綁定器, 代碼如下,其實就是將傳來的參數按照自己的方式用冒號分割,然後賦給屬性:
//參數類型
@Data
public class Person {
    private String username;
    private String address;
}

//控制器,接受 Person 類型的參數
@RestController
public class BinderTestController {

    @RequestMapping("/binderTest")
    public String binderTest(@RequestParam("person") Person person) {
        String result = person.toString() + " " + new Date();
        System.out.println(result);
        return result;
    }

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Person.class, new PersonEditor());
    }
}

//參數綁定,將參數分割,賦值給 Person
public class PersonEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        Person p = new Person();
        if (text != null) {
            String[] items = text.split(":");
            p.setUsername(items[0]);
            p.setAddress(items[1]);
        }
        setValue(p);
    }

    @Override
    public String getAsText() {
        return getValue().toString();
    }
}

測試:
測試輸入:http://localhost:8080/binderTest?person=mozping:shenzhen,即可
打印並返回:Person(username=mozping, address=shenzhen) Fri Nov 15 15:28:19 CST 2019
  • 調試圖:

在這裏插入圖片描述

  • 圖中可以看到,尋找到的 handler 類型就是控制器 BinderTestController,尋找到的 methods 只有一個,就是 initBinder,沒有全局的@InitBinder,因此全局的綁定方法是空,最後只找到我們自定義的這個。

在這裏插入圖片描述

4.2 自定義ModelAttribute方法

  • 自定義全局和局部的 @ModelAttribute方法,下面是局部的,ModelAttribute 方法會在控制器的其他方法之前執行,在3.4的 invokeHandlerMethod 方法中解釋過了,我們看代碼
@RestController
public class ModelAttributeController {

    @ModelAttribute
    public void myModel(Model model) {
        System.out.println("ModelAttribute method ------------ ");
    }

    @RequestMapping("/modelAttr")
    public String modelAttr(@RequestParam("name") String name) {
        System.out.println("modelAttr execute ... " + name);
        return "modelAttr";
    }
}
  • @ControllerAdvice 聲明定義全局的 @ModelAttribute 方法:
//然後是一個全局的 @ModelAttribute 方法,必須使用 @ControllerAdvice 聲明
@ControllerAdvice
public class GlobalModelAttribute {

    @ModelAttribute
    public void myModel(Model model) {
        System.out.println("Global ModelAttribute method ------------ ");
    }
}

請求:http://localhost:8080/modelAttr?name=mozping

打印:
Global ModelAttribute method ------------ 
ModelAttribute method ------------ 
modelAttr execute ... mozping
  • 可以看到 @ModelAttribute 的方法會在目標方法之前被執行,注意如果有多個 @RequestMapping 方法,@ModelAttribute都會在前面攔截執行,需要謹慎使用。
  • 非全局的 @ModelAttribute 方法必須在目標處理器類裏面聲明,框架也是從 bean裏面去尋找的,全局的則需要使用 @ControllerAdvice 聲明,全局的會優先執行
  • 在 @ModelAttribute 方法的參數中有一個 Model 形參,因此可以對示圖做一些修改,更多關於 @ModelAttribute 可以閱讀參考文章[5]

五、RequestMappingHandlerAdapter 初始化

  • 前面的 @InitBinder 和 @ModelAttribute 都有全局和局部的,局部的就是去Bean裏面去找,而全局的直接遍歷initBinderAdviceCache 和 modelAttributeAdviceCache 屬性集合處理,這些全局的在Bean的生命週期節點就已經掃描並保存好了。

  • RequestMappingHandlerAdapter 初始化部分放在最後,前面我們發現@InitBinder 和 @ModelAttribute 的掃描過程很類似,都是有全局的和局部的,局部的就定義在目標處理類的內部,代碼通過到目標Bean去掃描獲取,而全局的則定義在由 @ControllerAdvice 註解的類中,全局的 @InitBinder 和 @ModelAttribute分別緩存在屬性 initBinderAdviceCache 和 modelAttributeAdviceCache 中,這兩個緩存的集合在 Bean 初始化的時候就會去掃描並保存起來, 通過 initControllerAdviceCache() 方法來掃描,時機是在
    InitializingBean接口的 afterPropertiesSet 方法,代碼如下:

5.1 執行時機

  • InitializingBean#afterPropertiesSet方法的執行時機在屬性賦值完畢之後。
    @Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBody advice beans
		//將全局的 @ControllerAdvice 類掃描進來,裏面可能有全局的參數綁定器和 @ModelAttribute 方法
		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);
		}
	}

5.2 掃描加載

  • initControllerAdviceCache:加載全局的參數綁定器和@ModelAttribute方法,核心代碼如下:
	private void initControllerAdviceCache() {
	    
	    //1.找出全部@ControllerAdvice 的Bean,並排序好
		List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
		AnnotationAwareOrderComparator.sort(adviceBeans);
		
		List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
        
        //2.依次遍歷處理
		for (ControllerAdviceBean adviceBean : adviceBeans) {
			Class<?> beanType = adviceBean.getBeanType();
		    
		    //3.尋找不包含 RequestMapping註解 並且包含 ModelAttribute 註解 的方法
			Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
			if (!attrMethods.isEmpty()) {
			    //4.不爲空則將 ModelAttribute方法添加到緩存
				this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
			}
			
			//5.處理參數綁定器,尋找包含 @InitBinder 註解的方法
			Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
			if (!binderMethods.isEmpty()) {
			    //6.不爲空則將 InitBinder參數綁定方法添加到緩存
				this.initBinderAdviceCache.put(adviceBean, binderMethods);
			}
			
			//7.requestResponseBodyAdviceBeans集合添加 RequestBodyAdvice 類型的Bean
			if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
				requestResponseBodyAdviceBeans.add(adviceBean);
			}
			
			//8.requestResponseBodyAdviceBeans集合添加 RequestBodyAdvice 類型的Bean
			if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
				requestResponseBodyAdviceBeans.add(adviceBean);
			}
		}

		if (!requestResponseBodyAdviceBeans.isEmpty()) {
			this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
		}
	}
  • 下面想尋找全局增強的 ControllerAdvice 類型Bean的邏輯,很清晰,全局的 @InitBinder 和 @ModelAttribute 都是在 @ControllerAdvice 類裏面去掃描的;
	public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext applicationContext) {
		List<ControllerAdviceBean> beans = new ArrayList<>();
		for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class)) {
			if (applicationContext.findAnnotationOnBean(name, ControllerAdvice.class) != null) {
				beans.add(new ControllerAdviceBean(name, applicationContext));
			}
		}
		return beans;
	}
  • @InitBinder方法 的加載條件是:包含@InitBinder註解
public static final MethodFilter INIT_BINDER_METHODS = method ->
			AnnotationUtils.findAnnotation(method, InitBinder.class) != null;
  • @ModelAttribute方法 的加載條件是:包含 @ModelAttribute 註解且不包含 @RequestMapping 註解
	public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->
			((AnnotationUtils.findAnnotation(method, RequestMapping.class) == null) &&
			(AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null));

六、小結

    1. 全局的 @InitBinder 和 @ModelAttribute 都是在 @ControllerAdvice 類裏面去尋找的,
    1. @InitBinder 則是尋找 @ControllerAdvice 裏面註解了@InitBinder的方法;
    1. @ModelAttribute 則是尋找 @ControllerAdvice 裏面註解了@ModelAttribute並且沒有註解 RequestMapping的方法
    1. 局部的 @InitBinder 和 @ModelAttribute 都是在目標處理器內部去尋找的,註解過濾要求是一樣的
    1. 全局的優先於局部的先執行
  • 文章主要是梳理了 RequestMappingHandlerAdapter 的執行流程,配合示例重點看了參數綁定器和 @ModelAttribute的初始化以及加載時機和加載邏輯。
  • RequestMappingHandlerAdapter 的執行流程大體上如下:
1.session同步處理
2.目標方法調用 
( 獲取@InitBinder@ModelAttribute -> 設置參數處理器 -> 
				設置返回值處理器 -> 參數發現器 -> 調用 @ModelAttribute 方法 
											-> 目標方法調用 -> 結果處理和返回 )
3.Cache-Control 處理 
  • 在 getDataBinderFactory 和 getModelFactory 方法裏面分別有createInitBinderMethod 和 createModelAttributeMethod 方法來包裝目標的@InitBinder 和 @ModelAttribute方法,二者都是返回 InvocableHandlerMethod 對象,他是 HandlerMethod 對象,也就是封裝了目標方法的類,createModelAttributeMethod 和 createModelAttributeMethod 裏面都涉及到 HandlerMethodArgumentResolver 處理器方法參數解析器策略,以及返回值處理器HandlerMethodReturnValueHandler等,在後續文章再分析。

七、參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章