Spring MVC处理请求源码分析

《Spring Boot源码博客》

Spring MVC处理一个REST请求的过程如下

Spring MVC核心流程如下:

一、系统启动阶段初始化请求与Controller方法的映射集合。

二、根据请求查找对应的Controller方法。

三、解析请求参数,通过反射执行Controller方法。

四、处理返回结果。

一、初始化阶段

在初始化阶段需要了解几个Spring MVC组件

1、DispatcherServlet:为客户端的请求提供通用的处理方法,请求分发给各个处理器(Controller方法属于处理器)

2、HandlerMapping:是一个接口,定义请求和处理程序对象之间的映射关系。

2.1、 RequestMappingHandlerMapping:HandlerMapping的实现类,存储请求和Controller方法的映射关系。

2.1.1、RequestMappingInfo 封装了请求信息,HandlerMethod封装了Controller方法。例如:

@RequestMapping("/test2")
public class Test2Controller {
	@GetMapping("/path/{userId}")
	public Result path(@PathVariable("userId") long userId) {
	}
}

对于上面的代码。RequestMappingInfo保存请求类型GET、请求路径/test2/path/{userId}等信息。HandlerMethod保存着Test2Controller#path(Long)方法。

下图是Spring MVC处理请求的部分流程,DispatcherServlet接收请求,并使用RequestMappingHandlerMapping找出请求对应的Contoller方法:

上图左边的大橙色框,this.handlerMappings中的this是指DispatcherServlet,即handlerMappings是DispatcherServlet的属性。this.handlerMappings橙色框里面有个小的橙色框,表示RequestMappingHandlerMapping是this.handlerMappings列表的一个元素。

新建一个Controller,启动Spring MVC项目,探究Spring MVC初始化阶段做了什么。

1、在AbstractHandlerMethodMapping#processCandidateBean()打断点,启动Spring MVC。

/**
 * 创建bean时,会调用本方法。
 * 若类上有@Controller或者@RequestMapping。执行detectHandlerMethods(beanName)
 */
protected void processCandidateBean(String beanName) {
    ...仅展示主要代码

	// beanType是类Class
	// isHandler(beanType),Class上有@Controller或者@RequestMapping返回true
	if (beanType != null && isHandler(beanType)) {
		// 提取Controller类的url与方法的关系
		detectHandlerMethods(beanName);
	}
}

1.1、detectHandlerMethods(beanName)解析

/**
 * 源码位置 AbstractHandlerMethodMapping#detectHandlerMethods(java.lang.Object)
 * 在bean中查找处理程序方法
 */
protected void detectHandlerMethods(Object handler) {
     ...仅展示主要代码

	//寻找方法上有@RequestMapping注解的Method实例。
	Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> {
		// 返回RequestMappingInfo对象
		return getMappingForMethod(method, userType);
	});

	//将获取到的Method对象注册到HandlerMapping中去
	methods.forEach((method, mapping) -> {
		registerHandlerMethod(handler, invocableMethod, mapping);
	});
}

1.1.1、getMappingForMethod(method, userType)解析

/**
 * 使用方法和类上的@RequestMapping创建RequestMappingInfo
 */
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
	// 先使用方法上的@RequestMapping信息创建RequestMappingInfo
	RequestMappingInfo info = createRequestMappingInfo(method);
	if (info != null) {
		// 再使用类上的@RequestMapping信息创建RequestMappingInfo
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		if (typeInfo != null) {
			//将两个信息合并
			info = typeInfo.combine(info);
		}
		String prefix = getPathPrefix(handlerType);
		if (prefix != null) {
			info = RequestMappingInfo.paths(prefix).build().combine(info);
		}
	}
	return info;
}

1.1.2、registerHandlerMethod(handler, invocableMethod, mapping) 解析。在AbstractHandlerMethodMapping.MappingRegistry#register(java.lang.Object, java.lang.Object, java.lang.reflect.Method)方法中打断点

/**
 * 源码地址:AbstractHandlerMethodMapping.MappingRegistry#register(java.lang.Object, java.lang.Object, java.lang.reflect.Method)
 * RequestMappingHandlerMapping继承了AbstractHandlerMethodMapping
 * register(T mapping, Object handler, Method method)在RequestMappingHandlerMapping创建时执行
 */
public void register(T mapping, Object handler, Method method) {
    
    ...仅展示主要代码

	/**
	 * handler是Controller类在IOC中的名字,例如test1Controller
	 * method是Controller有@RequestMapping标注的方法
	 * 通过两者封装创建出HandlerMethod对象
	 */
	HandlerMethod handlerMethod = createHandlerMethod(handler, method);

	/**
	 * 创建及注册 MappingRegistration 信息
	 * this.registry是RequestMappingHandlerMapping.MappingRegistry#registry,是Map<T, MappingRegistration<T>>类型
	 *
	 * mapping是RequestMappingInfo类型,存储请求信息
	 * MappingRegistration实例包含了Controller方法
	 * 至此,this.registry便是存储了请求信息和Controller方法的映射关系
	 */
	this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}

通过上面的的步骤,RequestMappingHandlerMapping实例便初始化好了。

接下来便是将RequestMappingHandlerMapping实例添加到DispatcherServlet的handlerMappings属性中,在DispatcherServlet#initStrategies()打断点调试。

1、DispatcherServlet#initStrategies()解析

/**
 * 源码位置:DispatcherServlet#initStrategies(org.springframework.context.ApplicationContext)
 * 初始化DispatcherServlet使用的策略对象
 */
protected void initStrategies(ApplicationContext context) {
	//初始化文件上传处理器
	initMultipartResolver(context);
	//初始化国际化配置
	initLocaleResolver(context);
	//初始化主题处理器
	initThemeResolver(context);
	//初始化HandlerMapping
	initHandlerMappings(context);
	//初始化HandlerAdapter
	//HandlerAdapter用来调用具体的方法对用户发来的请求来进行处理
	initHandlerAdapters(context);
	//初始化异常处理器,
	// HandlerExceptionResolver是用来对请求处理过程中产生的异常进行处理
	initHandlerExceptionResolvers(context);
	//RequestToViewNameTranslator用于在视图路径为空的时候,自动解析请求
	//去获取ViewName
	initRequestToViewNameTranslator(context);
	//初始化视图处理器
	//ViewResolvers将逻辑视图转成view对象
	initViewResolvers(context);
}

2、主要研究initHandlerMappings(context);方法

/**
 * 源码位置:DispatcherServlet#initHandlerMappings(org.springframework.context.ApplicationContext)
 * 初始化DispatcherServlet使用的HandlerMappings
 */
private void initHandlerMappings(ApplicationContext context) {
	...仅展示主要代码

	/**
	 * 寻找IOC容器中为HandlerMapping类型的Bean
	 * RequestMappingHandlerMapping便是HandlerMapping的实现类之一
	 */
	Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
	if (!matchingBeans.isEmpty()) {
		/**
		 * 将HandlerMapping实现类集合赋值给DispatcherServlet的HandlerMappings属性
		 */
		this.handlerMappings = new ArrayList<>(matchingBeans.values());
		/**
		 * 对找到的HandlerMapping类型的Bean列表进行排序
		 * 排序后,RequestMappingHandlerMapping排在第一位,首选它
		 */
		AnnotationAwareOrderComparator.sort(this.handlerMappings);
	}
}

二、根据请求查找对应的Controller方法

本小节需要了解以下组件:

HandlerExecutionChain:处理程序执行链,由处理程序对象和任何处理程序拦截器组成。

HandlerMethod:封装由Controller类、Controller类方法组成的信息,提供对方法参数、返回值、注解的便捷访问。

编写一个controller方法,接收 GET localhost:8080/test1/query?pageNum=1&pageSize=2 请求。

1、请求都会经DispatcherServlet#doService()处理。

1.1、在DispatcherServlet#doDispatch()方法中打断点。

/**
 * 源码位置: DispatcherServlet#doDispatch(HttpServletRequest, HttpServletResponse)
 * 向处理程序分派请求,所有的HTTP方法都由该方法处理。
 * 通过HandlerMappings可以获取处理程序。
 * 通过HandlerAdapters查询出能解析请求参数的HandlerAdapter
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	...仅展示主要代码
	/**
	 * HandlerExecutionChain
	 * 处理程序执行链,由处理程序对象和任何处理程序拦截器组成。在本次请求中,由Controller方法和拦截器组成
	 */
	HandlerExecutionChain mappedHandler = null;
	/**
	 * 根据当前request获取handler,handler中包含了请求url,以及对应的controller、controller方法
	 */
	mappedHandler = getHandler(processedRequest);

	// 通过handler获取对应的适配器,adapter负责完成参数解析
	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

	/**
	 * 遍历所有定义的 interceptor,执行 preHandle 方法
	 */
	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;
	}

	// 调用目标Controller方法
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

	/**
	 * 拦截器postHandle方法进行处理
	 */
	mappedHandler.applyPostHandle(processedRequest, response, mv);

	// 处理最后的结果,渲染之类的逻辑都在这里
	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

}

先关注获取HandlerExecutionChain的逻辑

    HandlerExecutionChain mappedHandler = null;
    mappedHandler = getHandler(processedRequest);

1.1.1、DispatcherServlet#getHandler(HttpServletRequest request)方法解析。

/**
 * 源码位置: DispatcherServlet#getHandler(HttpServletRequest request)
 */
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
		/**
		 * 循环this.handlerMappings,遍历得到的第一个mapping是RequestMappingHandlerMapping
		 * 若HandlerMapping实现类的getHandler()的返回结果不为null,返回此handler
		 */
		for (HandlerMapping mapping : this.handlerMappings) {
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}

1.1.1.1、RequestMappingHandlerMapping.getHandler(HttpServletRequest request)解析

/**
 * 源码位置: AbstractHandlerMapping#getHandler(HttpServletRequest request)
 * 获取处理器链
 */
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	...仅展示主要代码

	//获取handler的具体逻辑由子类实现
	Object handler = getHandlerInternal(request);

	//根据handler和request构建处理器链HandlerExecutionChain
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

	return executionChain;
}

1.1.1.1.1、Object handler = getHandlerInternal(request); debug这个方法,最终来到下面的代码中。

/**
 * 源码位置: AbstractHandlerMethodMapping#getHandlerInternal(HttpServletRequest request)
 */
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	// 获取 request 中的 url,用来匹配 handler
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);

	// 根据路径寻找 Handler,并封装成 HandlerMethod
	HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);

	// 根据 handlerMethod 中的 bean 来实例化 Handler,并添加进 HandlerMethod
	return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}

handlerMethod如下图所示:

1.1.1.1.2、HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);解析。

/**
 * 源码位置: AbstractHandlerMapping.getHandlerExecutionChain(Object handler, HttpServletRequest request)
 * 构建处理器链HandlerExecutionChain
 */
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	//判断handler是不是执行器链,如果不是则创建一个执行器链
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain 
			? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

	// 添加拦截器
	String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
				chain.addInterceptor(mappedInterceptor.getInterceptor());
			}
		}
		else {
			chain.addInterceptor(interceptor);
		}
	}
	return chain;
}

回到1.1步骤的代码中:

    // 创建处理程序执行链
    HandlerExecutionChain mappedHandler = null;
    mappedHandler = getHandler(processedRequest);

    // 执行拦截器 preHandle 方法
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }

    // 执行拦截器postHandle方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);

这几行代码相信大家已经看懂了。

三、解析请求参数,反射执行Controller方法

解析请求参数

上面分析了通过请求找到对应的Controller方法,接下来分析如何解析请求参数。先了解几个组件:

HandlerAdapter:适配器,包含参数解析、返回值处理等功能。

HandlerMethodArgumentResolver:从request中解析出方法参数。

解析参数的类图如下:

1、在 DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response)处打断点。

/**
 * 源码位置: DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response)
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	...仅展示主要代码

	/**
	 * 前面已经分析过获取mappedHandler的过程了
	 */

	/**
	 * 获取HandlerAdapter
	 * 最终返回RequestMappingHandlerAdapter
	 */
	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

	// controller方法执行,并返回ModelAndView
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}

1.1、HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 解析

/**
 * 源码位置: DispatcherServlet.getHandlerAdapter(Object handler)
 * 返回支持当前handler的HandlerAdapter
 */
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
	if (this.handlerAdapters != null) {
		/**
		 * 遍历所有的 HandlerAdapter,找到和当前 Handler 匹配的HandlerAdapter实例
		 * 遍历过程中,第一个HandlerAdapter是RequestMappingHandlerAdapter
		 *
		 * RequestMappingHandlerAdapter的supports方法代码如下:
		 * 	   public final boolean supports(Object handler) {
		 * 	   	   return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
		 *     }
		 *
		 * 通过之前的代码分析得知,请求[ GET localhost:8080/test1/query?pageNum=1&pageSize=2 ]返回的handler是HandlerMethod类型,
		 * 所以会返回RequestMappingHandlerAdapter
		 */
		for (HandlerAdapter adapter : this.handlerAdapters) {
			if (adapter.supports(handler)) {
				return adapter;
			}
		}
	}
	throw new ServletException("No adapter for handler [" + handler +
			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

1.2、mv = ha.handle(processedRequest, response, mappedHandler.getHandler());解析

/**
 * 源码位置: RequestMappingHandlerAdapter.invokeHandlerMethod(HttpServletRequest request,
 * 			HttpServletResponse response, HandlerMethod handlerMethod)
 */
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
										   HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

	//把handlerMethod封装成ServletInvocableHandlerMethod
	ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);

	if (this.argumentResolvers != null) {
		//设置参数解析器
		invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
	}
	if (this.returnValueHandlers != null) {
		//设置返回值处理器
		invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
	}

	//设置参数名称发现器
	invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

	//核心逻辑,最终去执行Handler方法处理请求
	invocableMethod.invokeAndHandle(webRequest, mavContainer);

	return getModelAndView(mavContainer, modelFactory, webRequest);
}

1.2.1、invocableMethod.invokeAndHandle(webRequest, mavContainer);解析

	/**
	 * 源码位置: ServletInvocableHandlerMethod.invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs)
	 * 调用Controller方法并通过HandlerMethodReturnValueHandler处理返回值
	 */
	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
		...仅展示主要代码
				
		// 调用controller中的method方法
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

		// 处理返回值
		this.returnValueHandlers.handleReturnValue(
				returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		
	}

1.2.1.1、Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);解析

	/**
	 * 源码位置: InvocableHandlerMethod.invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs)
	 */
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
		// 获取controller方法实参
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		// 调用controller方法
		return doInvoke(args);
	}

1.2.1.1.1、Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);解析

/**
 * 源码位置: InvocableHandlerMethod.getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs)
 */
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	...仅展示主要代码

	//获取Controller方法参数列表
	MethodParameter[] parameters = getMethodParameters();
	//创建一个参数数组,保存从request解析出的方法参数
	Object[] args = new Object[parameters.length];
	for (int i = 0; i < parameters.length; i++) {
		MethodParameter parameter = parameters[i];
		//给每一个Controller方法实例参数初始化一个参数名称发现器
		parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
		args[i] = findProvidedArgument(parameter, providedArgs);

		//解析并绑定参数的核心逻辑
		args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
	}
	return args;
}

args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); 这句代码是获取参数值的关键。this.resolvers默认有26个参数解析器。

RequestParamMethodArgumentResolver支持@RequestParam解析,使用request.getParameterValues(name)获取值。

RequestResponseBodyMethodProcessor支持@RequestBody解析,使用MappingJackson2HttpMessageConverter将RequestBody反序列化为javaBean。

1.2.1.2、doInvoke(args);解析

/**
 * 源码位置: InvocableHandlerMethod.doInvoke(Object... args)
 */
protected Object doInvoke(Object... args) throws Exception {
	...仅展示主要代码
	// 方法设置accessible		
	ReflectionUtils.makeAccessible(getBridgedMethod());
	// 调动方法
	return getBridgedMethod().invoke(getBean(), args);
}

四、处理返回结果

HandlerMethodReturnValueHandler:处理返回值。

/**
 * 源码位置: ServletInvocableHandlerMethod.invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs)
 * 调用Controller方法并通过  HandlerMethodReturnValueHandler处理返回值
 */
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	...仅展示主要代码
	/**
	 * 前面已经分析了调用controller方法的代码
	 */
	 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

	/**
	 * 现在分析处理返回值的代码
	 */
	 this.returnValueHandlers.handleReturnValue(
	 returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}

1、this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);解析

/**
 * 源码位置: HandlerMethodReturnValueHandlerComposite.handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
 * 返回值处理
 */
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
							  ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	/**
	 * 选择能处理返回值的HandlerMethodReturnValueHandler,通过HandlerMethodReturnValueHandler实现类的supportsReturnType(MethodParameter returnType)判断能否处理返回值
	 * 例如:RequestResponseBodyMethodProcessor的supportsReturnType(MethodParameter returnType)是判断类或方法有@ResponseBody则返回true
	 */
	HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);

	/**
	 * 处理返回值
	 * RequestResponseBodyMethodProcessor会将Controller方法返回结果写入responseBody。
	 * 如果Controller方法返回ModelAndView,处理返回值的类是ModelAndViewMethodReturnValueHandler,此类将ModelAndView的属性设置给mavContainer
	 */
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

2、回到RequestMappingHandlerAdapter.invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) 方法处。

/**
 * 源码位置: RequestMappingHandlerAdapter.invokeHandlerMethod(HttpServletRequest request,
 * 			HttpServletResponse response, HandlerMethod handlerMethod)
 */
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
										   HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
	...仅展示部分代码

	/**
	 * 生成ModelAndView
	 */
	return getModelAndView(mavContainer, modelFactory, webRequest);
}

3、最后对response的处理。

/**
 * 源码位置: DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response)
 * 处理最后的结果
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	...仅展示部分代码
			
	// 处理最后的结果,渲染之类的逻辑都在这里
	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值