springmvc源码解析之DispatcherServlet三

说在前面

本次主要介绍DispatcherServlet,关注”天河聊架构“更需精彩。

 

springmvc配置解析

上次介绍了执行handler的抽象默认实现。

简单实现,进入到这个方法org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter#handle

@Override
   public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {

//    请求处理-》
      ((HttpRequestHandler) handler).handleRequest(request, response);
      return null;
   }

进入到这个方法org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#handleRequest

@Override
   public void handleRequest(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {

//    获取请求转发对象
      RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
      if (rd == null) {
         throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
               this.defaultServletName + "'");
      }
//    请求转发
      rd.forward(request, response);
   }

就是直接request拿到请求转发器执行转发。

重点来了,基于@RequestMapping的业务handler执行,进入到这个方法org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#handle

@Override
   public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {

      Class<?> clazz = ClassUtils.getUserClass(handler);
//    是否绑定了session缓存
      Boolean annotatedWithSessionAttributes = this.sessionAnnotatedClassesCache.get(clazz);
      if (annotatedWithSessionAttributes == null) {
//       解析@SessionAttributes注解
         annotatedWithSessionAttributes = (AnnotationUtils.findAnnotation(clazz, SessionAttributes.class) != null);
         this.sessionAnnotatedClassesCache.put(clazz, annotatedWithSessionAttributes);
      }

      if (annotatedWithSessionAttributes) {
//       检查session和浏览器缓存配置 -》
         checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
      }
      else {
         checkAndPrepare(request, response, true);
      }

      // 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) {
//             执行业务方法-》
               return invokeHandlerMethod(request, response, handler);
            }
         }
      }

      return invokeHandlerMethod(request, response, handler);
   }

进入到这个方法org.springframework.web.servlet.support.WebContentGenerator#checkAndPrepare(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int, boolean)

@Deprecated
   protected final void checkAndPrepare(
         HttpServletRequest request, HttpServletResponse response, int cacheSeconds, boolean lastModified)
         throws ServletException {

      checkRequest(request);
//    缓存设置
      applyCacheSeconds(response, cacheSeconds);
   }

往上返回这个方法org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#invokeHandlerMethod

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {

//    根据handler找到业务方法解析器 -》
      ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);
//    根据请求找到执行的业务方法 -》
      Method handlerMethod = methodResolver.resolveHandlerMethod(request);
      ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);
      ServletWebRequest webRequest = new ServletWebRequest(request, response);
      ExtendedModelMap implicitModel = new BindingAwareModelMap();
//    执行业务方法返回结果 -》
      Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
//    获取modelAndView对象 -》
      ModelAndView mav =
            methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);
//    更新model
      methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
      return mav;
   }

进入这个方法org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#getMethodResolver

private ServletHandlerMethodResolver getMethodResolver(Object handler) {
      Class<?> handlerClass = ClassUtils.getUserClass(handler);
//    从缓存中获取servletHandler方法解析器
      ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
      if (resolver == null) {
         synchronized (this.methodResolverCache) {
            resolver = this.methodResolverCache.get(handlerClass);
            if (resolver == null) {
               resolver = new ServletHandlerMethodResolver(handlerClass);
               this.methodResolverCache.put(handlerClass, resolver);
            }
         }
      }
      return resolver;
   }

往上返回这个方法org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.ServletHandlerMethodResolver#resolveHandlerMethod

public Method resolveHandlerMethod(HttpServletRequest request) throws ServletException {
//       找到请求路径-》
         String lookupPath = urlPathHelper.getLookupPathForRequest(request);
         Comparator<String> pathComparator = pathMatcher.getPatternComparator(lookupPath);
         Map<RequestSpecificMappingInfo, Method> targetHandlerMethods = new LinkedHashMap<RequestSpecificMappingInfo, Method>();
         Set<String> allowedMethods = new LinkedHashSet<String>(7);
         String resolvedMethodName = null;
         for (Method handlerMethod : getHandlerMethods()) {
            RequestSpecificMappingInfo mappingInfo = new RequestSpecificMappingInfo(this.mappings.get(handlerMethod));
            boolean match = false;
            if (mappingInfo.hasPatterns()) {
               for (String pattern : mappingInfo.getPatterns()) {
                  if (!hasTypeLevelMapping() && !pattern.startsWith("/")) {
                     pattern = "/" + pattern;
                  }
//                解析组合路径-》
                  String combinedPattern = getCombinedPattern(pattern, lookupPath, request);
                  if (combinedPattern != null) {
                     if (mappingInfo.matches(request)) {
                        match = true;
                        mappingInfo.addMatchedPattern(combinedPattern);
                     }
                     else {
//                      请求方法不匹配-》
                        if (!mappingInfo.matchesRequestMethod(request)) {
                           allowedMethods.addAll(mappingInfo.methodNames());
                        }
                        break;
                     }
                  }
               }
               mappingInfo.sortMatchedPatterns(pathComparator);
            }
            else if (useTypeLevelMapping(request)) {
               String[] typeLevelPatterns = getTypeLevelMapping().value();
               for (String typeLevelPattern : typeLevelPatterns) {
                  if (!typeLevelPattern.startsWith("/")) {
                     typeLevelPattern = "/" + typeLevelPattern;
                  }
                  boolean useSuffixPattern = useSuffixPattern(request);
                  if (getMatchingPattern(typeLevelPattern, lookupPath, useSuffixPattern) != null) {
                     if (mappingInfo.matches(request)) {
                        match = true;
                        mappingInfo.addMatchedPattern(typeLevelPattern);
                     }
                     else {
                        if (!mappingInfo.matchesRequestMethod(request)) {
                           allowedMethods.addAll(mappingInfo.methodNames());
                        }
                        break;
                     }
                  }
               }
               mappingInfo.sortMatchedPatterns(pathComparator);
            }
            else {
               // No paths specified: parameter match sufficient. 请求方法、参数、header匹配
               match = mappingInfo.matches(request);
               if (match && mappingInfo.getMethodCount() == 0 && mappingInfo.getParamCount() == 0 &&
                     resolvedMethodName != null && !resolvedMethodName.equals(handlerMethod.getName())) {
                  match = false;
               }
               else {
//                请求方法不匹配
                  if (!mappingInfo.matchesRequestMethod(request)) {
                     allowedMethods.addAll(mappingInfo.methodNames());
                  }
               }
            }
            if (match) {
               Method oldMappedMethod = targetHandlerMethods.put(mappingInfo, handlerMethod);
               if (oldMappedMethod != null && oldMappedMethod != handlerMethod) {
                  if (methodNameResolver != null && !mappingInfo.hasPatterns()) {
                     if (!oldMappedMethod.getName().equals(handlerMethod.getName())) {
                        if (resolvedMethodName == null) {
//                         获取方法名-》
                           resolvedMethodName = methodNameResolver.getHandlerMethodName(request);
                        }
                        if (!resolvedMethodName.equals(oldMappedMethod.getName())) {
                           oldMappedMethod = null;
                        }
                        if (!resolvedMethodName.equals(handlerMethod.getName())) {
                           if (oldMappedMethod != null) {
                              targetHandlerMethods.put(mappingInfo, oldMappedMethod);
                              oldMappedMethod = null;
                           }
                           else {
                              targetHandlerMethods.remove(mappingInfo);
                           }
                        }
                     }
                  }
                  if (oldMappedMethod != null) {
                     throw new IllegalStateException(
                           "Ambiguous handler methods mapped for HTTP path '" + lookupPath + "': {" +
                           oldMappedMethod + ", " + handlerMethod +
                           "}. If you intend to handle the same path in multiple methods, then factor " +
                           "them out into a dedicated handler class with that path mapped at the type level!");
                  }
               }
            }
         }
         if (!targetHandlerMethods.isEmpty()) {
            List<RequestSpecificMappingInfo> matches = new ArrayList<RequestSpecificMappingInfo>(targetHandlerMethods.keySet());
            RequestSpecificMappingInfoComparator requestMappingInfoComparator =
                  new RequestSpecificMappingInfoComparator(pathComparator, request);
            Collections.sort(matches, requestMappingInfoComparator);
            RequestSpecificMappingInfo bestMappingMatch = matches.get(0);
            String bestMatchedPath = bestMappingMatch.bestMatchedPattern();
            if (bestMatchedPath != null) {
               extractHandlerMethodUriTemplates(bestMatchedPath, lookupPath, request);
            }
            return targetHandlerMethods.get(bestMappingMatch);
         }
         else {
            if (!allowedMethods.isEmpty()) {
               throw new HttpRequestMethodNotSupportedException(request.getMethod(), StringUtils.toStringArray(allowedMethods));
            }
            throw new org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException(
                  lookupPath, request.getMethod(), request.getParameterMap());
         }
      }

进入方法org.springframework.web.util.UrlPathHelper#getLookupPathForRequest

public String getLookupPathForRequest(HttpServletRequest request) {
   // Always use full path within current servlet context? 总是在当前servlet上下文中使用完整路径? -》
   if (this.alwaysUseFullPath) {
      return getPathWithinApplication(request);
   }
   // Else, use path within current servlet mapping if applicable 否则,在当前servlet映射中使用path(如果适用)
   String rest = getPathWithinServletMapping(request);
   if (!"".equals(rest)) {
      return rest;
   }
   else {
      return getPathWithinApplication(request);
   }
}

这里介绍过了。

返回方法org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.ServletHandlerMethodResolver#getCombinedPattern

private String getCombinedPattern(String methodLevelPattern, String lookupPath, HttpServletRequest request) {
//       是否使用前缀匹配
         boolean useSuffixPattern = useSuffixPattern(request);
//       controller上是否有映射 -》
         if (useTypeLevelMapping(request)) {
            String[] typeLevelPatterns = getTypeLevelMapping().value();
            for (String typeLevelPattern : typeLevelPatterns) {
//             如果controller上的映射前面没有/自动拼接
               if (!typeLevelPattern.startsWith("/")) {
                  typeLevelPattern = "/" + typeLevelPattern;
               }
//             解析controller上和方法上都有映射
               String combinedPattern = pathMatcher.combine(typeLevelPattern, methodLevelPattern);
//             获取映射 -》
               String matchingPattern = getMatchingPattern(combinedPattern, lookupPath, useSuffixPattern);
               if (matchingPattern != null) {
                  return matchingPattern;
               }
            }
            return null;
         }
         String bestMatchingPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
         if (StringUtils.hasText(bestMatchingPattern) && bestMatchingPattern.endsWith("*")) {
            String combinedPattern = pathMatcher.combine(bestMatchingPattern, methodLevelPattern);
            String matchingPattern = getMatchingPattern(combinedPattern, lookupPath, useSuffixPattern);
            if (matchingPattern != null && !matchingPattern.equals(bestMatchingPattern)) {
               return matchingPattern;
            }
         }
         return getMatchingPattern(methodLevelPattern, lookupPath, useSuffixPattern);
      }

进入方法org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.ServletHandlerMethodResolver#useTypeLevelMapping

private boolean useTypeLevelMapping(HttpServletRequest request) {
   if (!hasTypeLevelMapping() || ObjectUtils.isEmpty(getTypeLevelMapping().value())) {
      return false;
   }
   Object value = request.getAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING);
   return (value != null) ? (Boolean) value : Boolean.TRUE;
}

返回方法org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.RequestMappingInfo#matchesRequestMethod

public boolean matchesRequestMethod(HttpServletRequest request) {
   return ServletAnnotationMappingUtils.checkRequestMethod(this.methods, request);
}

返回方法org.springframework.web.servlet.mvc.multiaction.AbstractUrlMethodNameResolver#getHandlerMethodName

@Override
   public final String getHandlerMethodName(HttpServletRequest request)
         throws NoSuchRequestHandlingMethodException {

//    获取请求路径
      String urlPath = this.urlPathHelper.getLookupPathForRequest(request);
//    根据url路径获取请求handler的名称
      String name = getHandlerMethodNameForUrlPath(urlPath);
      if (name == null) {
         throw new NoSuchRequestHandlingMethodException(urlPath, request.getMethod(), request.getParameterMap());
      }
      if (logger.isDebugEnabled()) {
         logger.debug("Returning handler method name '" + name + "' for lookup path: " + urlPath);
      }
      return name;
   }

返回方法org.springframework.web.bind.annotation.support.HandlerMethodInvoker#invokeHandlerMethod

public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
         NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {

//    桥接模式
      Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
      try {
         boolean debug = logger.isDebugEnabled();
         for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
//          从session中获取参数值
            Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName);
            if (attrValue != null) {
               implicitModel.addAttribute(attrName, attrValue);
            }
         }
         for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) {
            Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod);
//          解析要执行的方法 -》
            Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
            if (debug) {
               logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
            }
            String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
            if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
               continue;
            }
            ReflectionUtils.makeAccessible(attributeMethodToInvoke);
//          执行handler
            Object attrValue = attributeMethodToInvoke.invoke(handler, args);
            if ("".equals(attrName)) {
//             解析返回类型
               Class<?> resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
//             根据参数值找到返回类型
               attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
            }
            if (!implicitModel.containsAttribute(attrName)) {
               implicitModel.addAttribute(attrName, attrValue);
            }
         }
//       解析参数-》
         Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel);
         if (debug) {
            logger.debug("Invoking request handler method: " + handlerMethodToInvoke);
         }
         ReflectionUtils.makeAccessible(handlerMethodToInvoke);
//       执行handler
         return handlerMethodToInvoke.invoke(handler, args);
      }
      catch (IllegalStateException ex) {
         // Internal assertion failed (e.g. invalid signature):
         // throw exception with full handler method context...
         throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex);
      }
      catch (InvocationTargetException ex) {
         // User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception...
         ReflectionUtils.rethrowException(ex.getTargetException());
         return null;
      }
   }

进入方法org.springframework.web.bind.annotation.support.HandlerMethodInvoker#resolveHandlerArguments

private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
         NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {

//    获取controller方法的参数类型
      Class<?>[] paramTypes = handlerMethod.getParameterTypes();
      Object[] args = new Object[paramTypes.length];
      for (int i = 0; i < args.length; i++) {
         MethodParameter methodParam = new SynthesizingMethodParameter(handlerMethod, i);
         methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer);
         GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
         String paramName = null;
         String headerName = null;
         boolean requestBodyFound = false;
         String cookieName = null;
         String pathVarName = null;
         String attrName = null;
         boolean required = false;
         String defaultValue = null;
         boolean validate = false;
         Object[] validationHints = null;
         int annotationsFound = 0;
         Annotation[] paramAnns = methodParam.getParameterAnnotations();
         for (Annotation paramAnn : paramAnns) {
//          解析@RequestParam注解
            if (RequestParam.class.isInstance(paramAnn)) {
               RequestParam requestParam = (RequestParam) paramAnn;
               paramName = requestParam.name();
               required = requestParam.required();
               defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());
               annotationsFound++;
            }
//          解析@RequestHeader注解
            else if (RequestHeader.class.isInstance(paramAnn)) {
               RequestHeader requestHeader = (RequestHeader) paramAnn;
               headerName = requestHeader.name();
               required = requestHeader.required();
               defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());
               annotationsFound++;
            }
//          解析@RequestBody注解
            else if (RequestBody.class.isInstance(paramAnn)) {
               requestBodyFound = true;
               annotationsFound++;
            }
//          解析@CookieValue注解
            else if (CookieValue.class.isInstance(paramAnn)) {
               CookieValue cookieValue = (CookieValue) paramAnn;
               cookieName = cookieValue.name();
               required = cookieValue.required();
               defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());
               annotationsFound++;
            }
//          解析@PathVariable注解
            else if (PathVariable.class.isInstance(paramAnn)) {
               PathVariable pathVar = (PathVariable) paramAnn;
               pathVarName = pathVar.value();
               annotationsFound++;
            }
//          解析@ModelAttribute注解
            else if (ModelAttribute.class.isInstance(paramAnn)) {
               ModelAttribute attr = (ModelAttribute) paramAnn;
               attrName = attr.value();
               annotationsFound++;
            }
//          解析@Value注解
            else if (Value.class.isInstance(paramAnn)) {
               defaultValue = ((Value) paramAnn).value();
            }
            else {
//             解析@Validated注解
               Validated validatedAnn = AnnotationUtils.getAnnotation(paramAnn, Validated.class);
               if (validatedAnn != null || paramAnn.annotationType().getSimpleName().startsWith("Valid")) {
                  validate = true;
                  Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(paramAnn));
                  validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
               }
            }
         }

         if (annotationsFound > 1) {
            throw new IllegalStateException("Handler parameter annotations are exclusive choices - " +
                  "do not specify more than one such annotation on the same parameter: " + handlerMethod);
         }

         if (annotationsFound == 0) {
//          解析一般参数值
            Object argValue = resolveCommonArgument(methodParam, webRequest);
            if (argValue != WebArgumentResolver.UNRESOLVED) {
               args[i] = argValue;
            }
            else if (defaultValue != null) {
               args[i] = resolveDefaultValue(defaultValue);
            }
            else {
               Class<?> paramType = methodParam.getParameterType();
               if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
                  if (!paramType.isAssignableFrom(implicitModel.getClass())) {
                     throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " +
                           "Model or Map but is not assignable from the actual model. You may need to switch " +
                           "newer MVC infrastructure classes to use this argument.");
                  }
                  args[i] = implicitModel;
               }
               else if (SessionStatus.class.isAssignableFrom(paramType)) {
                  args[i] = this.sessionStatus;
               }
               else if (HttpEntity.class.isAssignableFrom(paramType)) {
//                解析HttpEntity请求-》
                  args[i] = resolveHttpEntityRequest(methodParam, webRequest);
               }
               else if (Errors.class.isAssignableFrom(paramType)) {
                  throw new IllegalStateException("Errors/BindingResult argument declared " +
                        "without preceding model attribute. Check your handler method signature!");
               }
               else if (BeanUtils.isSimpleProperty(paramType)) {
                  paramName = "";
               }
               else {
                  attrName = "";
               }
            }
         }

         if (paramName != null) {
//          @RequestParam 解析 -》
            args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler);
         }
         else if (headerName != null) {
//          @RequestHandler解析-》
            args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
         }
         else if (requestBodyFound) {
//          @RequestBody解析 -》
            args[i] = resolveRequestBody(methodParam, webRequest, handler);
         }
         else if (cookieName != null) {
//          @CookieValue 解析 -》
            args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
         }
         else if (pathVarName != null) {
//          @PathVariable 解析 -》
            args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);
         }
         else if (attrName != null) {
//          @ModelAttribute 解析-》
            WebDataBinder binder =
                  resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
            boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
            if (binder.getTarget() != null) {
//             参数绑定-》
               doBind(binder, webRequest, validate, validationHints, !assignBindingResult);
            }
            args[i] = binder.getTarget();
            if (assignBindingResult) {
               args[i + 1] = binder.getBindingResult();
               i++;
            }
            implicitModel.putAll(binder.getBindingResult().getModel());
         }
      }

      return args;
   }

 

说到最后

本次源码解析仅代表个人观点,仅供参考。

转载于:https://my.oschina.net/u/3775437/blog/3028200

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值