内容简介:springmvc 的请求流程,相信大家已经很熟悉了,不熟悉的同学可以参考下资料!有了整体流程的概念,是否对其中的实现细节就很清楚呢?我觉得不一定,比如:单是参数解析这块,就是个大学问呢?首先,我们从最靠近请求末端的地方说起!此时,handler已经找到,即将进行处理!
springmvc 的请求流程,相信大家已经很熟悉了,不熟悉的同学可以参考下资料!
有了整体流程的概念,是否对其中的实现细节就很清楚呢?我觉得不一定,比如:单是参数解析这块,就是个大学问呢?
首先,我们从最靠近请求末端的地方说起!此时,handler已经找到,即将进行处理!
这是在 RequestMappingHandlerAdapter 的处理方法 handleInternal(), 将请求交给业务代码的地方!
以下是 @ModelAttributeMethodProcessor进行处理的参数处理堆栈:
"http-nio-8080-exec-2@2414" daemon prio=5 tid=0x21 nid=NA runnable java.lang.Thread.State: RUNNABLE at org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler.setValue(BeanWrapperImpl.java:358) at org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(AbstractNestablePropertyAccessor.java:469) at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:292) at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:280) at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:95) at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:859) at org.springframework.validation.DataBinder.doBind(DataBinder.java:755) at org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:192) at org.springframework.web.bind.ServletRequestDataBinder.bind(ServletRequestDataBinder.java:106) at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.bindRequestParameters(ServletModelAttributeMethodProcessor.java:152) at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:113) at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) at javax.servlet.http.HttpServlet.service(HttpServlet.java:635) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) - locked <0x19d0> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) View Code
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter @Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required. 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... // 更多的时候,我们都是无状态请求的,所以都会走这里,进行业务接入 mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; } // 然后交给 RequestMappingHandlerAdapter 处理 /** * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView} * if view resolution is required. * @since 4.2 * @see #createInvocableHandlerMethod(HandlerMethod) */ protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { // 将request和response封装为一个类,方便后续使用 ServletWebRequest webRequest = new ServletWebRequest(request, response); try { // 使用 binderFactory 进行参数解析,该 binderFactory 将会伴随整个参数的一生 // 这个binderFactory 与 方法和参数关联 // 然后根据 binderFactory 获取 modelFactory, modelFactory 会绑定一些属性和参数解析器到里面,备用 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); // 创建 最终可用于调用业务方法的包装 ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); // 作为返回属性的一个容器 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, invocableMethod); 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); } // 进一步调用 handler 方法,进入参数解析状态 invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } } private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception { Class<?> handlerType = handlerMethod.getBeanType(); Set<Method> methods = this.initBinderCache.get(handlerType); if (methods == null) { methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS); this.initBinderCache.put(handlerType, methods); } List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>(); // Global methods first for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) { if (entry.getKey().isApplicableToBeanType(handlerType)) { Object bean = entry.getKey().resolveBean(); for (Method method : entry.getValue()) { initBinderMethods.add(createInitBinderMethod(bean, method)); } } } for (Method method : methods) { Object bean = handlerMethod.getBean(); initBinderMethods.add(createInitBinderMethod(bean, method)); } return createDataBinderFactory(initBinderMethods); } 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) { methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS); this.modelAttributeCache.put(handlerType, methods); } List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>(); // Global methods first 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); } // org.springframework.web.method.support.InvocableHandlerMethod /** * Invoke the method and handle the return value through one of the * configured {@link HandlerMethodReturnValueHandler}s. * @param webRequest the current request * @param mavContainer the ModelAndViewContainer for this request * @param providedArgs "given" arguments matched by type (not resolved) */ public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 此处只处理返回值问题,具体的调用逻辑再封装, 其中 providedArgs 此时仅是空值,将会在后续处理 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); } throw ex; } }
以上大体调用流程,下面我们来看下参数解析:
/** * Invoke the method after resolving its argument values in the context of the given request. * <p>Argument values are commonly resolved through {@link HandlerMethodArgumentResolver}s. * The {@code providedArgs} parameter however may supply argument values to be used directly, * i.e. without argument resolution. Examples of provided argument values include a * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance. * Provided argument values are checked before argument resolvers. * @param request the current request * @param mavContainer the ModelAndViewContainer for this request * @param providedArgs "given" arguments matched by type, not resolved * @return the raw value returned by the invoked method * @exception Exception raised if no suitable argument resolver can be found, * or if the method raised an exception */ public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 真正的参数解析在此处开始,是本文关心的点, 此时 providedArgs 还是为空的 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "' with arguments " + Arrays.toString(args)); } Object returnValue = doInvoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "] returned [" + returnValue + "]"); } return returnValue; }
真正的参数解析如下:
/** * Get the method argument values for the current request. */ 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); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } // 依次遍历解析器,只要有一个支持解析,则进行解析逻辑,否则抛出异常 if (this.argumentResolvers.supportsParameter(parameter)) { try { // 进行具体解析器的解析,当然,这里是解析器列表,调用后会再进行合适解析器的筛选过程 args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex); } throw ex; } } if (args[i] == null) { throw new IllegalStateException("Could not resolve method parameter at index " + parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() + ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i)); } } return args; }
可以看出,参数解析主要分为几步:
1. 获取参数类型数组,供后续填充使用;
2. 对每个参数类型,单独调用参数解析过程;
3. 在进行具体解析之前,先尝试简单解析是否成功(类似于读取缓存),不成功则再进行深度解析;
4. 深度解析交由所有解析器进行处理, 即 HandlerMethodArgumentResolverComposite, 它包含了所有的解析器;
// org.springframework.web.method.support.HandlerMethodArgumentResolverComposite /** * Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it. * @throws IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found. */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 获取具体适用的解析器,即调用其 supportsParameter(param) 方法判断即可 HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null) { throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]"); } // 解析器获取到后,就交由具体的解析器进行解析了,这里有很多的分支了,咱们可以挑选几个来看一下就可以了 return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); }
下面,我我们看一下 @ModelAttribute 修饰的参数解析方法(注意: 这里的解析只是针对任意一个参数的解析,所以无需关注整体参数形式):
// org.springframework.web.method.annotation.ModelAttributeMethodProcessor /** * Resolve the argument from the model or if not found instantiate it with * its default if it is available. The model attribute is then populated * with request values via data binding and optionally validated * if {@code @java.validation.Valid} is present on the argument. * @throws BindException if data binding and validation result in an error * and the next method parameter is not of type {@link Errors}. * @throws Exception if WebDataBinder initialization fails. */ @Override public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 获取参数名字, 如: User user 获取的结果为 user ; 其实现原理为获取该注解的 value 值 // 如果缓存中没有该参数的解析,那么就实例化一个参数实例,并放入缓存 String name = ModelFactory.getNameForParameter(parameter); Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest)); if (!mavContainer.isBindingDisabled(name)) { ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null && !ann.binding()) { mavContainer.setBindingDisabled(name); } } // 将参数包装进 WebDataBinder 中,方便统一操作, 将参数实例放入到 target 字段域中 WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { // 进行参数的解析过程, 将由 ServletModelAttributeMethodProcessor 处理 if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } // Add resolved attribute and BindingResult at the end of the model Map<String, Object> bindingResultModel = binder.getBindingResult().getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } // 如下为获取参数名的实现方式,如果指明 value, 则直接获取, 否则生成一个参数简称名的字段, 注意此处的名字并不一定是 参数真正的命名 // 其实函数的调用只要参数位置正确即可,并不需要真实的变量名 public static String getNameForParameter(MethodParameter parameter) { ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); String name = (ann != null ? ann.value() : null); return (StringUtils.hasText(name) ? name : Conventions.getVariableNameForParameter(parameter)); } // org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor /** * This implementation downcasts {@link WebDataBinder} to * {@link ServletRequestDataBinder} before binding. * @see ServletRequestDataBinderFactory */ @Override protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) { // 取出request 对象,因为只有 request 中有需要处理的参数,此时和 response 是确认无关的 ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class); ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder; // 调用自身的 binder 处理 servletBinder.bind(servletRequest); } /** * Bind the parameters of the given request to this binder's target, * also binding multipart files in case of a multipart request. * <p>This call can create field errors, representing basic binding * errors like a required field (code "required"), or type mismatch * between value and bean property (code "typeMismatch"). * <p>Multipart files are bound via their parameter name, just like normal * HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property, * invoking a "setUploadedFile" setter method. * <p>The type of the target property for a multipart file can be MultipartFile, * byte[], or String. The latter two receive the contents of the uploaded file; * all metadata like original file name, content type, etc are lost in those cases. * @param request request with parameters to bind (can be multipart) * @see org.springframework.web.multipart.MultipartHttpServletRequest * @see org.springframework.web.multipart.MultipartFile * @see #bind(org.springframework.beans.PropertyValues) */ public void bind(ServletRequest request) { // 将所有请求参数封装到 mpvs, 使后续可以直接获取, 其实现为 使用 ArrayList 来保存属性,参考: request.getParameterNames() MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request); MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class); if (multipartRequest != null) { bindMultipart(multipartRequest.getMultiFileMap(), mpvs); } // 此处作为扩展点,预留 addBindValues(mpvs, request); // 调用webDataBinder 设值到参数绑定 doBind(mpvs); } // org.springframework.web.bind.WebDataBinder /** * This implementation performs a field default and marker check * before delegating to the superclass binding process. * @see #checkFieldDefaults * @see #checkFieldMarkers */ @Override protected void doBind(MutablePropertyValues mpvs) { // 检查默认值和 marker 设置 checkFieldDefaults(mpvs); checkFieldMarkers(mpvs); // 调用父类实现数据 绑定,其实就是反射调用字段 super.doBind(mpvs); } // DataBinder.doBind() /** * Actual implementation of the binding process, working with the * passed-in MutablePropertyValues instance. * @param mpvs the property values to bind, * as MutablePropertyValues instance * @see #checkAllowedFields * @see #checkRequiredFields * @see #applyPropertyValues */ protected void doBind(MutablePropertyValues mpvs) { checkAllowedFields(mpvs); checkRequiredFields(mpvs); // 处理值 applyPropertyValues(mpvs); } /** * Apply given property values to the target object. * <p>Default implementation applies all of the supplied property * values as bean property values. By default, unknown fields will * be ignored. * @param mpvs the property values to be bound (can be modified) * @see #getTarget * @see #getPropertyAccessor * @see #isIgnoreUnknownFields * @see #getBindingErrorProcessor * @see BindingErrorProcessor#processPropertyAccessException */ protected void applyPropertyValues(MutablePropertyValues mpvs) { try { // Bind request parameters onto target object. getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields()); } catch (PropertyBatchUpdateException ex) { // Use bind error processor to create FieldErrors. for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) { getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult()); } } } @Override public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException { List<PropertyAccessException> propertyAccessExceptions = null; // 此处获取参数对象的n个字段为列表,以进行循环赋值, 即循环的依据是外部传入多少值,而不是参数实例有多少字段 List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ? ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues())); // 多个参数值给定时,循环设值即可 for (PropertyValue pv : propertyValues) { try { // This method may throw any BeansException, which won't be caught // here, if there is a critical failure such as no matching field. // We can attempt to deal only with less serious exceptions. // 对单个 field 设值 setPropertyValue(pv); } catch (NotWritablePropertyException ex) { if (!ignoreUnknown) { throw ex; } // Otherwise, just ignore it and continue... } catch (NullValueInNestedPathException ex) { if (!ignoreInvalid) { throw ex; } // Otherwise, just ignore it and continue... } catch (PropertyAccessException ex) { if (propertyAccessExceptions == null) { propertyAccessExceptions = new LinkedList<PropertyAccessException>(); } propertyAccessExceptions.add(ex); } } // org.springframework.beans.AbstractNestablePropertyAccessor @Override public void setPropertyValue(PropertyValue pv) throws BeansException { PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens; if (tokens == null) { String propertyName = pv.getName(); AbstractNestablePropertyAccessor nestedPa; try { nestedPa = getPropertyAccessorForPropertyPath(propertyName); } catch (NotReadablePropertyException ex) { throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", ex); } tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName)); if (nestedPa == this) { pv.getOriginalPropertyValue().resolvedTokens = tokens; } nestedPa.setPropertyValue(tokens, pv); } else { setPropertyValue(tokens, pv); } } // 对字段特殊处理,否则直接设值 protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { if (tokens.keys != null) { processKeyedProperty(tokens, pv); } else { processLocalProperty(tokens, pv); } } private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) { PropertyHandler ph = getLocalPropertyHandler(tokens.actualName); if (ph == null || !ph.isWritable()) { if (pv.isOptional()) { if (logger.isDebugEnabled()) { logger.debug("Ignoring optional value for property '" + tokens.actualName + "' - property not found on bean class [" + getRootClass().getName() + "]"); } return; } else { throw createNotWritablePropertyException(tokens.canonicalName); } } Object oldValue = null; try { Object originalValue = pv.getValue(); Object valueToApply = originalValue; if (!Boolean.FALSE.equals(pv.conversionNecessary)) { if (pv.isConverted()) { valueToApply = pv.getConvertedValue(); } else { if (isExtractOldValueForEditor() && ph.isReadable()) { try { oldValue = ph.getValue(); } catch (Exception ex) { if (ex instanceof PrivilegedActionException) { ex = ((PrivilegedActionException) ex).getException(); } if (logger.isDebugEnabled()) { logger.debug("Could not read previous value of property '" + this.nestedPath + tokens.canonicalName + "'", ex); } } } valueToApply = convertForProperty( tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor()); } pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); } // 经过各种判定后,终于调用反射了 ph.setValue(this.wrappedObject, valueToApply); } catch (TypeMismatchException ex) { throw ex; } catch (InvocationTargetException ex) { PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent( this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue()); if (ex.getTargetException() instanceof ClassCastException) { throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException()); } else { Throwable cause = ex.getTargetException(); if (cause instanceof UndeclaredThrowableException) { // May happen e.g. with Groovy-generated methods cause = cause.getCause(); } throw new MethodInvocationException(propertyChangeEvent, cause); } } catch (Exception ex) { PropertyChangeEvent pce = new PropertyChangeEvent( this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue()); throw new MethodInvocationException(pce, ex); } } // org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler @Override public void setValue(final Object object, Object valueToApply) throws Exception { final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ? ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() : this.pd.getWriteMethod()); if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) { if (System.getSecurityManager() != null) { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { writeMethod.setAccessible(true); return null; } }); } else { writeMethod.setAccessible(true); } } final Object value = valueToApply; if (System.getSecurityManager() != null) { try { AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { writeMethod.invoke(object, value); return null; } }, acc); } catch (PrivilegedActionException ex) { throw ex.getException(); } } else { // java.lang.reflect.Method, 反射设值 writeMethod.invoke(getWrappedInstance(), value); } }
所以,对于 @ModelAttribute 参数解析,其实主要有几步:
1. 获取 request 中的参数,封装到 MutablePropertyValues 中;
2. 依次检查每个字段域的有效性;
3. 处理字段类型的转换,如:将 String 转换为 int;
4. 获取字段的反射方法,进行字段值的绑定;
5. 循环设置,直到所有字段都检查完;
6. 验证字段的有效性,如有异常,抛出;
7. 做最后的数据类型确认,如果进一步处理,则转换;(conversionService)
// 验证字段有效性,如有必要的话, 以 Valid* 开头即符合验证前提 /** * Validate the model attribute if applicable. * <p>The default implementation checks for {@code @javax.validation.Valid}, * Spring's {@link org.springframework.validation.annotation.Validated}, * and custom annotations whose name starts with "Valid". * @param binder the DataBinder to be used * @param methodParam the method parameter */ protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) { Annotation[] annotations = methodParam.getParameterAnnotations(); for (Annotation ann : annotations) { Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); // 只要以 Valid 打头的验证都可以,即做到 spring 自身的验证方式有效,但是也兼容其他框架的验证方式 if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) { Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann)); Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); // 调用验证方式,一般为 调用其 validate() 方法; binder.validate(validationHints); break; } } } /** * Invoke the specified Validators, if any, with the given validation hints. * <p>Note: Validation hints may get ignored by the actual target Validator. * @param validationHints one or more hint objects to be passed to a {@link SmartValidator} * @see #setValidator(Validator) * @see SmartValidator#validate(Object, Errors, Object...) */ public void validate(Object... validationHints) { for (Validator validator : getValidators()) { if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) { ((SmartValidator) validator).validate(getTarget(), getBindingResult(), validationHints); } else if (validator != null) { validator.validate(getTarget(), getBindingResult()); } } }
// 转换堆栈如下: "http-nio-8080-exec-6@2418" daemon prio=5 tid=0x25 nid=NA runnable java.lang.Thread.State: RUNNABLE at org.springframework.core.convert.support.GenericConversionService.handleResult(GenericConversionService.java:328) at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:204) at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:173) at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:108) at org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:64) at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:47) at org.springframework.validation.DataBinder.convertIfNecessary(DataBinder.java:713) at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:126) at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) at javax.servlet.http.HttpServlet.service(HttpServlet.java:635) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) - locked <0x2028> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) View Code
/** * Convert the value to the required type (if necessary from a String), * for the specified property. * @param propertyName name of the property * @param oldValue the previous value, if available (may be {@code null}) * @param newValue the proposed new value * @param requiredType the type we must convert to * (or {@code null} if not known, for example in case of a collection element) * @param typeDescriptor the descriptor for the target property or field * @return the new value, possibly the result of type conversion * @throws IllegalArgumentException if type conversion failed */ @SuppressWarnings("unchecked") public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException { // Custom editor for this type? PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName); ConversionFailedException conversionAttemptEx = null; // No custom editor but custom ConversionService specified? ConversionService conversionService = this.propertyEditorRegistry.getConversionService(); if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) { TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue); if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) { try { return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor); } catch (ConversionFailedException ex) { // fallback to default conversion logic below conversionAttemptEx = ex; } } } Object convertedValue = newValue; // Value not of required type? if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) { if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) { TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor(); if (elementTypeDesc != null) { Class<?> elementType = elementTypeDesc.getType(); if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) { convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue); } } } if (editor == null) { editor = findDefaultEditor(requiredType); } convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor); } boolean standardConversion = false; if (requiredType != null) { // Try to apply some standard type conversion rules if appropriate. if (convertedValue != null) { if (Object.class == requiredType) { return (T) convertedValue; } else if (requiredType.isArray()) { // Array required -> apply appropriate conversion of elements. if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) { convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue); } return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType()); } else if (convertedValue instanceof Collection) { // Convert elements to target type, if determined. convertedValue = convertToTypedCollection( (Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor); standardConversion = true; } else if (convertedValue instanceof Map) { // Convert keys and values to respective target type, if determined. convertedValue = convertToTypedMap( (Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor); standardConversion = true; } if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) { convertedValue = Array.get(convertedValue, 0); standardConversion = true; } if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) { // We can stringify any primitive value... return (T) convertedValue.toString(); } else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) { if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) { try { Constructor<T> strCtor = requiredType.getConstructor(String.class); return BeanUtils.instantiateClass(strCtor, convertedValue); } catch (NoSuchMethodException ex) { // proceed with field lookup if (logger.isTraceEnabled()) { logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex); } } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex); } } } String trimmedValue = ((String) convertedValue).trim(); if (requiredType.isEnum() && "".equals(trimmedValue)) { // It's an empty enum identifier: reset the enum value to null. return null; } convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue); standardConversion = true; } else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) { convertedValue = NumberUtils.convertNumberToTargetClass( (Number) convertedValue, (Class<Number>) requiredType); standardConversion = true; } } else { // convertedValue == null if (javaUtilOptionalEmpty != null && requiredType == javaUtilOptionalEmpty.getClass()) { convertedValue = javaUtilOptionalEmpty; } } if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) { if (conversionAttemptEx != null) { // Original exception from former ConversionService call above... throw conversionAttemptEx; } else if (conversionService != null) { // ConversionService not tried before, probably custom editor found // but editor couldn't produce the required type... TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue); if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) { return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor); } } // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException StringBuilder msg = new StringBuilder(); msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue)); msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'"); if (propertyName != null) { msg.append(" for property '").append(propertyName).append("'"); } if (editor != null) { msg.append(": PropertyEditor [").append(editor.getClass().getName()).append( "] returned inappropriate value of type '").append( ClassUtils.getDescriptiveType(convertedValue)).append("'"); throw new IllegalArgumentException(msg.toString()); } else { msg.append(": no matching editors or conversion strategy found"); throw new IllegalStateException(msg.toString()); } } } if (conversionAttemptEx != null) { if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) { throw conversionAttemptEx; } logger.debug("Original ConversionService attempt failed - ignored since " + "PropertyEditor based conversion eventually succeeded", conversionAttemptEx); } return (T) convertedValue; }
以上是 @ModelAttribute 的参数解析, 下面我们再来看两个常用的类型解析:
1. @RequestParam 解析, 解析普通的变量;
2. @RequestBody 解析, 解析 json 的请求;
@RequestParam 的参数解析, 从 HandlerMethodArgumentResolverComposite -> RequestParamMethodArgumentResolver :
// org.springframework.web.method.annotation.RequestParamMethodArgumentResolver // org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver @Override public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 使用 NamedValueInfo 来封装参数, NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); MethodParameter nestedParameter = parameter.nestedIfOptional(); // 变量名称替换 比如将 ${a} 替换为 a; 这里会经过一层 BeanExpressionResolver 的解析处理 Object resolvedName = resolveStringValue(namedValueInfo.name); if (resolvedName == null) { throw new IllegalArgumentException( "Specified name must not resolve to null: [" + namedValueInfo.name + "]"); } // 根据参数名, 获取该值,该方法为 子类的扩展点,即由 RequestParamMethodArgumentResolver 处理 Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest); if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveStringValue(namedValueInfo.defaultValue); } else if (namedValueInfo.required && !nestedParameter.isOptional()) { handleMissingValue(namedValueInfo.name, nestedParameter, webRequest); } arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType()); } else if ("".equals(arg) && namedValueInfo.defaultValue != null) { arg = resolveStringValue(namedValueInfo.defaultValue); } if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); try { // 进行类型转换切入点处理, 由于前面返回的参数是 String 类型的,所以,类型的转换必然交给此处处理 arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter); } catch (ConversionNotSupportedException ex) { throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(), namedValueInfo.name, parameter, ex.getCause()); } catch (TypeMismatchException ex) { throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(), namedValueInfo.name, parameter, ex.getCause()); } } // 再一个预留扩展点,供用户自定义再次处理结果,默认为空 handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; } /** * Obtain the named value for the given method parameter. */ private NamedValueInfo getNamedValueInfo(MethodParameter parameter) { NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter); if (namedValueInfo == null) { namedValueInfo = createNamedValueInfo(parameter); namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo); this.namedValueInfoCache.put(parameter, namedValueInfo); } return namedValueInfo; } // RequestParamMethodArgumentResolver, 进行创建 NamedValueInfo @Override protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { RequestParam ann = parameter.getParameterAnnotation(RequestParam.class); return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo()); } /** * Resolve the given annotation-specified value, * potentially containing placeholders and expressions. */ private Object resolveStringValue(String value) { if (this.configurableBeanFactory == null) { return value; } String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value); BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver(); if (exprResolver == null) { return value; } return exprResolver.evaluate(placeholdersResolved, this.expressionContext); } // RequestParamMethodArgumentResolver @Override protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class); Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { return mpArg; } Object arg = null; if (multipartRequest != null) { List<MultipartFile> files = multipartRequest.getFiles(name); if (!files.isEmpty()) { arg = (files.size() == 1 ? files.get(0) : files); } } if (arg == null) { // 此处允许传入数组长度的为n的参数,但是只会取第一个参数使用, 而且返回值都是 String 类型 String[] paramValues = request.getParameterValues(name); if (paramValues != null) { arg = (paramValues.length == 1 ? paramValues[0] : paramValues); } } return arg; }
总结下 @RequestParam 的处理过程:
1. 由于 RequestParam 的处理方式简单,直接继承父类操作 resolveArgument();
2. 使用 namedValueInfo 来封装请求参数, 一般只针对简单类型的参数操作;
3. 解析定义的 变量名称,可支持 ${xxx} 这样的 expr 表达式处理;
4. 根据变量名称,从request中获取参数,返回第一个值作为参数的原始值;
5. 经过一些类型转换操作,如果需要的话;
6. 值设置完成后,预留一个扩展点给用户;
其实,简单说 @RequestParam 的处理就是,直接 获取 request 中对应的变量值即可;
下面,我们来看一个复杂点的参数解析: @RequestBody 的解析;
起点当然还是 HandlerMethodArgumentResolverComposite.resolveArgument() 了;
// org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor /** * Throws MethodArgumentNotValidException if validation fails. * @throws HttpMessageNotReadableException if {@link RequestBody#required()} * is {@code true} and there is no body content or if there is no suitable * converter to read the content with. */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 先检查可选选循环嵌套数情况 parameter = parameter.nestedIfOptional(); // 使用消息转换器进行接收参数,因为 对json的解析,不像其他参数解析一样套路只有一个,这个是有大量数据的解析,应该把这个选择权交给用户 // 比如默认使用 jackson 来解析, 但是往往其效率并不高,而用户则可以选择像 fastjson 这样的组件来处理 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); return adaptArgumentIfNecessary(arg, parameter); } @Override protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest); // 调用父类解析方法 Object arg = readWithMessageConverters(inputMessage, parameter, paramType); if (arg == null) { if (checkRequired(parameter)) { throw new HttpMessageNotReadableException("Required request body is missing: " + parameter.getMethod().toGenericString()); } } return arg; } // org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver /** * Create the method argument value of the expected parameter type by reading * from the given HttpInputMessage. * @param <T> the expected type of the argument value to be created * @param inputMessage the HTTP input message representing the current request * @param parameter the method parameter descriptor (may be {@code null}) * @param targetType the target type, not necessarily the same as the method * parameter type, e.g. for {@code HttpEntity<String>}. * @return the created method argument value * @throws IOException if the reading from the request fails * @throws HttpMediaTypeNotSupportedException if no suitable message converter is found */ @SuppressWarnings("unchecked") protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { // Content-Type: application/json MediaType contentType; boolean noContentType = false; try { contentType = inputMessage.getHeaders().getContentType(); } catch (InvalidMediaTypeException ex) { throw new HttpMediaTypeNotSupportedException(ex.getMessage()); } if (contentType == null) { noContentType = true; contentType = MediaType.APPLICATION_OCTET_STREAM; } Class<?> contextClass = (parameter != null ? parameter.getContainingClass() : null); Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null); if (targetClass == null) { ResolvableType resolvableType = (parameter != null ? ResolvableType.forMethodParameter(parameter) : ResolvableType.forType(targetType)); targetClass = (Class<T>) resolvableType.resolve(); } HttpMethod httpMethod = ((HttpRequest) inputMessage).getMethod(); Object body = NO_VALUE; try { // 通过获取 inputMessage.getBody(); 并封装为 EmptyBodyCheckingHttpInputMessage inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage); // 循环调用 消息转换器, 依次处理 for (HttpMessageConverter<?> converter : this.messageConverters) { Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass(); if (converter instanceof GenericHttpMessageConverter) { // fastjson 走这里 GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter; if (genericConverter.canRead(targetType, contextClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]"); } if (inputMessage.getBody() != null) { inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType); body = genericConverter.read(targetType, contextClass, inputMessage); body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType); } break; } } else if (targetClass != null) { // 调用转换器的 canRead() 方法,判断是否可处理该类型的数据,由这郸城 接入 json 转换器 // 当然,这里在公共抽象类中会有一个封装, return supports(clazz) && canRead(mediaType); 可以重写这两个方法 // 在springmvc中,不定义 json 转换器将导致报错 if (converter.canRead(targetClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]"); } // 做消息转换的切点埋入 if (inputMessage.getBody() != null) { inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType); body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage); body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType); } break; } } } } catch (IOException ex) { throw new HttpMessageNotReadableException("I/O error while reading input message", ex); } if (body == NO_VALUE) { if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) || (noContentType && inputMessage.getBody() == null)) { return null; } throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes); } return body; } // 内部类 EmptyBodyCheckingHttpInputMessage 如下 private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage { private final HttpHeaders headers; private final InputStream body; private final HttpMethod method; public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException { this.headers = inputMessage.getHeaders(); InputStream inputStream = inputMessage.getBody(); if (inputStream == null) { this.body = null; } else if (inputStream.markSupported()) { inputStream.mark(1); this.body = (inputStream.read() != -1 ? inputStream : null); inputStream.reset(); } else { // 检测是否有数据,没有则设置为 null PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream); int b = pushbackInputStream.read(); if (b == -1) { this.body = null; } else { this.body = pushbackInputStream; pushbackInputStream.unread(b); } } this.method = ((HttpRequest) inputMessage).getMethod(); } @Override public HttpHeaders getHeaders() { return this.headers; } @Override public InputStream getBody() throws IOException { return this.body; } public HttpMethod getMethod() { return this.method; } }
这里我们使用 fastjson 的转换器来看一下json的转换吧; FastJsonHttpMessageConverter.read()
由于 FastJsonHttpMessageConverter 是一个 GenericHttpMessageConverter, 所以会走第一个通用逻辑.
// com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) { return super.canRead(contextClass, mediaType); } @Override protected boolean supports(Class<?> clazz) { // 交由 canRead() 统一控制 return true; } /** * Returns {@code true} if any of the {@linkplain #setSupportedMediaTypes(List) * supported} media types {@link MediaType#includes(MediaType) include} the * given media type. * @param mediaType the media type to read, can be {@code null} if not specified. * Typically the value of a {@code Content-Type} header. * @return {@code true} if the supported media types include the media type, * or if the media type is {@code null} */ protected boolean canRead(MediaType mediaType) { // 没有 application/json 的设置也是支持的哦 if (mediaType == null) { return true; } for (MediaType supportedMediaType : getSupportedMediaTypes()) { if (supportedMediaType.includes(mediaType)) { return true; } } return false; } // 而其 read() 方法,则是比较简洁的,直接调用 fastjson 的 parseObject() 方法即可; /* * @see org.springframework.http.converter.GenericHttpMessageConverter#read(java.lang.reflect.Type, java.lang.Class, org.springframework.http.HttpInputMessage) */ public Object read(Type type, // Class<?> contextClass, // HttpInputMessage inputMessage // ) throws IOException, HttpMessageNotReadableException { InputStream in = inputMessage.getBody(); return JSON.parseObject(in, fastJsonConfig.getCharset(), type, fastJsonConfig.getFeatures()); }
好了,我们来总结下 json 的转换方式吧!
1. 检查循环嵌套问题;
2. 将消息体封装进 EmptyBodyCheckingHttpInputMessage 中;
3. 获取配置好的转换器,其中必须要有 json 转换器,否则将报错;
4. 调用配置json转换器,进行格式验证;
5. 进行消息转换,转换逻辑与spring就没什么关系了;(如果有兴趣查看json的转换逻辑可以参考fastjson源码)
这里一个关键的步骤是,配置一个json转换器! 这里给出一个最简单的配置方式:
<mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json;charset=UTF-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
仅此足够!
以上所述就是小编给大家介绍的《springmvc 请求参数解析细节》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- flask 源码解析5:请求
- Retrofit网络请求源码解析
- Volley 源码解析之网络请求
- dubbo源码解析(四十六)消费端发送请求过程
- Go Web编程--深入学习解析HTTP请求
- 详解用 Go 语言解析各种 HTTP 请求的方法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。