SpringMVC的DispatcherServlet初始化过程

DispatcherServlet作为一个Servlet,需要根据Servlet规范使用Java配置或web.xml声明和映射。DispatcherServlet会根据Spring的配置来发现请求映射、视图解析、异常处理等所需的委托组件。

DispatcherServlet和ApplicationContext的关系

  • DispatcherServlet需要WebApplicationContext来配置,WebApplicationContext继承于ApplicationContext

  • WebApplicationContext有一个指向Servlet上下文(ServletContext)及其关联Servlet的链接。绑定ServletContext的目的就是为了应用程序可以在需要时使用RequestContextUtils的静态方法访问WebApplicationContext

  • 大多数应用程序一个WebApplicationContext已经足够,但是亚特可以有一个上下文层次结构:其中一个Root WebApplicationContext在多个DispatcherServlet/Servlet实例之间共享,每个实例都有自己的child WebApplicationContext

DispatcherServlet解析图(来源于:官网)
在这里插入图片描述

DispatcherServlet初始化

DispatcherServlet首先是Servlet,Servlet有自己的生命周期方法,想要了解DispatcherServlet的初始化源码,就需要看DispatcherServlet的类结构设计

在这里插入图片描述

从类结构设计图上来看,DispatcherContext继承/实现的两大核心的接口/类:ApplicationConetxtAwre和GenericServlet

而init()方法就是来自于GenericServlet,由HttpServletBean重写

  • init()

    init()方法如下:主要读取web.xml中servlet参数配置,并交给子类方法initServletBean()继续初始化

    HttpServletBean.init()

    /**
     * 将配置参数映射到该Servlet的Bean属性上,并调用子类初始化
     * 如果Bean属性无效或需要的属性确实或子类初始化失败    则抛出ServletExecption
     **/
    public final void init() throws ServletException {
      //读取web.xml中的servlet配置
      PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
      if (!pvs.isEmpty()) {
        try {
          //转换成BeanWrapper,为了方便使用Spring的属性注入功能
          BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
          //注入Resource类型,需要依赖于ResourceEditor解析,所以注册Resource类关联到ResourceEditor解析器
          ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
          bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
          //未实现功能,提供给子类去扩展更多的初始化
          this.initBeanWrapper(bw);
          //让Spring注入namespace、contextConfigLocation等属性
          bw.setPropertyValues(pvs, true);
        } catch (BeansException var4) {
          if (this.logger.isErrorEnabled()) {
            this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
          }
    
          throw var4;
        }
      }
      //让子类扩展的初始化Bean,HttpServletBean并未实现,而是FrameworkServlet
      this.initServletBean();
    }
    

    FrameworkServlet.initServletBean()

    protected final void initServletBean() throws ServletException {
      this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
      if (this.logger.isInfoEnabled()) {
        this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
      }
    
      long startTime = System.currentTimeMillis();
    
      try {
        //初始化WebApplicationConetxt
        this.webApplicationContext = this.initWebApplicationContext();
        //未实现,由子类进行扩展
        this.initFrameworkServlet();
      } catch (RuntimeException | ServletException var4) {
        this.logger.error("Context initialization failed", var4);
        throw var4;
      }
    
      if (this.logger.isDebugEnabled()) {
        String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data";
        this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
      }
    
      if (this.logger.isInfoEnabled()) {
        this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
      }
    
    }
    
  • initWebApplicationContext()

    initWebApplicationContext用于初始化和刷新WebApplicationContext

    FrameworkServlet.initWebApplicationContext()

    /**
     * 为这个Servlet初始化和发布WebApplicationContext,实际委托给createWebApplicationContext来创建的上下文,可以在子类中被重载
     **/
    protected WebApplicationContext initWebApplicationContext() {
      //获取WebApplicationContext实例
      WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
      WebApplicationContext wac = null;
      //如果在构造函数已经被初始化
      if (this.webApplicationContext != null) {
        //在创建时注入一个上下文实例
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
          ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
          //上下文没有被刷新
          if (!cwac.isActive()) {
            //上下文实例被注入时没有明确的父级
            if (cwac.getParent() == null) {
              //设置为:根应用程序上下文作为父级(rootContext可能为空)
              cwac.setParent(rootContext);
            }
            //设置并刷新WebApplicationContext
            this.configureAndRefreshWebApplicationContext(cwac);
          }
        }
      }
      //如果没有在构造函数中初始化,则通过contextAttribute初始化
      if (wac == null) {
        //寻找是否有一个已经在Servlet上下文中注册的实例,如果存在,则假定父级上线文已经被设置了,并且用户已经进行了各种初始化
        wac = this.findWebApplicationContext();
      }
      //如果还是没有,则重新创建一个本地的实例
      if (wac == null) {
        wac = this.createWebApplicationContext(rootContext);
      }
      //如果ConfigurableApplication不具备刷新功能,或者在构造时注入的上下文已经被刷新
      if (!this.refreshEventReceived) {
        //在这里进行手动的刷新
        synchronized(this.onRefreshMonitor) {
          this.onRefresh(wac);
        }
      }
    
      if (this.publishContext) {
        //将这个上下文作为一个servletContext的attribute发布
        String attrName = this.getServletContextAttributeName();
        this.getServletContext().setAttribute(attrName, wac);
      }
    
      return wac;
    }
    

    webApplicationContext只会初始化一次,依次尝试构造函数初始化,如果没有,则通过contextAttribute初始化,如果仍然没有,则创建新的

    webApplicationContext如何创建新的
    FrameworkServlet.createWebApplicationContext()

    /**
     * 为这个Servlet实例化WebApplicationContext,可以是一个默认的XmlWebApplicationContext,也可以是一个自定义的Context
     * 如果实现自定义的Context,应该要实现ConfigurableWebApplicationContext接口,可以在子类中重写方法
     * 还需要在创建的上下文中将这个servlet实例注册为应用监听器(用于触发onRefresh()方法的回调)
     * 并在返回上下文实例之前调用ConfigurableApplicationContext的refresh()方法
     **/
    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
      Class<?> contextClass = this.getContextClass();
      if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
      } else {
        //通过反射方式初始化
        ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        wac.setEnvironment(this.getEnvironment());
        wac.setParent(parent);
        //就是Demo案例中的springmvc.xml
        String configLocation = this.getContextConfigLocation();
        if (configLocation != null) {
          wac.setConfigLocation(configLocation);
        }
        //初始化Spring环境
        this.configureAndRefreshWebApplicationContext(wac);
        return wac;
      }
    }
    

    FrameworkServlet.configureAndRefreshWebApplicationContext()

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
      //设置context的ID标识
      if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        //如果contextId不为空,则作为ConfigurableWebApplicationContext的ID标识,否则使用默认id标识
        if (this.contextId != null) {
          wac.setId(this.contextId);
        } else {
          wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName());
        }
      }
      //设置servletContext、servletConfig、namespace、listener
      wac.setServletContext(this.getServletContext());
      wac.setServletConfig(this.getServletConfig());
      wac.setNamespace(this.getNamespace());
      wac.addApplicationListener(new SourceFilteringListener(wac, new FrameworkServlet.ContextRefreshListener()));
      //获取ConfigurableWebApplicationContext的环境参数实例
      ConfigurableEnvironment env = wac.getEnvironment();
      if (env instanceof ConfigurableWebEnvironment) {
        //获取ConfigurableWebApplicationContext的环境参数实例的initPropertySources方法在任何情况下都会在Context被刷新的时候调用
        //这里提前执行initPropertySources是为了确保servlet属性源已经加载完毕,可用于在refresh之前的任何后处理或初始化中使用
        ((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig());
      }
      //未实现,提供给子类扩展
      this.postProcessWebApplicationContext(wac);
      this.applyInitializers(wac);
      //Spring环境初始化完成,开始初始化DispatcherServlet处理流程中需要的组件
      wac.refresh();
    }
    
  • refresh()

    初始化WebApplicationContext完成后,就需要进行刷新,刷新过程中需要进行一系列的初始化和注册等操作

    ServletWebServerApplicationContext.refresh()

    public final void refresh() throws BeansException, IllegalStateException {
      try {
        //委托给父级进行刷新
        super.refresh();
      } catch (RuntimeException var3) {
        WebServer webServer = this.webServer;
        if (webServer != null) {
          webServer.stop();
        }
    
        throw var3;
      }
    }
    

    AbstractApplicationContext.refresh()

    public void refresh() throws BeansException, IllegalStateException {
      synchronized(this.startupShutdownMonitor) {
        //开始刷新
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
        //刷新前配置资源信息准备
        this.prepareRefresh();
        //刷新内部的bean工厂
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
        //刷新后的Bean工厂准备
        this.prepareBeanFactory(beanFactory);
    
        try {
          //后处理bean工厂
          this.postProcessBeanFactory(beanFactory);
          //开始后处理
          StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
          //设置bean的工厂处理器
          this.invokeBeanFactoryPostProcessors(beanFactory);
          //注册bean的后处理器
          this.registerBeanPostProcessors(beanFactory);
          beanPostProcess.end();
          //初始化信息资源
          this.initMessageSource();
          //初始化应用程序的事件组播器
          this.initApplicationEventMulticaster();
          //刷新ThemeSource源文件
          this.onRefresh();
          //注册监听器
          this.registerListeners();
          //完成bean工厂的初始化加载
          this.finishBeanFactoryInitialization(beanFactory);
          //完成刷新
          this.finishRefresh();
        } catch (BeansException var10) {
          if (this.logger.isWarnEnabled()) {
            this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
          }
    
          this.destroyBeans();
          this.cancelRefresh(var10);
          throw var10;
        } finally {
          this.resetCommonCaches();
          contextRefresh.end();
        }
    
      }
    }
    

    ThemeSource接口定义一些主题资源的规范,由具体的能够解决主题的对象实现,给定Theme的消息需要参数化和国际化

    onRefresh()用于初始化主题资源,在FrameworkServlet中还实现了一个onRefresh(WebApplicationContext wac)方法,用于刷新DispatcherServlet的组件
    这个方法在FrameworkServlet.initWebApplicationContext()中被调用,即WebApplicationContext被初始化之后调用

    DispatcherServlet.onRefresh()

    protected void onRefresh(ApplicationContext context) {
      this.initStrategies(context);
    }
    //初始化这个Servlet使用的策略对象
    protected void initStrategies(ApplicationContext context) {
      //初始化分片解析器
      this.initMultipartResolver(context);
      //初始化本地资源解析器
      this.initLocaleResolver(context);
      //初始化主题解析器
      this.initThemeResolver(context);
      //初始化映射处理器,用于将请求映射到对应的处理器
      this.initHandlerMappings(context);
      //初始化适配处理器,用于将请求转发给处理器进行处理
      this.initHandlerAdapters(context);
      //初始化异常处理器
      this.initHandlerExceptionResolvers(context);
      //初始化视图名称解析器
      this.initRequestToViewNameTranslator(context);
      //初始化视图解析器
      this.initViewResolvers(context);
      //初始化FlashMap管理器
      this.initFlashMapManager(context);
    }
    
  • initHanlder方法

    onRefresh()调用初始化策略的方法,生成ApplicationContext对应的组件(九大组件)

    initMultipartResolver()

    //支持上传文件等操作的组件
    private void initMultipartResolver(ApplicationContext context) {
      try {
        //获取mutilpartResolver的bean实例,该实例必须实现MutipartResolver
        this.multipartResolver = (MultipartResolver)context.getBean("multipartResolver", MultipartResolver.class);
        if (this.logger.isTraceEnabled()) {
          this.logger.trace("Detected " + this.multipartResolver);
        } else if (this.logger.isDebugEnabled()) {
          this.logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
        }
      } catch (NoSuchBeanDefinitionException var3) {
        this.multipartResolver = null;
        if (this.logger.isTraceEnabled()) {
          this.logger.trace("No MultipartResolver 'multipartResolver' declared");
        }
      }
    }
    

    initLocaleResolver()

    //用于初始化本地资源,确定要使用哪种语言来渲染视图
    private void initLocaleResolver(ApplicationContext context) {
      try {
        this.localeResolver = (LocaleResolver)context.getBean("localeResolver", LocaleResolver.class);
        if (this.logger.isTraceEnabled()) {
          this.logger.trace("Detected " + this.localeResolver);
        } else if (this.logger.isDebugEnabled()) {
          this.logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
        }
      } catch (NoSuchBeanDefinitionException var3) {
        //获取不到则使用默认本地资源
        this.localeResolver = (LocaleResolver)this.getDefaultStrategy(context, LocaleResolver.class);
        if (this.logger.isTraceEnabled()) {
          this.logger.trace("No LocaleResolver 'localeResolver': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
        }
      }
    }
    

    initThemeResolver()

    //解析请求中的主题信息,主题可以被看做是一个应用程序的外观的对象实例,通过使用不同的主题,可以更改应用程序的外观
    private void initThemeResolver(ApplicationContext context) {
      try {
        this.themeResolver = (ThemeResolver)context.getBean("themeResolver", ThemeResolver.class);
        if (this.logger.isTraceEnabled()) {
          this.logger.trace("Detected " + this.themeResolver);
        } else if (this.logger.isDebugEnabled()) {
          this.logger.debug("Detected " + this.themeResolver.getClass().getSimpleName());
        }
      } catch (NoSuchBeanDefinitionException var3) {
        //如果获取不到主题就使用默认主题
        this.themeResolver = (ThemeResolver)this.getDefaultStrategy(context, ThemeResolver.class);
        if (this.logger.isTraceEnabled()) {
          this.logger.trace("No ThemeResolver 'themeResolver': using default [" + this.themeResolver.getClass().getSimpleName() + "]");
        }
      }
    }
    

    initHandlerMappings()重点

    //用于将请求正确的映射到相应的处理程序上
    private void initHandlerMappings(ApplicationContext context) {
      this.handlerMappings = null;
      //检测所有的HandlerMapping处理器
      if (this.detectAllHandlerMappings) {
        //获取所有的HandlerMapping处理器实现类
        Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        //如果HandlerMapping处理器的实现类不为空
        if (!matchingBeans.isEmpty()) {
          //添加到handerMappings列表中
          this.handlerMappings = new ArrayList(matchingBeans.values());
          //进行排序
          AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
      } else {
        try {
          //如果不需要检测所有的HandlerMapping实现类,则尝试从ApplicationContext中获取名为HandlerMapping的HandlerMapping实现类,并将其作为单个元素添加到handlerMappings列表中
          HandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class);
          this.handlerMappings = Collections.singletonList(hm);
        } catch (NoSuchBeanDefinitionException var4) {
        }
      }
      //如果检测的HandlerMapping实现类为空,或者ApplicationContext中获取不到,则获取默认的HandlerMapping策略,并添加到handlerMappings列表中
      if (this.handlerMappings == null) {
        this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class);
        if (this.logger.isTraceEnabled()) {
          this.logger.trace("No HandlerMappings declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");
        }
      }
      //遍历handlerMappings列表,检测其中的HandlerMapping实现类是否使用了路径模式,即@PathVariable注解,如果使用了则将parseRequestPath属性设置为true
      Iterator var6 = this.handlerMappings.iterator();
    
      while(var6.hasNext()) {
        HandlerMapping mapping = (HandlerMapping)var6.next();
        if (mapping.usesPathPatterns()) {
          this.parseRequestPath = true;
          break;
        }
      }
    
    }
    

    initHandlerAdapters()重点

    //初始化适配处理器,用于调用合适的处理器
    private void initHandlerAdapters(ApplicationContext context) {
      this.handlerAdapters = null;
      //是否进行HandlerAdapter检查
      if (this.detectAllHandlerAdapters) {
        //获取所有的HandlerAdapter
        Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        //如果HandlerAdapter不为空,则将所有的HandlerAdapter加入到handlerAdapter列表中,并排序
        if (!matchingBeans.isEmpty()) {
          this.handlerAdapters = new ArrayList(matchingBeans.values());
          AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
      } else {
        try {
          //不检查,则从ApplicationContext中获取名为handerAdapter的HandlerAdapter实现类
          HandlerAdapter ha = (HandlerAdapter)context.getBean("handlerAdapter", HandlerAdapter.class);
          //并加入handlerAdapters列表中
          this.handlerAdapters = Collections.singletonList(ha);
        } catch (NoSuchBeanDefinitionException var3) {
        }
      }
      //如果没有获取到任何的handlerAdapter,则获取默认的HandlerAdapter对象
      if (this.handlerAdapters == null) {
        this.handlerAdapters = this.getDefaultStrategies(context, HandlerAdapter.class);
        if (this.logger.isTraceEnabled()) {
          this.logger.trace("No HandlerAdapters declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");
        }
      }
    }
    

    initHandlerExceptionResolvers()重点

    //用于获取异常的处理器,在出现异常时,使用处理器进行处理 
    private void initHandlerExceptionResolvers(ApplicationContext context) {
       this.handlerExceptionResolvers = null;
       if (this.detectAllHandlerExceptionResolvers) {
         Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
         if (!matchingBeans.isEmpty()) {
           this.handlerExceptionResolvers = new ArrayList(matchingBeans.values());
           AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
         }
       } else {
         try {
           HandlerExceptionResolver her = (HandlerExceptionResolver)context.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
           this.handlerExceptionResolvers = Collections.singletonList(her);
         } catch (NoSuchBeanDefinitionException var3) {
         }
       }
    
       if (this.handlerExceptionResolvers == null) {
         this.handlerExceptionResolvers = this.getDefaultStrategies(context, HandlerExceptionResolver.class);
         if (this.logger.isTraceEnabled()) {
           this.logger.trace("No HandlerExceptionResolvers declared in servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");
         }
       }
    
     }
    

    initRequestToViewNameTranslator()

    //请求转视图名称解析器,用于将请求转换为视图名称
    private void initRequestToViewNameTranslator(ApplicationContext context) {
      try {
        this.viewNameTranslator = (RequestToViewNameTranslator)context.getBean("viewNameTranslator", RequestToViewNameTranslator.class);
        if (this.logger.isTraceEnabled()) {
          this.logger.trace("Detected " + this.viewNameTranslator.getClass().getSimpleName());
        } else if (this.logger.isDebugEnabled()) {
          this.logger.debug("Detected " + this.viewNameTranslator);
        }
      } catch (NoSuchBeanDefinitionException var3) {
        this.viewNameTranslator = (RequestToViewNameTranslator)this.getDefaultStrategy(context, RequestToViewNameTranslator.class);
        if (this.logger.isTraceEnabled()) {
          this.logger.trace("No RequestToViewNameTranslator 'viewNameTranslator': using default [" + this.viewNameTranslator.getClass().getSimpleName() + "]");
        }
      }
    
    }
    

    initViewResolvers()

    //解析视图处理器 
    private void initViewResolvers(ApplicationContext context) {
       this.viewResolvers = null;
       if (this.detectAllViewResolvers) {
         Map<String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
         if (!matchingBeans.isEmpty()) {
           this.viewResolvers = new ArrayList(matchingBeans.values());
           AnnotationAwareOrderComparator.sort(this.viewResolvers);
         }
       } else {
         try {
           ViewResolver vr = (ViewResolver)context.getBean("viewResolver", ViewResolver.class);
           this.viewResolvers = Collections.singletonList(vr);
         } catch (NoSuchBeanDefinitionException var3) {
         }
       }
    
       if (this.viewResolvers == null) {
         this.viewResolvers = this.getDefaultStrategies(context, ViewResolver.class);
         if (this.logger.isTraceEnabled()) {
           this.logger.trace("No ViewResolvers declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");
         }
       }
    
     }
    

    initFlashMapManager()

    //用于解析flashMap 
    private void initFlashMapManager(ApplicationContext context) {
       try {
         this.flashMapManager = (FlashMapManager)context.getBean("flashMapManager", FlashMapManager.class);
         if (this.logger.isTraceEnabled()) {
           this.logger.trace("Detected " + this.flashMapManager.getClass().getSimpleName());
         } else if (this.logger.isDebugEnabled()) {
           this.logger.debug("Detected " + this.flashMapManager);
         }
       } catch (NoSuchBeanDefinitionException var3) {
         this.flashMapManager = (FlashMapManager)this.getDefaultStrategy(context, FlashMapManager.class);
         if (this.logger.isTraceEnabled()) {
           this.logger.trace("No FlashMapManager 'flashMapManager': using default [" + this.flashMapManager.getClass().getSimpleName() + "]");
         }
       }
    
     }
    
  • 48
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Carl·杰尼龟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值