当前位置:   article > 正文

Spring源码学习30_spring 源码 30案例

spring 源码 30案例

本文对invokeHandlerMethod方法进行分析

  1. /**
  2. *
  3. * 如果视图解析是必要的调用 {@link RequestMapping}注解的处理方法准备一个{@link ModelAndView}
  4. *
  5. * @since 4.2
  6. * @see #createInvocableHandlerMethod(HandlerMethod)
  7. */
  8. protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
  9. HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
  10. //创建一个ServletWebRequest对象
  11. ServletWebRequest webRequest = new ServletWebRequest(request, response);
  12. try {
  13. // 获取容器中全局配置的InitBinder和当前HandlerMethod所对应的Controller中
  14. // 配置的InitBinder,用于进行参数的绑定
  15. // 并创建一个WebDataBinderFactory对象
  16. WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
  17. // 获取容器中全局配置的ModelAttribute和当前当前HandlerMethod所对应的Controller
  18. // 中配置的ModelAttribute,这些配置的方法将会在目标方法调用之前进行调用
  19. // 他要用binderFactory是因为他需要处理 对应处理方法的model属性
  20. ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
  21. // 将handlerMethod封装为一个ServletInvocableHandlerMethod对象,
  22. // 该对象用于对当前request的整体调用流程进行了封装
  23. // 比如使用WebDataBinder格式化参数 使用argumentresolvers解析参数 处理返回数据
  24. ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
  25. //设置参数解析器 解析的是handlerMethod的方法参数
  26. invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
  27. //设置返回值处理器
  28. invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
  29. //设置用于参数绑定的factory
  30. invocableMethod.setDataBinderFactory(binderFactory);
  31. //设置ParameterNameDiscoverer该对象按照一定规则获取当前参数的名称
  32. invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
  33. //创建一个ModelAndViewContainer对象
  34. //我们要将处理方法的model 和视图封装起来需要用到该类
  35. ModelAndViewContainer mavContainer = new ModelAndViewContainer();
  36. //添加所有闪存属性.
  37. mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
  38. modelFactory.initModel(webRequest, mavContainer, invocableMethod);
  39. mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
  40. // 获取当前的AsyncWebRequest,这里AsyncWebRequest的主要作用是用于判断目标
  41. // handler的返回值是否为WebAsyncTask或DefferredResult,如果是这两种中的一种,
  42. // 则说明当前请求的处理应该是异步的。所谓的异步,指的是当前请求会将Controller中
  43. // 封装的业务逻辑放到一个线程池中进行调用,待该调用有返回结果之后再返回到response中。
  44. // 这种处理的优点在于用于请求分发的线程能够解放出来,从而处理更多的请求,只有待目标任务
  45. // 完成之后才会回来将该异步任务的结果返回。
  46. AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
  47. asyncWebRequest.setTimeout(this.asyncRequestTimeout);
  48. //将异步任务的线程池,request和interceptors封装到WebAsyncManager中
  49. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  50. asyncManager.setTaskExecutor(this.taskExecutor);
  51. asyncManager.setAsyncWebRequest(asyncWebRequest);
  52. asyncManager.registerCallableInterceptors(this.callableInterceptors);
  53. asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
  54. // 用于判断当前请求是否有异步任务结果的,如果存在,则对异步任务结果进行封装
  55. if (asyncManager.hasConcurrentResult()) {
  56. Object result = asyncManager.getConcurrentResult();
  57. mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
  58. asyncManager.clearConcurrentResult();
  59. if (logger.isDebugEnabled()) {
  60. logger.debug("Found concurrent result value [" + result + "]");
  61. }
  62. invocableMethod = invocableMethod.wrapConcurrentResult(result);
  63. }
  64. // 对请求参数进行处理,调用目标HandlerMethod,并且将返回值封装为一个ModelAndView对象
  65. invocableMethod.invokeAndHandle(webRequest, mavContainer);
  66. if (asyncManager.isConcurrentHandlingStarted()) {
  67. return null;
  68. }
  69. // 对封装的ModelAndView进行处理,主要是判断当前请求是否进行了重定向,如果进行了重定向,
  70. // 还会判断是否需要将FlashAttributes封装到新的请求中
  71. return getModelAndView(mavContainer, modelFactory, webRequest);
  72. }
  73. finally {
  74. // 调用request destruction callbacks和对SessionAttributes进行处理
  75. webRequest.requestCompleted();
  76. }
  77. }

该方法主要做了四件事件,获取当前容器中使用@InitBinder注解注册的属性转换器;获取使用@ModelAttribute但没有@RequestMapping的方法,这些方法会在调用handlerMethod之前调用;请求的异步执行处理;处理请求参数调用目标方法处理返回值谢谢。

a.getDataBinderFactory(handlerMethod),使用该方法为每个handlerMethod方法创建一个WebDataBinderFactory,WebDataBinderFactory

在@ModelAttribute方法和handlerMethod方法的参数绑定执行

  1. private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
  2. //HandlerMethod对象的处理类类型
  3. Class<?> handlerType = handlerMethod.getBeanType();
  4. //handlerMethod所对应的控制器中的所有@InitBinder方法注解的方法缓存
  5. Set<Method> methods = this.initBinderCache.get(handlerType);
  6. if (methods == null) {
  7. // 在当前bean中查找所有标注了@InitBinder注解的方法,这里INIT_BINDER_METHODS就是一个
  8. // 选择器,表示只获取使用@InitBinder标注的方法
  9. methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
  10. //放入缓存里
  11. this.initBinderCache.put(handlerType, methods);
  12. }
  13. List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
  14. 这里initBinderAdviceCache是在RequestMappingHandlerAdapter初始化时同步初始化的,
  15. // 其内包含的方法有如下两个特点:①当前方法所在类使用@ControllerAdvice进行标注了;
  16. // ②当前方法使用@InitBinder进行了标注。也就是说其内保存的方法可以理解为是全局类型
  17. // 的参数绑定方法,如下这种
  18. // @ControllerAdvice
  19. // public class ControllerInitBinderExample{
  20. // @InitBinder
  21. // }
  22. // 获取使用@ControllerAdvice修饰的bean中的@InitBinder方法缓存;在afterPropertiesSet()方法中找到的
  23. for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
  24. //检查是否应该应用于给定的bean类型{@code @ControllerAdvice}实例。
  25. //比如配置了@ControllerAdvice的扫描包路径决定可否应用
  26. if (entry.getKey().isApplicableToBeanType(handlerType)) {
  27. Object bean = entry.getKey().resolveBean();
  28. for (Method method : entry.getValue()) {
  29. initBinderMethods.add(createInitBinderMethod(bean, method));
  30. }
  31. }
  32. }
  33. // 这里是将当前HandlerMethod所在bean中的InitBinder添加到需要执行的initBinderMethods中。
  34. // 这里从添加的顺序可以看出,全局类型的InitBinder会在当前bean中的InitBinder之前执行
  35. for (Method method : methods) {
  36. Object bean = handlerMethod.getBean();
  37. initBinderMethods.add(createInitBinderMethod(bean, method));
  38. }
  39. return createDataBinderFactory(initBinderMethods);
  40. }

b.getModelFactory(handlerMethod, binderFactory)方法;

  1. private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
  2. // 这里SessionAttributeHandler的作用是声明几个属性,使其能够在多个请求之间共享,
  3. // 并且其能够保证当前request返回的model中始终保有这些属性
  4. SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
  5. Class<?> handlerType = handlerMethod.getBeanType();
  6. //从缓存中找到该handlermethod对应的类加了@ModelAttribute的方法
  7. Set<Method> methods = this.modelAttributeCache.get(handlerType);
  8. if (methods == null) {
  9. //没有找到,搜索该bean对应的类中的所有@ModelAttribute方法
  10. //但是 没有使用@RequestMapping标注的方法,并将这些方法缓存起来
  11. methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
  12. this.modelAttributeCache.put(handlerType, methods);
  13. }
  14. List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>();
  15. // Global methods first
  16. // 获取那些加了@ControllerAdvice的bean的 所有加了@ModelAttribute方法
  17. for (Entry<ControllerAdviceBean, Set<Method>> entry : this.modelAttributeAdviceCache.entrySet()) {
  18. if (entry.getKey().isApplicableToBeanType(handlerType)) {
  19. Object bean = entry.getKey().resolveBean();
  20. for (Method method : entry.getValue()) {
  21. attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
  22. }
  23. }
  24. }
  25. // 将当前方法所属的类中使用@ModelAttribute标注的方法添加到需要执行的attrMethods中。从这里的添加顺序
  26. // 可以看出,全局类型的方法将会先于局部类型的方法执行
  27. for (Method method : methods) {
  28. Object bean = handlerMethod.getBean();
  29. attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
  30. }
  31. //将需要执行的方法等数据封装为ModelFactory对象
  32. return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
  33. }

方法和getDataBinderFactory很类似,我们需要知道的ModelFactory主要是管理Model属性的这些model属性可以来自@ModelAttribute方法的执行产生的以及@SessionAttribute注解传递session的。其具体的执行过程实际上是在后面的ModelFactory.initModel()方法中进行。

c.创建了ServletInvocableHandlerMethod对象;真正的handlerMethod的请求处理调用在该类中进行

d.创建ModelAndViewContainer对象;记录在控制器方法调用中的模型属性和视图。

e.modelFactory.initModel(webRequest, mavContainer, invocableMethod);

  1. * 按以下顺序填充模型:
  2. * <ol>
  3. * <li>检索列为{@code @SessionAttributes}的“已知”会话属性。
  4. * <li>Invoke {@code @ModelAttribute} methods
  5. * <li>Find {@code @ModelAttribute} method arguments also listed as
  6. * {@code @SessionAttributes} and ensure they're present in the model raising
  7. * an exception if necessary.
  8. * </ol>
  9. * @param request the current request
  10. * @param container a container with the model to be initialized
  11. * @param handlerMethod the method for which the model is initialized
  12. * @throws Exception may arise from {@code @ModelAttribute} methods
  13. */
  14. public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
  15. throws Exception {
  16. //获取request的sessionAttributesHandler
  17. Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
  18. // 将@SessionAttribute声明的参数封装到ModelAndViewContainer中
  19. container.mergeAttributes(sessionAttributes);
  20. //调用@ModelAttributeMethod方法谢谢
  21. invokeModelAttributeMethods(request, container);
  22. for (String name : findSessionAttributeArguments(handlerMethod)) {
  23. //当前handlerMethod的container的model属性不包含name,添加进去
  24. if (!container.containsAttribute(name)) {
  25. //获取到当前attributeName对应在session中的值
  26. Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
  27. if (value == null) {
  28. throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
  29. }
  30. //添加到模型的属性中
  31. container.addAttribute(name, value);
  32. }
  33. }
  34. }

这里initModel()方法主要做了两件事:①保证@SessionAttribute声明的参数的存在;②调用使用@ModelAttribute标注的方法。我们直接阅读invokeModelAttributeMethods()方法的源码

  1. /**
  2. * 调用模型属性方法以填充model。 仅当模型中尚未存在属性时才添加model。
  3. */
  4. private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)
  5. throws Exception {
  6. while (!this.modelMethods.isEmpty()) {
  7. InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
  8. // 获取当前方法中标注的ModelAttribute属性,然后判断当前request中是否有与该属性中name字段
  9. // 标注的值相同的属性,如果存在,并且当前ModelAttribute设置了不对该属性进行绑定,那么
  10. // 就直接略过当前方法的执行
  11. ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
  12. if (container.containsAttribute(ann.name())) {
  13. if (!ann.binding()) {
  14. container.setBindingDisabled(ann.name());
  15. }
  16. continue;
  17. }
  18. // 通过ArgumentResolver对方法参数进行处理,并且调用目标方法
  19. Object returnValue = modelMethod.invokeForRequest(request, container);
  20. // 如果当前方法的返回值不为空,则判断当前@ModelAttribute是否设置了需要绑定返回值,
  21. // 如果设置了,则将返回值绑定到请求中,后续handler可以直接使用该参数
  22. if (!modelMethod.isVoid()){
  23. String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
  24. if (!ann.binding()) {
  25. container.setBindingDisabled(returnValueName);
  26. }
  27. // 如果request中不包含该参数,则将该返回值添加到ModelAndViewContainer中,
  28. if (!container.containsAttribute(returnValueName)) {
  29. container.addAttribute(returnValueName, returnValue);
  30. }
  31. }
  32. }
  33. }

这里就是对@ModelAttribute方法的真正调用处。

f.请求异步处理支持,不看也罢

g.在进行所需要的前置方法和异步任务调用后,开始通过c创建的ServletInvocableHandlerMethod类的对象调执行真正的业务处理逻辑

  1. /**
  2. * 调用方法,并使用一个配置的{@link HandlerMethodReturnValueHandler}s处理返回值
  3. * @param webRequest the current request
  4. * @param mavContainer the ModelAndViewContainer for this request
  5. * @param providedArgs "given" arguments matched by type (not resolved)
  6. */
  7. public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
  8. Object... providedArgs) throws Exception {
  9. // 对目标handler的参数进行处理,并且调用目标handler
  10. Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
  11. // 设置相关的返回状态
  12. setResponseStatus(webRequest);
  13. // 如果请求处理完成,则设置requestHandled属性
  14. if (returnValue == null) {
  15. if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
  16. mavContainer.setRequestHandled(true);
  17. return;
  18. }
  19. }
  20. else if (StringUtils.hasText(getResponseStatusReason())) {
  21. // 如果请求失败,但是有错误原因,那么也会设置requestHandled属性
  22. mavContainer.setRequestHandled(true);
  23. return;
  24. }
  25. mavContainer.setRequestHandled(false);
  26. try {
  27. // 遍历当前容器中所有ReturnValueHandler,判断哪种handler支持当前返回值的处理,
  28. // 如果支持,则使用该handler处理该返回值
  29. this.returnValueHandlers.handleReturnValue(
  30. returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
  31. }
  32. catch (Exception ex) {
  33. if (logger.isTraceEnabled()) {
  34. logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
  35. }
  36. throw ex;
  37. }
  38. }

这里清晰的看到真正对请求的处理委托给了invokeForRequest(webRequest,mavContainer,providedArgs);该方法由父类InvocableHandlerMethod实现。ServletInvocableHandlerMethod主要是对返回值的处理。

h.invokeForRequest(webRequest, mavContainer, providedArgs);

  1. /**
  2. * 在给定请求的上下文中解析其参数值后调用该方法。
  3. * <p>参数值通常通过{@link HandlerMethodArgumentResolver}来解决。
  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. * @throws 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. // 将request中的参数转换为当前handler的参数形式
  18. Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
  19. if (logger.isTraceEnabled()) {
  20. logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
  21. "' with arguments " + Arrays.toString(args));
  22. }
  23. // 这里doInvoke()方法主要是结合处理后的参数,使用反射对目标方法进行调用
  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. }
  31. /**
  32. *获取当前请求的方法参数值。
  33. */
  34. private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
  35. Object... providedArgs) throws Exception {
  36. // 获取当前handler所声明的所有参数,主要包括参数名,参数类型,参数位置,所标注的注解等等属性
  37. MethodParameter[] parameters = getMethodParameters();
  38. Object[] args = new Object[parameters.length];
  39. for (int i = 0; i < parameters.length; i++) {
  40. MethodParameter parameter = parameters[i];
  41. parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
  42. //调用方提供了参数providedArgs;我们真正的请求处理是不会有providedArgs
  43. //所以肯定为null
  44. args[i] = resolveProvidedArgument(parameter, providedArgs);
  45. if (args[i] != null) {
  46. continue;
  47. }
  48. // 如果在调用方提供的参数中不能找到当前类型的参数值,则遍历Spring容器中所有的
  49. // ArgumentResolver,判断哪种类型的Resolver支持对当前参数的解析,这里的判断
  50. // 方式比较简单,比如RequestParamMethodArgumentResolver就是判断当前参数
  51. // 是否使用@RequestParam注解进行了标注
  52. if (this.argumentResolvers.supportsParameter(parameter)) {
  53. try {
  54. args[i] = this.argumentResolvers.resolveArgument(
  55. parameter, mavContainer, request, this.dataBinderFactory);
  56. continue;
  57. }
  58. catch (Exception ex) {
  59. if (logger.isDebugEnabled()) {
  60. logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
  61. }
  62. throw ex;
  63. }
  64. }
  65. //没有找到对参数的合适处理 抛出异常
  66. if (args[i] == null) {
  67. throw new IllegalStateException("Could not resolve method parameter at index " +
  68. parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
  69. ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
  70. }
  71. }
  72. return args;
  73. }
  74. /**
  75. * Invoke the handler method with the given argument values.
  76. */
  77. protected Object doInvoke(Object... args) throws Exception {
  78. ReflectionUtils.makeAccessible(getBridgedMethod());
  79. try {
  80. return getBridgedMethod().invoke(getBean(), args);
  81. }
  82. catch (IllegalArgumentException ex) {
  83. assertTargetBean(getBridgedMethod(), getBean(), args);
  84. String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
  85. throw new IllegalStateException(getInvocationErrorMessage(text, args), ex);
  86. }
  87. catch (InvocationTargetException ex) {
  88. // Unwrap for HandlerExceptionResolvers ...
  89. Throwable targetException = ex.getTargetException();
  90. if (targetException instanceof RuntimeException) {
  91. throw (RuntimeException) targetException;
  92. }
  93. else if (targetException instanceof Error) {
  94. throw (Error) targetException;
  95. }
  96. else if (targetException instanceof Exception) {
  97. throw (Exception) targetException;
  98. }
  99. else {
  100. String text = getInvocationErrorMessage("Failed to invoke handler method", args);
  101. throw new IllegalStateException(text, targetException);
  102. }
  103. }
  104. }

主要使用了argument-resolvers对方法参数的转换处理和反射调用处理器的方法处理请求。

i.使用returnValueHandlers处理返回值returnValueHandlers.handleReturnValue(       returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

  1. /**
  2. * 迭代已注册的{@link HandlerMethodReturnValueHandler}并调用支持它的那个。
  3. * @throws IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.
  4. */
  5. @Override
  6. public void handleReturnValue(Object returnValue, MethodParameter returnType,
  7. ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
  8. // 获取能够处理当前返回值的Handler,比如如果返回值是ModelAndView类型,那么这里的handler就是
  9. // ModelAndViewMethodReturnValueHandler
  10. HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
  11. if (handler == null) {
  12. throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
  13. }
  14. handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
  15. }

j.回到RequestMappingHandlerAdapter的invokeHandlerMethod(request,response,handlerMethod)的 getModelAndView(mavContainer, modelFactory, webRequest);方法

  1. private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
  2. ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
  3. modelFactory.updateModel(webRequest, mavContainer);
  4. if (mavContainer.isRequestHandled()) {
  5. return null;
  6. }
  7. ModelMap model = mavContainer.getModel();
  8. ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
  9. if (!mavContainer.isViewReference()) {
  10. mav.setView((View) mavContainer.getView());
  11. }
  12. if (model instanceof RedirectAttributes) {
  13. Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
  14. HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
  15. RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
  16. }
  17. return mav;
  18. }

通过ModelAndViewContainer包装ModelAndView对象并且支持对重定向的处理。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/66260
推荐阅读
相关标签
  

闽ICP备14008679号