SpringMVC RequestMappingHandlerAdapter源碼解析

SpringMVC RequestMappingHandlerAdapter

他是幹什麼

RequestMappingHandlerAdapter處理執行我們最常用的@requestMapping 的方法,也是目前mvc提供的最常用最複雜的 handlerAdapter相對於其他的。他功能很簡單:通過反射執行我們標記 @requestMapping 的方法。但是他複雜在,需要我們對執行@requestMapping 入參的綁定(因爲沒有對入參做要求,所以很實用,但是處理起來會面臨各種各樣的情況,所以很複雜),和一些我們定義好的前置處理步驟比如@initbinder,@modeAttribute等標記代碼塊的執行,要給對參數,有的都要給。

初始化

我們先從初始化入手看他的結構,一看到InitializingBean,找afterPropertiesSet如下

	@Override
	public void afterPropertiesSet() {
		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);
		}
	}

先看第一行

//找到@ControllerAdvice 和ModelAttribute,InitBinder的method和bean
initControllerAdviceCache(); 



/*
* 對應源碼
*/
private void initControllerAdviceCache() {
		if (getApplicationContext() == null) {
			return;
		}
		if (logger.isInfoEnabled()) {
			logger.info("Looking for @ControllerAdvice: " + getApplicationContext());
		}
		// 找到所有@ControllerAdvice標記的bean
		List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
		AnnotationAwareOrderComparator.sort(beans);//排序
		
		List<Object> requestResponseBodyAdviceBeans = new ArrayList<Object>();

		for (ControllerAdviceBean bean : beans) {
			//找到ModelAttribute標記,同時沒有被RequestMapping標記的method
			Set<Method> attrMethods = MethodIntrospector.selectMethods(bean.getBeanType(), MODEL_ATTRIBUTE_METHODS);
			if (!attrMethods.isEmpty()) {
				this.modelAttributeAdviceCache.put(bean, attrMethods);
				if (logger.isInfoEnabled()) {
					logger.info("Detected @ModelAttribute methods in " + bean);
				}
			}
			//找到@initbinder標記的method
			Set<Method> binderMethods = MethodIntrospector.selectMethods(bean.getBeanType(), INIT_BINDER_METHODS);
			if (!binderMethods.isEmpty()) {
				this.initBinderAdviceCache.put(bean, binderMethods);
				if (logger.isInfoEnabled()) {
					logger.info("Detected @InitBinder methods in " + bean);
				}
			}
			//看是不是實現了RequestBodyAdvice接口,實現了放到一個list中
			if (RequestBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {
				requestResponseBodyAdviceBeans.add(bean);
				if (logger.isInfoEnabled()) {
					logger.info("Detected RequestBodyAdvice bean in " + bean);
				}
			}
			//看是不是實現了ResponseBodyAdvice接口,實現了放到一個list中
			if (ResponseBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {
				requestResponseBodyAdviceBeans.add(bean);
				if (logger.isInfoEnabled()) {
					logger.info("Detected ResponseBodyAdvice bean in " + bean);
				}
			}
		}
		//把所有的bodyAdvice 放入一個requestResponseBodyAdvice,以備後面使用
		if (!requestResponseBodyAdviceBeans.isEmpty()) {
			this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
		}
	}


經過以上操作就把所有的spring中的@ControllerAdvice 註解的bean,和對應的@initbinder,@modeAttribute的method還有兩個request/ResponseBodyAdvice都放到我們的RequestMappingHandlerAdapter中的,以備後面使用也就是完成的initControllerAdviceCache 初始化initControllerAdviceCache緩存。

後面三種就是加載各種解析器

調用過程

程序入口是handleInternal方法,看一下源碼

@Override
	protected ModelAndView handleInternal(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ModelAndView mav;
		//校驗session是否必須存在,還有是否支這個請求
		checkRequest(request);

		// 是否對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 {
				// 如果沒有可用的HttpSession ->不需要互斥
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
			// 如果根本不要同步
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}
		//看看response報文頭中是否包含cache_control
		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			//判斷當前這handler是否有@sessionAttribute,如果有就解析出來,放到緩存中準備下次使用
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				//設置返回緩存過期時間
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

		return mav;
	}

我們從頭開始看

checkRequest(request);

protected final void checkRequest(HttpServletRequest request) throws ServletException {
		// 得到當前請求的方法類型,看看當前handleradapter是否支持這個方法
		String method = request.getMethod();
		if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
			throw new HttpRequestMethodNotSupportedException(
					method, StringUtils.toStringArray(this.supportedMethods));
		}

		// 看看是否必須要這個session,如果必須沒有拋異常
		if (this.requireSession && request.getSession(false) == null) {
			throw new HttpSessionRequiredException("Pre-existing session required but none found");
		}
	}

第二段執行方法後面細說,剩下的一段就是

if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

這段代碼總的意思是:
一般情況下,如果返回報文中沒有對緩存的特殊控制,如果有@sessionAttribute則組織使用緩存,否則就什麼也不做

下面我們看重點,執行方法

mav = invokeHandlerMethod(request, response, handlerMethod);



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

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			//看下面 1
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			//看下面 2
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			//看下面3
			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			invocableMethod.setDataBinderFactory(binderFactory);
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
			//新建傳遞參數的 mavcontainer容器,並把相應的參數設置進去
			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			//設置flashMap的參數進去
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			//用modelFactoey把 sessionAttribute和modelAttribute的參數設置進去
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			//這個是在mavcontainer中會用到後面細說
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
			//做一些異步處理相關操作,後面細說
			AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
			asyncWebRequest.setTimeout(this.asyncRequestTimeout);

			WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
			asyncManager.setTaskExecutor(this.taskExecutor);
			asyncManager.setAsyncWebRequest(asyncWebRequest);
			asyncManager.registerCallableInterceptors(this.callableInterceptors);
			asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

			if (asyncManager.hasConcurrentResult()) {
				Object result = asyncManager.getConcurrentResult();
				mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
				asyncManager.clearConcurrentResult();
				if (logger.isDebugEnabled()) {
					logger.debug("Found concurrent result value [" + result + "]");
				}
				//執行請求
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}

			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}
			//看 4
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}

我們看一下這個方法,首先封裝了一個webRequest,這個用於argumentResolver解析參數時使用,然後就是對 WebDataBinderFactory, ModelFactory, ServletInvocableHandlerMethod進行定義和初始化

  1. WebDataBinderFactory
    用來創建WebDataBinder,WebDataBinder主要用來參數綁定,實現參數跟String之間的類型轉換,看一下源碼
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
		//取得bean的class
		Class<?> handlerType = handlerMethod.getBeanType();
		//看看是不是這個controller已經在緩存中有了
		Set<Method> methods = this.initBinderCache.get(handlerType);
		// 如果沒有
		if (methods == null) {
			//查找這個controller看看是不是有initbinder,有的話加載到緩存中,下次使用
			methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
			this.initBinderCache.put(handlerType, methods);
		}
		//自定義保存initbinder方法的零時變量
		List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
		// 先找到符合條件的全局 initbinder,添加到initbindermethods中
		for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
			if (entry.getKey().isApplicableToBeanType(handlerType)) {
				Object bean = entry.getKey().resolveBean();
				for (Method method : entry.getValue()) {
					// 先添加全局的initbinder
					initBinderMethods.add(createInitBinderMethod(bean, method));
				}
			}
		}
		//再添加當前這個controller的
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			initBinderMethods.add(createInitBinderMethod(bean, method));
		}
		//創建factory返回
		return createDataBinderFactory(initBinderMethods);
	}

從上面的源碼可以看出,主要是找到所有的initbinder 來創建WebDataBinderFactory,注意下面這個MethodIntrospector.selectMethods 找不到也會返回一個空set,不是null,這個可以確保我們只用查一次,不用發現發沒有,次次查

methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
  1. ModelFactoey
    用來處理model,主要是(1)在處理器處理之前對model初始化 (2)在處理完後對model進行參數更新

先說初始化,初始化主要是做三部分內容

  1. 將原來的@sessionAttribute 中的值設置到model中。
  2. 執行註釋了@modelAttribute 的方法,並將值設置到model中。
  3. 處理器註釋了@modelAttribute 的參數,如果同時在@sessionAttribute中也配置了,而且在mavContainer中還沒有值,則從全部的sessionAttribute中找到值並設置上。

更新是先對SessionAttributes進行設置,設置的規則是如果處理器設置了sessionStatus中的setComplete 就將sessionAttributes清空,否則就將model中的參數設置給sessionAttribute,然後按照需要把給model設置參數對應的bindingResult下面看一下源碼

private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
		// 獲取SessionAttributesHandler,也就是@sessionAttribute標籤的一些信息
		SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
		Class<?> handlerType = handlerMethod.getBeanType();
		//同上面initbinder的邏輯,找到modeAttribute 的方法
		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<InvocableHandlerMethod>();
		// 和上面的一樣,先是公共的,再是私有的
		for (Entry<ControllerAdviceBean, Set<Method>> entry : this.modelAttributeAdviceCache.entrySet()) {
			if (entry.getKey().isApplicableToBeanType(handlerType)) {
				Object bean = entry.getKey().resolveBean();
				for (Method method : entry.getValue()) {
					attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
				}
			}
		}
		for (Method method : methods) {
			Object bean = handlerMethod.getBean();
			attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
		}
		return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
	}

看一下 new ModelFactory(attrMethods, binderFactory, sessionAttrHandler); 可以看出創建他需要 一個是要用的 initbinder一個是modelAttributes,還有一個是sessionAttributesm給齊了

  1. ServletInvocableHandlerMethod核心方法請求的處理就是通過他來執行,參數綁定,和請求處理都是他來完成,首先把handlerMethod 封裝爲 servletInvocableHandlermethod,然後再把解析器設置進去
			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			invocableMethod.setDataBinderFactory(binderFactory);
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

4.getModelAndView(mavContainer, modelFactory, webRequest); 主要做三件事1)調用modelFactory更新了model。2)根據mavcontainer 創建了modelandview。3)如果mavcontainer是redirectAttribute類型的就將值設置到 falshmap中

getModelAndView(mavContainer, modelFactory, webRequest);

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
			ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
		//更新model
		modelFactory.updateModel(webRequest, mavContainer);
		if (mavContainer.isRequestHandled()) {
			return null;
		}
		ModelMap model = mavContainer.getModel();
		//創建mav
		ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
		if (!mavContainer.isViewReference()) {
			mav.setView((View) mavContainer.getView());
		}
		//設置falshMap
		if (model instanceof RedirectAttributes) {
			Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
			HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
			RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
		}
		return mav;
	}


小結

總的來說邏輯是很簡單的,就是用來很多陌生的組件,後面有專門的分析,主要流程的就是綁定參數,執行請求,處理返回值。

1)參數綁定
參數的主要來源就是有6類參數的主要來源就是有6類

  1. request中參數
  2. cookie中的參數
  3. session中參數
  4. falshMap中的參數
  5. @sessionAttribute中的參數
  6. @modelAttributes中的參數
    前面三個在request中處理,falshMap是直接在RequestMappingHandlerAdapter處理,後面兩個在ModelFactory中處理

2) 處理請求,用的是ServletInvocableHandlerMethod
3) 處理收尾,主要是更新model中的緩存,和modelAndView的創建

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