#基礎 ###類名稱 ####HandlerMethodArgumentResolver 解決方法參數到指定信息參數集合的策略接口。 ###方法名稱 supportsParameter 檢查指定參數是否被該接口支持 參數 MethodParameter parameter 要被檢查的方法參數。 resolveArgument 將請求參數中的參數方法轉換成變量 參數 一、parameter 要被轉換的參數 二、mavContainer 目前請求的ModelAndViewContainer 三、webRequest 如今的請求 四、binderFactory 建立實例的工廠 五、返回被轉換好的值java
###問題: springmvc在請求到方法以前如何包裝到request對象到方法頭對象。 有三種狀況: @RequestParam 系統自帶標籤 @CurrentUser User user 自定義標籤 JqGridPage jqGridPage ###springmvc參數處理流程web
類:DispatcherServlet protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; //處理器前置處理 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //獲取處理器適配 <!-- --> HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); try { //真正的觸發適配器處理請求() mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } //設置view applyDefaultViewName(request, mv); //處理器後置處理 mappedHandler.applyPostHandle(processedRequest, response, mv); }
springmvc設置了五種處理器的適配器。他們分別是AbstractHandlerMethodAdapter HttpRequestHandlerAdapter RequestMappingHandlerAdapter SimpleControllerHandlerAdapter(/) SimpleServletHandlerAdapter。在從上到下配置的過程當中遇到了合適的匹配,那麼就將採用該適配器處理請求。咱們拿SimpleControllerHandlerAdapter爲例繼續分析。spring
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { //委託WebContentGenerator作檢查和準備工做 checkAndPrepare(request, response, this instanceof LastModified); // 在session的互斥變量同步模塊執行handleRequestInternal if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { return handleRequestInternal(request, response); } } } return handleRequestInternal(request, response); } 類ParameterizableViewController protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { //返回對象ModelAndView return new ModelAndView(getViewName(), RequestContextUtils.getInputFlashMap(request)); }
SimpleControllerHandlerAdapter適配器很簡單就是將咱們請求的路徑包裝成新的返回對象ModelAndView對象返回。這個方法適用於剛剛登錄系統想要請求登錄頁面的時候。 接下來咱們再以RequestMappingHandlerAdapter爲例作請求。json
類RequestMappingHandlerAdapter protected final ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { return invokeHandleMethod(request, response, handlerMethod); } private ModelAndView invokeHandleMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { //得到處理器 ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); //執行處理器 requestMappingMethod.invokeAndHandle(webRequest, mavContainer); } //得到返回的ModelandView return getModelAndView(mavContainer, modelFactory, webRequest); } 類:ServletInvocableHandlerMethod public final void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); } 類InvocableHandlerMethod public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); Object returnValue = invoke(args); return returnValue; } private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //得到方法的全部參數 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; //循環每個參數得到參數的參數解析器 for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); GenericTypeResolver.resolveParameterType(parameter, getBean().getClass()); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (this.argumentResolvers.supportsParameter(parameter)) { try { //執行解析器(這裏使用了組合模式將全部的解析器都放置到了HandlerMethodArgumentResolverComposite對象中統一處理,good method~) args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); continue; } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex); } throw ex; } } if (args[i] == null) { String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i); throw new IllegalStateException(msg); } } return args; } 類:HandlerMethodArgumentResolverComposite public Object resolveArgument( MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { //得到支持方法參數的處理器去處理該參數的事宜。 HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]"); return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); }
在一個request請求首先要得到可以處理該請求的適配器。而後在適配器中對該請求作處理。處理很複雜,我簡略了一些代碼。這裏主要說方法頭的參數處理: 步驟一、在適配器中調用方法handleInternal()作一些參數校驗和準備工做。 步驟二、調用invokeHandleMethod(request, response, handlerMethod)得到處理參數的類ServletInvocableHandlerMethod。 步驟三、調用ServletInvocableHandlerMethod類中的方法invokeAndHandle()處理參數。 步驟四、invokeAndHandle方法調invokeForRequest方法處理每個參數。 步驟五、invokeForRequest方法調用父類invokeForRequest()方法去找到每個參數解析器並解析參數。 步驟5:調用invoke(args)方法將步驟五中的參數包裝成一個對象返回。 ###參數解析器 回到咱們上面說的三種參數處理方式 @RequestParam 系統自帶標籤和JqGridPage jqGridPage對象都是是由ModelAttributeMethodProcessor處理器處理。session
public boolean supportsReturnType(MethodParameter returnType) { //支持標籤@ModelAttribute if (returnType.getMethodAnnotation(ModelAttribute.class) != null) { return true; } //對未註釋標籤的若是是非普通對象也返回true else if (this.annotationNotRequired) { return !BeanUtils.isSimpleProperty(returnType.getParameterType()); } else { return false; } } //參數解析過程 @Override public final Object resolveArgument( MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception { //得到參數名稱 String name = ModelFactory.getNameForParameter(parameter); //若是mavContainer沒有這個參數則調用mavContainer.getModel().get(name)返回參數,若是返回參數失敗會調用createAttribute(name, parameter, binderFactory, request)建立一個實例對象 Object attribute = (mavContainer.containsAttribute(name)) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request); //建立WebDataBinder類型的對象 WebDataBinder binder = binderFactory.createBinder(request, attribute, name); if (binder.getTarget() != null) { //綁定request參數到對象中去 bindRequestParameters(binder, request); //作校驗 validateIfApplicable(binder, parameter); //與錯誤拋異常 if (binder.getBindingResult().hasErrors()) { if (isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } } //沒有錯誤將得到的數據放置到mvc容器中mavContainer,返回實例對象。 Map<String, Object> bindingResultModel = binder.getBindingResult().getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return binder.getTarget(); }
@CurrentUser User自定義標籤咱們能夠擴展HandlerMethodArgumentResolver方法自定義參數解析器。mvc
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver { public CurrentUserMethodArgumentResolver() { } @Override public boolean supportsParameter(MethodParameter parameter) { //參數帶有標籤@CurrentUser則返回true if (parameter.hasParameterAnnotation(CurrentUser.class)) { return true; } return false; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { //獲取request中的參數,將做爲結果返回 CurrentUser currentUserAnnotation = parameter.getParameterAnnotation(CurrentUser.class); return webRequest.getAttribute(currentUserAnnotation.value(), NativeWebRequest.SCOPE_REQUEST); } }
最後別忘了將自定義的resolver放置到mvc而配置文件中。app
<mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <!-- StringHttpMessageConverter編碼爲UTF-8,防止亂碼 --> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8"/> <property name = "supportedMediaTypes"> <list> <bean class="org.springframework.http.MediaType"> <constructor-arg index="0" value="text"/> <constructor-arg index="1" value="plain"/> <constructor-arg index="2" value="UTF-8"/> </bean> <bean class="org.springframework.http.MediaType"> <constructor-arg index="0" value="*"/> <constructor-arg index="1" value="*"/> <constructor-arg index="2" value="UTF-8"/> </bean> </list> </property> </bean> <!-- 避免IE執行AJAX時,返回JSON出現下載文件 --> <bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> </list> </property> <!--<property name="serializerFeature">--> <!--這個地方加上這個功能吧,能本身配置一些東西,好比時間的格式化,null輸出""等等--> <!--</property>--> </bean> </mvc:message-converters> <mvc:argument-resolvers> <bean class="web.bind.method.annotation.PageableMethodArgumentResolver"/> <bean class="web.bind.method.annotation.SearchableMethodArgumentResolver"/> <bean class="web.bind.method.annotation.FormModelMethodArgumentResolver"/> <bean class="web.bind.method.annotation.CurrentUserMethodArgumentResolver"/> </mvc:argument-resolvers> </mvc:annotation-driven>
至此,咱們從源碼級別分別闡述了springmvc過程當中參數的處理時機和參數處理流程。這塊內容在咱們處理參數方面內容給了一個啓示。能夠本身定義處理器處理前臺頁面複雜的參數請求。
###參考文獻: ####http://jinnianshilongnian.iteye.com/blog/1608234 ####http://jinnianshilongnian.iteye.com/blog/1498155async