SpringBoot與SpringMVC中參數傳遞的原理解析
一:普通參數與基本註解
HandlerMapping中找到能處理請求的Handler(Controller,method())
為當前Handler找一個適配器HandlerAdapter:RequestMappingHandlerAdapter
1.HandlerAdapter
0-支持方法上標註@RequestMapping
1-支持函數式編程的
xxxx
2.執行目標方法
3.參數解析器:確定要執行的目標方法每一個參數的值是什麼
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
先判斷是否支持該參數類型, 如果支持, 就調用resolveArgument解析方法
4.返回值處理器
5.挨個判斷所有參數解析器哪個支持這個參數:HandlerMethodArgumentResolver: 把控著支持的方法參數類型
請求進來後, 首先從handlerMapping中查找是否有對應的映射處理, 得到映射適配器Adapter,再通過適配器,查找有哪些方法匹配請求,首先判斷方法名,以及參數類型是否匹配,首先獲得方法中聲明的參數名字, 放到數組裡,循環遍歷27種解析器判斷是否有支持處理對應參數名字類型的解析器,如果有的話,根據名字進行解析參數,根據名字獲得域數據中的參數, 循環每個參數名字進行判斷, 從而為每個參數進行賦值.
對於自定義的POJO類參數:
ServletRequestMethodArgumentResolver 這個解析器用來解析: 是通過主要是通過判斷是否是簡單類型得到的
@Override public boolean supportsParameter(MethodParameter parameter) { return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))); } public static boolean isSimpleValueType(Class<?> type) { return (Void.class != type && void.class != type && (ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type)); } public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer"); Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory"); String name = ModelFactory.getNameForParameter(parameter); ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null) { mavContainer.setBinding(name, ann.binding()); } Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { // Create attribute instance try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { if (isBindExceptionRequired(parameter)) { // No BindingResult parameter -> fail with BindException throw ex; } // Otherwise, expose null/empty value and associated BindingResult if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } else { attribute = ex.getTarget(); } bindingResult = ex.getBindingResult(); } } if (bindingResult == null) { // Bean property binding and validation; // skipped in case of binding failure on construction. WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } // Value type adaptation, also covering java.util.Optional if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
WebDataBinder binder =binderFactory.createBinder(webRequest,attribute,name)
WebDataBinder:web數據綁定器,將請求參數的值綁定到指定的javaBean裡面
WebDataBinder 利用它裡面的Converters將請求數據轉成指定的數據類型,通過反射一系列操作,再次封裝到javabean中
GenericConversionService:在設置每一個值的時候,找它裡面所有的converter哪個可以將這個數據類型(request帶來參數的字符串)轉換到指定的類型(javabean—某一個類型)
未來我們可以給WebDataBinder裡面放自己的Converter
private static final class StringToNumber implements Converter<String, T> { converter總接口: @FunctionalInterface public interface Converter<S, T> {
//自定義轉換器:實現按照自己的規則給相應對象賦值
@Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter<String, Pet>() { @Override public Pet convert(String source) { if (!StringUtils.isEmpty(source)){ Pet pet = new Pet(); String[] split = source.split(","); pet.setName(split[0]); pet.setAge(split[1]); return pet; } return null; } }); }
二:復雜參數
Map/Model(map/model裡面的數據會被放在request的請求域 相當於request.setAttribute)/Errors/BindingResult/RedirectAttributes(重定向攜帶數據)/ServletRespons().SessionStaus.UriComponentsBuilder
6.在上面第五步目標方法執行完成後:
將所有的數據都放在ModelAdnViewContainer;包含要去的頁面地址View,還包含Model數據
7.處理派發結果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
在頁面進行響應前, 進行視圖渲染的時候:
exposeModelAsRequestAttributes(model, request); 該方法將model中所有參數都放在請求域數據中
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose the model object as request attributes. exposeModelAsRequestAttributes(model, request); // Expose helpers as request attributes, if any. exposeHelpers(request); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } // If already included or response already committed, perform include, else forward. if (useInclude(request, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including [" + getUrl() + "]"); } rd.include(request, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to [" + getUrl() + "]"); } rd.forward(request, response); } }
通過循環遍歷model中的所有數據放在請求域中
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception { model.forEach((name, value) -> { if (value != null) { request.setAttribute(name, value); } else { request.removeAttribute(name); } }); }
不管我們在方法形參位置放 Map集合或者Molde 最終在底層源碼都是同一個對象在mvcContainer容器中進行保存
到此這篇關於SpringBoot與SpringMVC中參數傳遞的原理的文章就介紹到這瞭,更多相關SpringBoot SpringMVC參數傳遞內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 一文搞懂SpringMVC中@InitBinder註解的使用
- SpringMVC 參數綁定相關知識總結
- @validated註解異常返回JSON值方式
- spring mvc中@RequestBody註解的作用說明
- 詳解如何在SpringBoot中自定義參數解析器