springmvc 的请求流程,相信大家已经很熟悉了,不熟悉的同学可以参考下资料!

  有了整体流程的概念,是否对其中的实现细节就很清楚呢?我觉得不一定,比如:单是参数解析这块,就是个大学问呢?

  首先,我们从最靠近请求末端的地方说起!此时,handler已经找到,即将进行处理!

  这是在 RequestMappingHandlerAdapter 的处理方法 handleInternal(), 将请求交给业务代码的地方!

以下是 @ModelAttributeMethodProcessor进行处理的参数处理堆栈:

  1. "http-nio-8080-exec-2@2414" daemon prio=5 tid=0x21 nid=NA runnable
  2. java.lang.Thread.State: RUNNABLE
  3. at org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler.setValue(BeanWrapperImpl.java:358)
  4. at org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(AbstractNestablePropertyAccessor.java:469)
  5. at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:292)
  6. at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:280)
  7. at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:95)
  8. at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:859)
  9. at org.springframework.validation.DataBinder.doBind(DataBinder.java:755)
  10. at org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:192)
  11. at org.springframework.web.bind.ServletRequestDataBinder.bind(ServletRequestDataBinder.java:106)
  12. at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.bindRequestParameters(ServletModelAttributeMethodProcessor.java:152)
  13. at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:113)
  14. at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
  15. at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158)
  16. at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
  17. at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
  18. at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
  19. at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
  20. at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
  21. at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
  22. at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
  23. at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
  24. at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
  25. at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
  26. at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
  27. at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
  28. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
  29. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  30. at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
  31. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  32. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  33. at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
  34. at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
  35. at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
  36. at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
  37. at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
  38. at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
  39. at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
  40. at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
  41. at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
  42. at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
  43. at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
  44. at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
  45. at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
  46. - locked <0x19d0> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
  47. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
  48. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
  49. at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
  50. at java.lang.Thread.run(Thread.java:745)
  1. // org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
  2. @Override
  3. protected ModelAndView handleInternal(HttpServletRequest request,
  4. HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
  5.  
  6. ModelAndView mav;
  7. checkRequest(request);
  8.  
  9. // Execute invokeHandlerMethod in synchronized block if required.
  10. if (this.synchronizeOnSession) {
  11. HttpSession session = request.getSession(false);
  12. if (session != null) {
  13. Object mutex = WebUtils.getSessionMutex(session);
  14. synchronized (mutex) {
  15. mav = invokeHandlerMethod(request, response, handlerMethod);
  16. }
  17. }
  18. else {
  19. // No HttpSession available -> no mutex necessary
  20. mav = invokeHandlerMethod(request, response, handlerMethod);
  21. }
  22. }
  23. else {
  24. // No synchronization on session demanded at all...
  25. // 更多的时候,我们都是无状态请求的,所以都会走这里,进行业务接入
  26. mav = invokeHandlerMethod(request, response, handlerMethod);
  27. }
  28.  
  29. if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
  30. if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
  31. applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
  32. }
  33. else {
  34. prepareResponse(response);
  35. }
  36. }
  37.  
  38. return mav;
  39. }
  40.  
  41. // 然后交给 RequestMappingHandlerAdapter 处理
  42. /**
  43. * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
  44. * if view resolution is required.
  45. * @since 4.2
  46. * @see #createInvocableHandlerMethod(HandlerMethod)
  47. */
  48. protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
  49. HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
  50.  
  51. // 将request和response封装为一个类,方便后续使用
  52. ServletWebRequest webRequest = new ServletWebRequest(request, response);
  53. try {
  54. // 使用 binderFactory 进行参数解析,该 binderFactory 将会伴随整个参数的一生
  55. // 这个binderFactory 与 方法和参数关联
  56. // 然后根据 binderFactory 获取 modelFactory, modelFactory 会绑定一些属性和参数解析器到里面,备用
  57. WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
  58. ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
  59.  
  60. // 创建 最终可用于调用业务方法的包装
  61. ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
  62. invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
  63. invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
  64. invocableMethod.setDataBinderFactory(binderFactory);
  65. invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
  66.  
  67. // 作为返回属性的一个容器
  68. ModelAndViewContainer mavContainer = new ModelAndViewContainer();
  69. mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
  70. modelFactory.initModel(webRequest, mavContainer, invocableMethod);
  71. mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
  72.  
  73. AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
  74. asyncWebRequest.setTimeout(this.asyncRequestTimeout);
  75.  
  76. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  77. asyncManager.setTaskExecutor(this.taskExecutor);
  78. asyncManager.setAsyncWebRequest(asyncWebRequest);
  79. asyncManager.registerCallableInterceptors(this.callableInterceptors);
  80. asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
  81.  
  82. if (asyncManager.hasConcurrentResult()) {
  83. Object result = asyncManager.getConcurrentResult();
  84. mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
  85. asyncManager.clearConcurrentResult();
  86. if (logger.isDebugEnabled()) {
  87. logger.debug("Found concurrent result value [" + result + "]");
  88. }
  89. invocableMethod = invocableMethod.wrapConcurrentResult(result);
  90. }
  91.  
  92. // 进一步调用 handler 方法,进入参数解析状态
  93. invocableMethod.invokeAndHandle(webRequest, mavContainer);
  94. if (asyncManager.isConcurrentHandlingStarted()) {
  95. return null;
  96. }
  97.  
  98. return getModelAndView(mavContainer, modelFactory, webRequest);
  99. }
  100. finally {
  101. webRequest.requestCompleted();
  102. }
  103. }
  104.  
  105. private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
  106. Class<?> handlerType = handlerMethod.getBeanType();
  107. Set<Method> methods = this.initBinderCache.get(handlerType);
  108. if (methods == null) {
  109. methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
  110. this.initBinderCache.put(handlerType, methods);
  111. }
  112. List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
  113. // Global methods first
  114. for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
  115. if (entry.getKey().isApplicableToBeanType(handlerType)) {
  116. Object bean = entry.getKey().resolveBean();
  117. for (Method method : entry.getValue()) {
  118. initBinderMethods.add(createInitBinderMethod(bean, method));
  119. }
  120. }
  121. }
  122. for (Method method : methods) {
  123. Object bean = handlerMethod.getBean();
  124. initBinderMethods.add(createInitBinderMethod(bean, method));
  125. }
  126. return createDataBinderFactory(initBinderMethods);
  127. }
  128.  
  129. private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
  130. SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
  131. Class<?> handlerType = handlerMethod.getBeanType();
  132. Set<Method> methods = this.modelAttributeCache.get(handlerType);
  133. if (methods == null) {
  134. methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
  135. this.modelAttributeCache.put(handlerType, methods);
  136. }
  137. List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>();
  138. // Global methods first
  139. for (Entry<ControllerAdviceBean, Set<Method>> entry : this.modelAttributeAdviceCache.entrySet()) {
  140. if (entry.getKey().isApplicableToBeanType(handlerType)) {
  141. Object bean = entry.getKey().resolveBean();
  142. for (Method method : entry.getValue()) {
  143. attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
  144. }
  145. }
  146. }
  147. for (Method method : methods) {
  148. Object bean = handlerMethod.getBean();
  149. attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
  150. }
  151. return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
  152. }
  153.  
  154. // org.springframework.web.method.support.InvocableHandlerMethod
  155. /**
  156. * Invoke the method and handle the return value through one of the
  157. * configured {@link HandlerMethodReturnValueHandler}s.
  158. * @param webRequest the current request
  159. * @param mavContainer the ModelAndViewContainer for this request
  160. * @param providedArgs "given" arguments matched by type (not resolved)
  161. */
  162. public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
  163. Object... providedArgs) throws Exception {
  164.  
  165. // 此处只处理返回值问题,具体的调用逻辑再封装, 其中 providedArgs 此时仅是空值,将会在后续处理
  166. Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
  167. setResponseStatus(webRequest);
  168.  
  169. if (returnValue == null) {
  170. if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
  171. mavContainer.setRequestHandled(true);
  172. return;
  173. }
  174. }
  175. else if (StringUtils.hasText(getResponseStatusReason())) {
  176. mavContainer.setRequestHandled(true);
  177. return;
  178. }
  179.  
  180. mavContainer.setRequestHandled(false);
  181. try {
  182. this.returnValueHandlers.handleReturnValue(
  183. returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
  184. }
  185. catch (Exception ex) {
  186. if (logger.isTraceEnabled()) {
  187. logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
  188. }
  189. throw ex;
  190. }
  191. }

  以上大体调用流程,下面我们来看下参数解析:

  1. /**
  2. * Invoke the method after resolving its argument values in the context of the given request.
  3. * <p>Argument values are commonly resolved through {@link HandlerMethodArgumentResolver}s.
  4. * The {@code providedArgs} parameter however may supply argument values to be used directly,
  5. * i.e. without argument resolution. Examples of provided argument values include a
  6. * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
  7. * Provided argument values are checked before argument resolvers.
  8. * @param request the current request
  9. * @param mavContainer the ModelAndViewContainer for this request
  10. * @param providedArgs "given" arguments matched by type, not resolved
  11. * @return the raw value returned by the invoked method
  12. * @exception Exception raised if no suitable argument resolver can be found,
  13. * or if the method raised an exception
  14. */
  15. public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
  16. Object... providedArgs) throws Exception {
  17.  
  18. // 真正的参数解析在此处开始,是本文关心的点, 此时 providedArgs 还是为空的
  19. Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
  20. if (logger.isTraceEnabled()) {
  21. logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
  22. "' with arguments " + Arrays.toString(args));
  23. }
  24. Object returnValue = doInvoke(args);
  25. if (logger.isTraceEnabled()) {
  26. logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
  27. "] returned [" + returnValue + "]");
  28. }
  29. return returnValue;
  30. }

  真正的参数解析如下:

  1. /**
  2. * Get the method argument values for the current request.
  3. */
  4. private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
  5. Object... providedArgs) throws Exception {
  6.  
  7. MethodParameter[] parameters = getMethodParameters();
  8. Object[] args = new Object[parameters.length];
  9. for (int i = 0; i < parameters.length; i++) {
  10. MethodParameter parameter = parameters[i];
  11. parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
  12. args[i] = resolveProvidedArgument(parameter, providedArgs);
  13. if (args[i] != null) {
  14. continue;
  15. }
  16. // 依次遍历解析器,只要有一个支持解析,则进行解析逻辑,否则抛出异常
  17. if (this.argumentResolvers.supportsParameter(parameter)) {
  18. try {
  19. // 进行具体解析器的解析,当然,这里是解析器列表,调用后会再进行合适解析器的筛选过程
  20. args[i] = this.argumentResolvers.resolveArgument(
  21. parameter, mavContainer, request, this.dataBinderFactory);
  22. continue;
  23. }
  24. catch (Exception ex) {
  25. if (logger.isDebugEnabled()) {
  26. logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
  27. }
  28. throw ex;
  29. }
  30. }
  31. if (args[i] == null) {
  32. throw new IllegalStateException("Could not resolve method parameter at index " +
  33. parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
  34. ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
  35. }
  36. }
  37. return args;
  38. }

  可以看出,参数解析主要分为几步:
    1. 获取参数类型数组,供后续填充使用;
    2. 对每个参数类型,单独调用参数解析过程;
    3. 在进行具体解析之前,先尝试简单解析是否成功(类似于读取缓存),不成功则再进行深度解析;
    4. 深度解析交由所有解析器进行处理, 即 HandlerMethodArgumentResolverComposite, 它包含了所有的解析器;

  1. // org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
  2. /**
  3. * Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it.
  4. * @throws IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
  5. */
  6. @Override
  7. public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
  8. NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  9.  
  10. // 获取具体适用的解析器,即调用其 supportsParameter(param) 方法判断即可
  11. HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
  12. if (resolver == null) {
  13. throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
  14. }
  15. // 解析器获取到后,就交由具体的解析器进行解析了,这里有很多的分支了,咱们可以挑选几个来看一下就可以了
  16. return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
  17. }

下面,我我们看一下 @ModelAttribute 修饰的参数解析方法(注意: 这里的解析只是针对任意一个参数的解析,所以无需关注整体参数形式):

  1. // org.springframework.web.method.annotation.ModelAttributeMethodProcessor
  2. /**
  3. * Resolve the argument from the model or if not found instantiate it with
  4. * its default if it is available. The model attribute is then populated
  5. * with request values via data binding and optionally validated
  6. * if {@code @java.validation.Valid} is present on the argument.
  7. * @throws BindException if data binding and validation result in an error
  8. * and the next method parameter is not of type {@link Errors}.
  9. * @throws Exception if WebDataBinder initialization fails.
  10. */
  11. @Override
  12. public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
  13. NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  14.  
  15. // 获取参数名字, 如: User user 获取的结果为 user ; 其实现原理为获取该注解的 value 值
  16. // 如果缓存中没有该参数的解析,那么就实例化一个参数实例,并放入缓存
  17. String name = ModelFactory.getNameForParameter(parameter);
  18. Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) :
  19. createAttribute(name, parameter, binderFactory, webRequest));
  20.  
  21. if (!mavContainer.isBindingDisabled(name)) {
  22. ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
  23. if (ann != null && !ann.binding()) {
  24. mavContainer.setBindingDisabled(name);
  25. }
  26. }
  27.  
  28. // 将参数包装进 WebDataBinder 中,方便统一操作, 将参数实例放入到 target 字段域中
  29. WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
  30. if (binder.getTarget() != null) {
  31. // 进行参数的解析过程, 将由 ServletModelAttributeMethodProcessor 处理
  32. if (!mavContainer.isBindingDisabled(name)) {
  33. bindRequestParameters(binder, webRequest);
  34. }
  35. validateIfApplicable(binder, parameter);
  36. if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
  37. throw new BindException(binder.getBindingResult());
  38. }
  39. }
  40.  
  41. // Add resolved attribute and BindingResult at the end of the model
  42. Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
  43. mavContainer.removeAttributes(bindingResultModel);
  44. mavContainer.addAllAttributes(bindingResultModel);
  45.  
  46. return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
  47. }
  48. // 如下为获取参数名的实现方式,如果指明 value, 则直接获取, 否则生成一个参数简称名的字段, 注意此处的名字并不一定是 参数真正的命名
  49. // 其实函数的调用只要参数位置正确即可,并不需要真实的变量名
  50. public static String getNameForParameter(MethodParameter parameter) {
  51. ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
  52. String name = (ann != null ? ann.value() : null);
  53. return (StringUtils.hasText(name) ? name : Conventions.getVariableNameForParameter(parameter));
  54. }
  55. // org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor
  56. /**
  57. * This implementation downcasts {@link WebDataBinder} to
  58. * {@link ServletRequestDataBinder} before binding.
  59. * @see ServletRequestDataBinderFactory
  60. */
  61. @Override
  62. protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
  63. // 取出request 对象,因为只有 request 中有需要处理的参数,此时和 response 是确认无关的
  64. ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
  65. ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
  66. // 调用自身的 binder 处理
  67. servletBinder.bind(servletRequest);
  68. }
  69.  
  70. /**
  71. * Bind the parameters of the given request to this binder's target,
  72. * also binding multipart files in case of a multipart request.
  73. * <p>This call can create field errors, representing basic binding
  74. * errors like a required field (code "required"), or type mismatch
  75. * between value and bean property (code "typeMismatch").
  76. * <p>Multipart files are bound via their parameter name, just like normal
  77. * HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
  78. * invoking a "setUploadedFile" setter method.
  79. * <p>The type of the target property for a multipart file can be MultipartFile,
  80. * byte[], or String. The latter two receive the contents of the uploaded file;
  81. * all metadata like original file name, content type, etc are lost in those cases.
  82. * @param request request with parameters to bind (can be multipart)
  83. * @see org.springframework.web.multipart.MultipartHttpServletRequest
  84. * @see org.springframework.web.multipart.MultipartFile
  85. * @see #bind(org.springframework.beans.PropertyValues)
  86. */
  87. public void bind(ServletRequest request) {
  88. // 将所有请求参数封装到 mpvs, 使后续可以直接获取, 其实现为 使用 ArrayList 来保存属性,参考: request.getParameterNames()
  89. MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
  90. MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
  91. if (multipartRequest != null) {
  92. bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
  93. }
  94. // 此处作为扩展点,预留
  95. addBindValues(mpvs, request);
  96. // 调用webDataBinder 设值到参数绑定
  97. doBind(mpvs);
  98. }
  99.  
  100. // org.springframework.web.bind.WebDataBinder
  101. /**
  102. * This implementation performs a field default and marker check
  103. * before delegating to the superclass binding process.
  104. * @see #checkFieldDefaults
  105. * @see #checkFieldMarkers
  106. */
  107. @Override
  108. protected void doBind(MutablePropertyValues mpvs) {
  109. // 检查默认值和 marker 设置
  110. checkFieldDefaults(mpvs);
  111. checkFieldMarkers(mpvs);
  112. // 调用父类实现数据 绑定,其实就是反射调用字段
  113. super.doBind(mpvs);
  114. }
  115.  
  116. // DataBinder.doBind()
  117. /**
  118. * Actual implementation of the binding process, working with the
  119. * passed-in MutablePropertyValues instance.
  120. * @param mpvs the property values to bind,
  121. * as MutablePropertyValues instance
  122. * @see #checkAllowedFields
  123. * @see #checkRequiredFields
  124. * @see #applyPropertyValues
  125. */
  126. protected void doBind(MutablePropertyValues mpvs) {
  127. checkAllowedFields(mpvs);
  128. checkRequiredFields(mpvs);
  129. // 处理值
  130. applyPropertyValues(mpvs);
  131. }
  132.  
  133. /**
  134. * Apply given property values to the target object.
  135. * <p>Default implementation applies all of the supplied property
  136. * values as bean property values. By default, unknown fields will
  137. * be ignored.
  138. * @param mpvs the property values to be bound (can be modified)
  139. * @see #getTarget
  140. * @see #getPropertyAccessor
  141. * @see #isIgnoreUnknownFields
  142. * @see #getBindingErrorProcessor
  143. * @see BindingErrorProcessor#processPropertyAccessException
  144. */
  145. protected void applyPropertyValues(MutablePropertyValues mpvs) {
  146. try {
  147. // Bind request parameters onto target object.
  148. getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
  149. }
  150. catch (PropertyBatchUpdateException ex) {
  151. // Use bind error processor to create FieldErrors.
  152. for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
  153. getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
  154. }
  155. }
  156. }
  157.  
  158. @Override
  159. public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
  160. throws BeansException {
  161.  
  162. List<PropertyAccessException> propertyAccessExceptions = null;
  163. // 此处获取参数对象的n个字段为列表,以进行循环赋值, 即循环的依据是外部传入多少值,而不是参数实例有多少字段
  164. List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
  165. ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
  166. // 多个参数值给定时,循环设值即可
  167. for (PropertyValue pv : propertyValues) {
  168. try {
  169. // This method may throw any BeansException, which won't be caught
  170. // here, if there is a critical failure such as no matching field.
  171. // We can attempt to deal only with less serious exceptions.
  172. // 对单个 field 设值
  173. setPropertyValue(pv);
  174. }
  175. catch (NotWritablePropertyException ex) {
  176. if (!ignoreUnknown) {
  177. throw ex;
  178. }
  179. // Otherwise, just ignore it and continue...
  180. }
  181. catch (NullValueInNestedPathException ex) {
  182. if (!ignoreInvalid) {
  183. throw ex;
  184. }
  185. // Otherwise, just ignore it and continue...
  186. }
  187. catch (PropertyAccessException ex) {
  188. if (propertyAccessExceptions == null) {
  189. propertyAccessExceptions = new LinkedList<PropertyAccessException>();
  190. }
  191. propertyAccessExceptions.add(ex);
  192. }
  193. }
  194.  
  195. // org.springframework.beans.AbstractNestablePropertyAccessor
  196. @Override
  197. public void setPropertyValue(PropertyValue pv) throws BeansException {
  198. PropertyTokenHolder tokens = (PropertyTokenHolder) pv.resolvedTokens;
  199. if (tokens == null) {
  200. String propertyName = pv.getName();
  201. AbstractNestablePropertyAccessor nestedPa;
  202. try {
  203. nestedPa = getPropertyAccessorForPropertyPath(propertyName);
  204. }
  205. catch (NotReadablePropertyException ex) {
  206. throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
  207. "Nested property in path '" + propertyName + "' does not exist", ex);
  208. }
  209. tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
  210. if (nestedPa == this) {
  211. pv.getOriginalPropertyValue().resolvedTokens = tokens;
  212. }
  213. nestedPa.setPropertyValue(tokens, pv);
  214. }
  215. else {
  216. setPropertyValue(tokens, pv);
  217. }
  218. }
  219.  
  220. // 对字段特殊处理,否则直接设值
  221. protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
  222. if (tokens.keys != null) {
  223. processKeyedProperty(tokens, pv);
  224. }
  225. else {
  226. processLocalProperty(tokens, pv);
  227. }
  228. }
  229.  
  230. private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
  231. PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
  232. if (ph == null || !ph.isWritable()) {
  233. if (pv.isOptional()) {
  234. if (logger.isDebugEnabled()) {
  235. logger.debug("Ignoring optional value for property '" + tokens.actualName +
  236. "' - property not found on bean class [" + getRootClass().getName() + "]");
  237. }
  238. return;
  239. }
  240. else {
  241. throw createNotWritablePropertyException(tokens.canonicalName);
  242. }
  243. }
  244.  
  245. Object oldValue = null;
  246. try {
  247. Object originalValue = pv.getValue();
  248. Object valueToApply = originalValue;
  249. if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
  250. if (pv.isConverted()) {
  251. valueToApply = pv.getConvertedValue();
  252. }
  253. else {
  254. if (isExtractOldValueForEditor() && ph.isReadable()) {
  255. try {
  256. oldValue = ph.getValue();
  257. }
  258. catch (Exception ex) {
  259. if (ex instanceof PrivilegedActionException) {
  260. ex = ((PrivilegedActionException) ex).getException();
  261. }
  262. if (logger.isDebugEnabled()) {
  263. logger.debug("Could not read previous value of property '" +
  264. this.nestedPath + tokens.canonicalName + "'", ex);
  265. }
  266. }
  267. }
  268. valueToApply = convertForProperty(
  269. tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
  270. }
  271. pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
  272. }
  273. // 经过各种判定后,终于调用反射了
  274. ph.setValue(this.wrappedObject, valueToApply);
  275. }
  276. catch (TypeMismatchException ex) {
  277. throw ex;
  278. }
  279. catch (InvocationTargetException ex) {
  280. PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
  281. this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
  282. if (ex.getTargetException() instanceof ClassCastException) {
  283. throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
  284. }
  285. else {
  286. Throwable cause = ex.getTargetException();
  287. if (cause instanceof UndeclaredThrowableException) {
  288. // May happen e.g. with Groovy-generated methods
  289. cause = cause.getCause();
  290. }
  291. throw new MethodInvocationException(propertyChangeEvent, cause);
  292. }
  293. }
  294. catch (Exception ex) {
  295. PropertyChangeEvent pce = new PropertyChangeEvent(
  296. this.rootObject, this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
  297. throw new MethodInvocationException(pce, ex);
  298. }
  299. }
  300.  
  301. // org.springframework.beans.BeanWrapperImpl$BeanPropertyHandler
  302. @Override
  303. public void setValue(final Object object, Object valueToApply) throws Exception {
  304. final Method writeMethod = (this.pd instanceof GenericTypeAwarePropertyDescriptor ?
  305. ((GenericTypeAwarePropertyDescriptor) this.pd).getWriteMethodForActualAccess() :
  306. this.pd.getWriteMethod());
  307. if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) {
  308. if (System.getSecurityManager() != null) {
  309. AccessController.doPrivileged(new PrivilegedAction<Object>() {
  310. @Override
  311. public Object run() {
  312. writeMethod.setAccessible(true);
  313. return null;
  314. }
  315. });
  316. }
  317. else {
  318. writeMethod.setAccessible(true);
  319. }
  320. }
  321. final Object value = valueToApply;
  322. if (System.getSecurityManager() != null) {
  323. try {
  324. AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
  325. @Override
  326. public Object run() throws Exception {
  327. writeMethod.invoke(object, value);
  328. return null;
  329. }
  330. }, acc);
  331. }
  332. catch (PrivilegedActionException ex) {
  333. throw ex.getException();
  334. }
  335. }
  336. else {
  337. // java.lang.reflect.Method, 反射设值
  338. writeMethod.invoke(getWrappedInstance(), value);
  339. }
  340. }

  所以,对于 @ModelAttribute 参数解析,其实主要有几步:
    1. 获取 request 中的参数,封装到 MutablePropertyValues 中;
    2. 依次检查每个字段域的有效性;
    3. 处理字段类型的转换,如:将 String 转换为 int;
    4. 获取字段的反射方法,进行字段值的绑定;
    5. 循环设置,直到所有字段都检查完;
    6. 验证字段的有效性,如有异常,抛出;
    7. 做最后的数据类型确认,如果进一步处理,则转换;(conversionService)

  1. // 验证字段有效性,如有必要的话, 以 Valid* 开头即符合验证前提
  2. /**
  3. * Validate the model attribute if applicable.
  4. * <p>The default implementation checks for {@code @javax.validation.Valid},
  5. * Spring's {@link org.springframework.validation.annotation.Validated},
  6. * and custom annotations whose name starts with "Valid".
  7. * @param binder the DataBinder to be used
  8. * @param methodParam the method parameter
  9. */
  10. protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
  11. Annotation[] annotations = methodParam.getParameterAnnotations();
  12. for (Annotation ann : annotations) {
  13. Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
  14. // 只要以 Valid 打头的验证都可以,即做到 spring 自身的验证方式有效,但是也兼容其他框架的验证方式
  15. if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
  16. Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
  17. Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
  18. // 调用验证方式,一般为 调用其 validate() 方法;
  19. binder.validate(validationHints);
  20. break;
  21. }
  22. }
  23. }
  24.  
  25. /**
  26. * Invoke the specified Validators, if any, with the given validation hints.
  27. * <p>Note: Validation hints may get ignored by the actual target Validator.
  28. * @param validationHints one or more hint objects to be passed to a {@link SmartValidator}
  29. * @see #setValidator(Validator)
  30. * @see SmartValidator#validate(Object, Errors, Object...)
  31. */
  32. public void validate(Object... validationHints) {
  33. for (Validator validator : getValidators()) {
  34. if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
  35. ((SmartValidator) validator).validate(getTarget(), getBindingResult(), validationHints);
  36. }
  37. else if (validator != null) {
  38. validator.validate(getTarget(), getBindingResult());
  39. }
  40. }
  41. }
  1. // 转换堆栈如下:
  2. "http-nio-8080-exec-6@2418" daemon prio=5 tid=0x25 nid=NA runnable
  3. java.lang.Thread.State: RUNNABLE
  4. at org.springframework.core.convert.support.GenericConversionService.handleResult(GenericConversionService.java:328)
  5. at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:204)
  6. at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:173)
  7. at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:108)
  8. at org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:64)
  9. at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:47)
  10. at org.springframework.validation.DataBinder.convertIfNecessary(DataBinder.java:713)
  11. at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:126)
  12. at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
  13. at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158)
  14. at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128)
  15. at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
  16. at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
  17. at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
  18. at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
  19. at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
  20. at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
  21. at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
  22. at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
  23. at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
  24. at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
  25. at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
  26. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
  27. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  28. at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
  29. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
  30. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
  31. at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
  32. at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
  33. at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
  34. at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
  35. at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
  36. at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
  37. at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
  38. at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
  39. at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
  40. at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
  41. at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
  42. at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
  43. at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
  44. - locked <0x2028> (a org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper)
  45. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
  46. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
  47. at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
  48. at java.lang.Thread.run(Thread.java:745)
  1. /**
  2. * Convert the value to the required type (if necessary from a String),
  3. * for the specified property.
  4. * @param propertyName name of the property
  5. * @param oldValue the previous value, if available (may be {@code null})
  6. * @param newValue the proposed new value
  7. * @param requiredType the type we must convert to
  8. * (or {@code null} if not known, for example in case of a collection element)
  9. * @param typeDescriptor the descriptor for the target property or field
  10. * @return the new value, possibly the result of type conversion
  11. * @throws IllegalArgumentException if type conversion failed
  12. */
  13. @SuppressWarnings("unchecked")
  14. public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue,
  15. Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {
  16.  
  17. // Custom editor for this type?
  18. PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
  19.  
  20. ConversionFailedException conversionAttemptEx = null;
  21.  
  22. // No custom editor but custom ConversionService specified?
  23. ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
  24. if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
  25. TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
  26. if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
  27. try {
  28. return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
  29. }
  30. catch (ConversionFailedException ex) {
  31. // fallback to default conversion logic below
  32. conversionAttemptEx = ex;
  33. }
  34. }
  35. }
  36.  
  37. Object convertedValue = newValue;
  38.  
  39. // Value not of required type?
  40. if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
  41. if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
  42. convertedValue instanceof String) {
  43. TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
  44. if (elementTypeDesc != null) {
  45. Class<?> elementType = elementTypeDesc.getType();
  46. if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
  47. convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
  48. }
  49. }
  50. }
  51. if (editor == null) {
  52. editor = findDefaultEditor(requiredType);
  53. }
  54. convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
  55. }
  56.  
  57. boolean standardConversion = false;
  58.  
  59. if (requiredType != null) {
  60. // Try to apply some standard type conversion rules if appropriate.
  61.  
  62. if (convertedValue != null) {
  63. if (Object.class == requiredType) {
  64. return (T) convertedValue;
  65. }
  66. else if (requiredType.isArray()) {
  67. // Array required -> apply appropriate conversion of elements.
  68. if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
  69. convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
  70. }
  71. return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
  72. }
  73. else if (convertedValue instanceof Collection) {
  74. // Convert elements to target type, if determined.
  75. convertedValue = convertToTypedCollection(
  76. (Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
  77. standardConversion = true;
  78. }
  79. else if (convertedValue instanceof Map) {
  80. // Convert keys and values to respective target type, if determined.
  81. convertedValue = convertToTypedMap(
  82. (Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
  83. standardConversion = true;
  84. }
  85. if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
  86. convertedValue = Array.get(convertedValue, 0);
  87. standardConversion = true;
  88. }
  89. if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
  90. // We can stringify any primitive value...
  91. return (T) convertedValue.toString();
  92. }
  93. else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
  94. if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
  95. try {
  96. Constructor<T> strCtor = requiredType.getConstructor(String.class);
  97. return BeanUtils.instantiateClass(strCtor, convertedValue);
  98. }
  99. catch (NoSuchMethodException ex) {
  100. // proceed with field lookup
  101. if (logger.isTraceEnabled()) {
  102. logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
  103. }
  104. }
  105. catch (Exception ex) {
  106. if (logger.isDebugEnabled()) {
  107. logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
  108. }
  109. }
  110. }
  111. String trimmedValue = ((String) convertedValue).trim();
  112. if (requiredType.isEnum() && "".equals(trimmedValue)) {
  113. // It's an empty enum identifier: reset the enum value to null.
  114. return null;
  115. }
  116. convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
  117. standardConversion = true;
  118. }
  119. else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
  120. convertedValue = NumberUtils.convertNumberToTargetClass(
  121. (Number) convertedValue, (Class<Number>) requiredType);
  122. standardConversion = true;
  123. }
  124. }
  125. else {
  126. // convertedValue == null
  127. if (javaUtilOptionalEmpty != null && requiredType == javaUtilOptionalEmpty.getClass()) {
  128. convertedValue = javaUtilOptionalEmpty;
  129. }
  130. }
  131.  
  132. if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
  133. if (conversionAttemptEx != null) {
  134. // Original exception from former ConversionService call above...
  135. throw conversionAttemptEx;
  136. }
  137. else if (conversionService != null) {
  138. // ConversionService not tried before, probably custom editor found
  139. // but editor couldn't produce the required type...
  140. TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
  141. if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
  142. return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
  143. }
  144. }
  145.  
  146. // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException
  147. StringBuilder msg = new StringBuilder();
  148. msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));
  149. msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");
  150. if (propertyName != null) {
  151. msg.append(" for property '").append(propertyName).append("'");
  152. }
  153. if (editor != null) {
  154. msg.append(": PropertyEditor [").append(editor.getClass().getName()).append(
  155. "] returned inappropriate value of type '").append(
  156. ClassUtils.getDescriptiveType(convertedValue)).append("'");
  157. throw new IllegalArgumentException(msg.toString());
  158. }
  159. else {
  160. msg.append(": no matching editors or conversion strategy found");
  161. throw new IllegalStateException(msg.toString());
  162. }
  163. }
  164. }
  165.  
  166. if (conversionAttemptEx != null) {
  167. if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
  168. throw conversionAttemptEx;
  169. }
  170. logger.debug("Original ConversionService attempt failed - ignored since " +
  171. "PropertyEditor based conversion eventually succeeded", conversionAttemptEx);
  172. }
  173.  
  174. return (T) convertedValue;
  175. }

以上是 @ModelAttribute 的参数解析, 下面我们再来看两个常用的类型解析:
  1. @RequestParam 解析, 解析普通的变量;
  2. @RequestBody 解析, 解析 json 的请求;

@RequestParam 的参数解析, 从 HandlerMethodArgumentResolverComposite -> RequestParamMethodArgumentResolver :

  1. // org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
  2. // org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver
  3. @Override
  4. public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
  5. NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  6.  
  7. // 使用 NamedValueInfo 来封装参数,
  8. NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
  9. MethodParameter nestedParameter = parameter.nestedIfOptional();
  10.  
  11. // 变量名称替换 比如将 ${a} 替换为 a; 这里会经过一层 BeanExpressionResolver 的解析处理
  12. Object resolvedName = resolveStringValue(namedValueInfo.name);
  13. if (resolvedName == null) {
  14. throw new IllegalArgumentException(
  15. "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
  16. }
  17.  
  18. // 根据参数名, 获取该值,该方法为 子类的扩展点,即由 RequestParamMethodArgumentResolver 处理
  19. Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
  20. if (arg == null) {
  21. if (namedValueInfo.defaultValue != null) {
  22. arg = resolveStringValue(namedValueInfo.defaultValue);
  23. }
  24. else if (namedValueInfo.required && !nestedParameter.isOptional()) {
  25. handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
  26. }
  27. arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
  28. }
  29. else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
  30. arg = resolveStringValue(namedValueInfo.defaultValue);
  31. }
  32.  
  33. if (binderFactory != null) {
  34. WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
  35. try {
  36. // 进行类型转换切入点处理, 由于前面返回的参数是 String 类型的,所以,类型的转换必然交给此处处理
  37. arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
  38. }
  39. catch (ConversionNotSupportedException ex) {
  40. throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
  41. namedValueInfo.name, parameter, ex.getCause());
  42. }
  43. catch (TypeMismatchException ex) {
  44. throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
  45. namedValueInfo.name, parameter, ex.getCause());
  46.  
  47. }
  48. }
  49.  
  50. // 再一个预留扩展点,供用户自定义再次处理结果,默认为空
  51. handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
  52.  
  53. return arg;
  54. }
  55.  
  56. /**
  57. * Obtain the named value for the given method parameter.
  58. */
  59. private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
  60. NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
  61. if (namedValueInfo == null) {
  62. namedValueInfo = createNamedValueInfo(parameter);
  63. namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
  64. this.namedValueInfoCache.put(parameter, namedValueInfo);
  65. }
  66. return namedValueInfo;
  67. }
  68.  
  69. // RequestParamMethodArgumentResolver, 进行创建 NamedValueInfo
  70. @Override
  71. protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
  72. RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
  73. return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
  74. }
  75.  
  76. /**
  77. * Resolve the given annotation-specified value,
  78. * potentially containing placeholders and expressions.
  79. */
  80. private Object resolveStringValue(String value) {
  81. if (this.configurableBeanFactory == null) {
  82. return value;
  83. }
  84. String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
  85. BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
  86. if (exprResolver == null) {
  87. return value;
  88. }
  89. return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
  90. }
  91.  
  92. // RequestParamMethodArgumentResolver
  93. @Override
  94. protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
  95. HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
  96. MultipartHttpServletRequest multipartRequest =
  97. WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
  98.  
  99. Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
  100. if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
  101. return mpArg;
  102. }
  103.  
  104. Object arg = null;
  105. if (multipartRequest != null) {
  106. List<MultipartFile> files = multipartRequest.getFiles(name);
  107. if (!files.isEmpty()) {
  108. arg = (files.size() == 1 ? files.get(0) : files);
  109. }
  110. }
  111. if (arg == null) {
  112. // 此处允许传入数组长度的为n的参数,但是只会取第一个参数使用, 而且返回值都是 String 类型
  113. String[] paramValues = request.getParameterValues(name);
  114. if (paramValues != null) {
  115. arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
  116. }
  117. }
  118. return arg;
  119. }

  总结下 @RequestParam 的处理过程:
    1. 由于 RequestParam 的处理方式简单,直接继承父类操作 resolveArgument();
    2. 使用 namedValueInfo 来封装请求参数, 一般只针对简单类型的参数操作;
    3. 解析定义的 变量名称,可支持 ${xxx} 这样的 expr 表达式处理;
    4. 根据变量名称,从request中获取参数,返回第一个值作为参数的原始值;
    5. 经过一些类型转换操作,如果需要的话;
    6. 值设置完成后,预留一个扩展点给用户;
  其实,简单说 @RequestParam 的处理就是,直接 获取 request 中对应的变量值即可;

下面,我们来看一个复杂点的参数解析: @RequestBody 的解析;

  起点当然还是 HandlerMethodArgumentResolverComposite.resolveArgument() 了;

  1. // org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
  2. /**
  3. * Throws MethodArgumentNotValidException if validation fails.
  4. * @throws HttpMessageNotReadableException if {@link RequestBody#required()}
  5. * is {@code true} and there is no body content or if there is no suitable
  6. * converter to read the content with.
  7. */
  8. @Override
  9. public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
  10. NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
  11.  
  12. // 先检查可选选循环嵌套数情况
  13. parameter = parameter.nestedIfOptional();
  14. // 使用消息转换器进行接收参数,因为 对json的解析,不像其他参数解析一样套路只有一个,这个是有大量数据的解析,应该把这个选择权交给用户
  15. // 比如默认使用 jackson 来解析, 但是往往其效率并不高,而用户则可以选择像 fastjson 这样的组件来处理
  16. Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
  17. String name = Conventions.getVariableNameForParameter(parameter);
  18.  
  19. WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
  20. if (arg != null) {
  21. validateIfApplicable(binder, parameter);
  22. if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
  23. throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
  24. }
  25. }
  26. mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
  27.  
  28. return adaptArgumentIfNecessary(arg, parameter);
  29. }
  30.  
  31. @Override
  32. protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
  33. Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
  34.  
  35. HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
  36. ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
  37.  
  38. // 调用父类解析方法
  39. Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
  40. if (arg == null) {
  41. if (checkRequired(parameter)) {
  42. throw new HttpMessageNotReadableException("Required request body is missing: " +
  43. parameter.getMethod().toGenericString());
  44. }
  45. }
  46. return arg;
  47. }
  48.  
  49. // org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver
  50. /**
  51. * Create the method argument value of the expected parameter type by reading
  52. * from the given HttpInputMessage.
  53. * @param <T> the expected type of the argument value to be created
  54. * @param inputMessage the HTTP input message representing the current request
  55. * @param parameter the method parameter descriptor (may be {@code null})
  56. * @param targetType the target type, not necessarily the same as the method
  57. * parameter type, e.g. for {@code HttpEntity<String>}.
  58. * @return the created method argument value
  59. * @throws IOException if the reading from the request fails
  60. * @throws HttpMediaTypeNotSupportedException if no suitable message converter is found
  61. */
  62. @SuppressWarnings("unchecked")
  63. protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
  64. Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
  65.  
  66. // Content-Type: application/json
  67. MediaType contentType;
  68. boolean noContentType = false;
  69. try {
  70. contentType = inputMessage.getHeaders().getContentType();
  71. }
  72. catch (InvalidMediaTypeException ex) {
  73. throw new HttpMediaTypeNotSupportedException(ex.getMessage());
  74. }
  75. if (contentType == null) {
  76. noContentType = true;
  77. contentType = MediaType.APPLICATION_OCTET_STREAM;
  78. }
  79.  
  80. Class<?> contextClass = (parameter != null ? parameter.getContainingClass() : null);
  81. Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
  82. if (targetClass == null) {
  83. ResolvableType resolvableType = (parameter != null ?
  84. ResolvableType.forMethodParameter(parameter) : ResolvableType.forType(targetType));
  85. targetClass = (Class<T>) resolvableType.resolve();
  86. }
  87.  
  88. HttpMethod httpMethod = ((HttpRequest) inputMessage).getMethod();
  89. Object body = NO_VALUE;
  90.  
  91. try {
  92. // 通过获取 inputMessage.getBody(); 并封装为 EmptyBodyCheckingHttpInputMessage
  93. inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);
  94.  
  95. // 循环调用 消息转换器, 依次处理
  96. for (HttpMessageConverter<?> converter : this.messageConverters) {
  97. Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
  98. if (converter instanceof GenericHttpMessageConverter) {
  99. // fastjson 走这里
  100. GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
  101. if (genericConverter.canRead(targetType, contextClass, contentType)) {
  102. if (logger.isDebugEnabled()) {
  103. logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
  104. }
  105. if (inputMessage.getBody() != null) {
  106. inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
  107. body = genericConverter.read(targetType, contextClass, inputMessage);
  108. body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
  109. }
  110. else {
  111. body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
  112. }
  113. break;
  114. }
  115. }
  116. else if (targetClass != null) {
  117. // 调用转换器的 canRead() 方法,判断是否可处理该类型的数据,由这郸城 接入 json 转换器
  118. // 当然,这里在公共抽象类中会有一个封装, return supports(clazz) && canRead(mediaType); 可以重写这两个方法
  119. // 在springmvc中,不定义 json 转换器将导致报错
  120. if (converter.canRead(targetClass, contentType)) {
  121. if (logger.isDebugEnabled()) {
  122. logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
  123. }
  124. // 做消息转换的切点埋入
  125. if (inputMessage.getBody() != null) {
  126. inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
  127. body = ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
  128. body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
  129. }
  130. else {
  131. body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
  132. }
  133. break;
  134. }
  135. }
  136. }
  137. }
  138. catch (IOException ex) {
  139. throw new HttpMessageNotReadableException("I/O error while reading input message", ex);
  140. }
  141.  
  142. if (body == NO_VALUE) {
  143. if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
  144. (noContentType && inputMessage.getBody() == null)) {
  145. return null;
  146. }
  147. throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
  148. }
  149.  
  150. return body;
  151. }
  152.  
  153. // 内部类 EmptyBodyCheckingHttpInputMessage 如下
  154. private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage {
  155.  
  156. private final HttpHeaders headers;
  157.  
  158. private final InputStream body;
  159.  
  160. private final HttpMethod method;
  161.  
  162. public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
  163. this.headers = inputMessage.getHeaders();
  164. InputStream inputStream = inputMessage.getBody();
  165. if (inputStream == null) {
  166. this.body = null;
  167. }
  168. else if (inputStream.markSupported()) {
  169. inputStream.mark(1);
  170. this.body = (inputStream.read() != -1 ? inputStream : null);
  171. inputStream.reset();
  172. }
  173. else {
  174. // 检测是否有数据,没有则设置为 null
  175. PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
  176. int b = pushbackInputStream.read();
  177. if (b == -1) {
  178. this.body = null;
  179. }
  180. else {
  181. this.body = pushbackInputStream;
  182. pushbackInputStream.unread(b);
  183. }
  184. }
  185. this.method = ((HttpRequest) inputMessage).getMethod();
  186. }
  187.  
  188. @Override
  189. public HttpHeaders getHeaders() {
  190. return this.headers;
  191. }
  192.  
  193. @Override
  194. public InputStream getBody() throws IOException {
  195. return this.body;
  196. }
  197.  
  198. public HttpMethod getMethod() {
  199. return this.method;
  200. }
  201. }

  这里我们使用 fastjson 的转换器来看一下json的转换吧; FastJsonHttpMessageConverter.read()

  由于 FastJsonHttpMessageConverter 是一个 GenericHttpMessageConverter, 所以会走第一个通用逻辑.

  1. // com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter
  2. public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
  3. return super.canRead(contextClass, mediaType);
  4. }
  5. @Override
  6. protected boolean supports(Class<?> clazz) {
  7. // 交由 canRead() 统一控制
  8. return true;
  9. }
  10. /**
  11. * Returns {@code true} if any of the {@linkplain #setSupportedMediaTypes(List)
  12. * supported} media types {@link MediaType#includes(MediaType) include} the
  13. * given media type.
  14. * @param mediaType the media type to read, can be {@code null} if not specified.
  15. * Typically the value of a {@code Content-Type} header.
  16. * @return {@code true} if the supported media types include the media type,
  17. * or if the media type is {@code null}
  18. */
  19. protected boolean canRead(MediaType mediaType) {
  20. // 没有 application/json 的设置也是支持的哦
  21. if (mediaType == null) {
  22. return true;
  23. }
  24. for (MediaType supportedMediaType : getSupportedMediaTypes()) {
  25. if (supportedMediaType.includes(mediaType)) {
  26. return true;
  27. }
  28. }
  29. return false;
  30. }
  31.  
  32. // 而其 read() 方法,则是比较简洁的,直接调用 fastjson 的 parseObject() 方法即可;
  33. /*
  34. * @see org.springframework.http.converter.GenericHttpMessageConverter#read(java.lang.reflect.Type, java.lang.Class, org.springframework.http.HttpInputMessage)
  35. */
  36. public Object read(Type type, //
  37. Class<?> contextClass, //
  38. HttpInputMessage inputMessage //
  39. ) throws IOException, HttpMessageNotReadableException {
  40.  
  41. InputStream in = inputMessage.getBody();
  42. return JSON.parseObject(in, fastJsonConfig.getCharset(), type, fastJsonConfig.getFeatures());
  43. }

  好了,我们来总结下 json 的转换方式吧!
    1. 检查循环嵌套问题;
    2. 将消息体封装进 EmptyBodyCheckingHttpInputMessage 中;
    3. 获取配置好的转换器,其中必须要有 json 转换器,否则将报错;
    4. 调用配置json转换器,进行格式验证;
    5. 进行消息转换,转换逻辑与spring就没什么关系了;(如果有兴趣查看json的转换逻辑可以参考fastjson源码)

  这里一个关键的步骤是,配置一个json转换器! 这里给出一个最简单的配置方式:

  1. <mvc:annotation-driven>
  2. <mvc:message-converters register-defaults="true">
  3. <bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
  4. <property name="supportedMediaTypes">
  5. <list>
  6. <value>application/json;charset=UTF-8</value>
  7. </list>
  8. </property>
  9. </bean>
  10. </mvc:message-converters>
  11. </mvc:annotation-driven>

  仅此足够!

springmvc 请求参数解析细节的更多相关文章

  1. SpringMVC请求参数解析

    请求参数解析 客户端请求在handlerMapping中找到对应handler后,将会继续执行DispatchServlet的doPatch()方法. 首先是找到handler对应的适配器. Hand ...

  2. SpringMVC请求参数接收总结

    前提 在日常使用SpringMVC进行开发的时候,有可能遇到前端各种类型的请求参数,这里做一次相对全面的总结.SpringMVC中处理控制器参数的接口是HandlerMethodArgumentRes ...

  3. SpringMVC请求参数接收总结(一)

    前提 在日常使用SpringMVC进行开发的时候,有可能遇到前端各种类型的请求参数,这里做一次相对全面的总结.SpringMVC中处理控制器参数的接口是HandlerMethodArgumentRes ...

  4. SpringMVC请求参数总结

    前提 在日常使用SpringMVC进行开发的时候,有可能遇到前端各种类型的请求参数,这里做一次相对全面的总结.SpringMVC中处理控制器参数的接口是HandlerMethodArgumentRes ...

  5. 2.5万字长文简单总结SpringMVC请求参数接收

    这是公众号<Throwable文摘>发布的第22篇原创文章,暂时收录于专辑<架构与实战>.暂定下一篇发布的长文是<图文分析JUC同步器框架>,下一篇发布的短文是&l ...

  6. springmvc请求参数异常统一处理

    1.ExceptionHandlerController package com.oy.controller; import java.text.MessageFormat; import org.s ...

  7. SpringBoot系列教程web篇之Post请求参数解析姿势汇总

    作为一个常年提供各种Http接口的后端而言,如何获取请求参数可以说是一项基本技能了,本篇为<190824-SpringBoot系列教程web篇之Get请求参数解析姿势汇总>之后的第二篇,对 ...

  8. SpringBoot系列教程web篇之Get请求参数解析姿势汇总

    一般在开发web应用的时候,如果提供http接口,最常见的http请求方式为GET/POST,我们知道这两种请求方式的一个显著区别是GET请求的参数在url中,而post请求可以不在url中:那么一个 ...

  9. springmvc请求参数异常统一处理,结合钉钉报告信息定位bug位置

    参考之前一篇博客:springmvc请求参数异常统一处理 1.ExceptionHandlerController package com.oy.controller; import java.tex ...

随机推荐

  1. window 服务器 安装 sql server 2008 r2 express 并启用远程访问

    目前市面上的数据库服务器虽然好,但是并不便宜,一个月数千RMB, 我们可以通过在已有的数据库上自建数据库来解决 目前已知的SQL Server 2008 R2的版本有: 企业版.标准版.工作组版.We ...

  2. JS-两个空数组为什么不相等?

    var a = [], b = []; console.log(a==b); 控制台的打印结果是什么?答案是:false. 接下来看解析: 原始值的比较是值的比较: 它们的值相等时它们就相等(==) ...

  3. SQL数据库的操作,表的操作

    数据库定义语言(DDL):用于对数据库及数据库中的各种对象进行创建,删除,修改等操作 (1)create:用于创建数据库或数据库对象 (2)alter:用于对数据库或数据库对象进行修改 (3)drop ...

  4. BandwagonHost

    https://kiwivm.64clouds.com/main-exec.php?mode=extras_shadowsocks https://kiwivm.64clouds.com/main-e ...

  5. leetcode刷题七<整数反转>

    给出一个 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示例 : 输入: 输出: 示例 : 输入: - 输出: - 示例 : 输入: 输出: 假设我们的环境只能存储得下 32 位的有符号整 ...

  6. 怎样在ASP.NET(C#) 使用Json序列化反序列化问题?

    using System; using System.Collections.Generic; using System.Web; using System.Web.Script.Serializat ...

  7. 用python做一个搜索引擎(Pylucene)

    什么是搜索引擎? 搜索引擎是“对网络信息资源进行搜集整理并提供信息查询服务的系统,包括信息搜集.信息整理和用户查询三部分”.如图1是搜索引擎的一般结构,信息搜集模块从网络采集信息到网络信息库之中(一般 ...

  8. Jmeter中实现base64加密

    Jmeter已不再提供内置base64加密函数,遇到base64加密需求,需要通过beanshell实现 直接上beanshell代码: import org.apache.commons.net.u ...

  9. Python-list,字典,Tuple

    list:用[]包围,逗号隔开如:l = [1,2,3] 其他用法:li = ["a" ,"b" , "c", "d"] ...

  10. 如何使用 tf object detection

    # 如何使用 tf object detection https://juejin.i m/entry/5a7976166fb9a06335319080 https://towardsdatascie ...