如何使用Java代码方式配置Spring的IOC容器


Spring 3.0开始, Spring 提供的许多功能都是使用的 JavaConfig配置的。所以我们可以使用 Java而不是 XML文件来定义应用程序类外部的 bean

@Bean和@Configuration 的基础使用

SpringJava配置主要组成部分就是@Configuration注释的类和@Bean注释的方法。其实@Bean注释与XML中的<bean />元素具有相同的作用 ,@Bean注解也是主要用于标注的方法来实例化,配置和初始化要由Spring IoC容器管理的新对象。@Bean批注方法也可以与任何Spring @Component一起使用。但是,一般我们都是与@Configuration bean一起使用。 用@Configuration注释类表示该类的主要目的是作为Bean定义的来源。此外,@Configuration类允许通过调用同一类中的其他@Bean方法来定义Bean之间的依赖关系。
最简单的@Configuration类的内容如下:

@Configuration
public class Javaconfig {

    @Bean
    public User user(){
        return new User("张三",18);
    }
}

其中Bean的定义如下:

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // get/set ...
}

使用AnnotationConfigApplicationContext实例化Spring容器的几中方式

AnnotationConfigApplicationContext这种通用的ApplicationContext实现不仅能够接受@Configuration类作为输入,而且还可以接受普通的@Component类和带有SR-330元数据注释的类。 当提供@Configuration类作为输入时,@Configuration类本身将注册为Bean定义,并且该类中所有已声明的@Bean方法也将注册为Bean定义。 提供@ComponentJSR-330类时,它们将注册为bean定义。

  • 使用简单默认的方式创建AnnotationConfigApplicationContext
    与实例化ClassPathXmlApplicationContext时将Spring XML文件用作参数的方式几乎相同,实例化 AnnotationConfigApplicationContext时可以将@Configuration类用作参数
    如下面的示例所示完全不需要使用XML就可以初始化IOC容器
public class CommonCeshi {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(Javaconfig.class);
        User user = applicationContext.getBean("user", User.class);
        System.out.println(user.toString());
    }   
}

如上所述,AnnotationConfigApplicationContext不限于仅与@Configuration类一起使用。可以将任何@ComponentJSR-330带注释的类作为参数提供给构造函数,如以下示例所示:

public class CommonCeshi {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(User.class);
        User user = applicationContext.getBean("user", User.class);
        System.out.println(user.toString());
    }
}

其中上述的User类使用了@Component注解,让其添加到AnnotationConfigApplicationContext也是可以构建IOC容器的,User定义如下:

@Component
public class User {
    private String name = "历史";
    private int age = 12;

    // get/set ...
}
  • 使用register(Class<?>…​)的方式构建AnnotationConfigApplicationContext容器
    您可以使用无参数构造函数实例化AnnotationConfigApplicationContext,然后使用register()方法对其进行配置。以编程方式构建AnnotationConfigApplicationContext时,此方法特别有用。以下示例显示了如何执行此操作:
public class CommonCeshi {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext();
        applicationContext.register(User.class);
        //记得这里需要执行refresh()方法刷新容器
        applicationContext.refresh();
        User user = applicationContext.getBean("user", User.class);
        System.out.println(user.toString());
    }
}
  • 使用scan(String…​)开启组件扫描来实例化AnnotationConfigApplicationContext容器
    scan()方法的主要作用就是扫描参数中指定包结构下通过@Component注解标注的类,把这些类定义成Bean并加载到IOC容器中,组件扫描可以使用以下几中方法定义
    1. 使用@ComponentScan(basePackages = "xxxx")注解的形式开启扫描

      @Configuration
      @ComponentScan(value = "ioc.javabaseconfig")
      public class Javaconfig {
      }
      
    2. 使用XML形式定义开启扫描

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
          <context:component-scan base-package="ioc.javabaseconfig"/>
      </beans>
      
    3. 使用调用scan(xxx)方法的形式开启扫描

      public class CommonCeshi {
          public static void main(String[] args) {
              AnnotationConfigApplicationContext applicationContext =
                      new AnnotationConfigApplicationContext();
              applicationContext.scan("ioc.javabaseconfig");
              //记得这里需要执行refresh()方法刷新容器
              applicationContext.refresh();
              User user = applicationContext.getBean("user", User.class);
              System.out.println(user.toString());
          }
      }
      

通过AnnotationConfigWebApplicationContext支持Web应用程序

AnnotationConfigWebApplicationContext提供了AnnotationConfigApplicationContextWebApplicationContext变体。在配置Spring ContextLoaderListener Servlet侦听器,Spring MVC DispatcherServlet等时,可以使用此实现。以下web.xml代码片段配置了典型的Spring MVC Web应用程序(请注意contextClass context-paraminit-param的用法):

<web-app>
    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

@Bean注解的使用

@Bean是方法级别的注解,跟XML <bean />元素的作用是一样的。<bean />提供的一些属性,例如: init-method destroy-method autowiring@Bean注解都支持。可以在@Configuration注解类或@Component注解的类中使用@Bean注解。

  • 如何声明一个Bean
    要声明bean,可以使用@Bean注解对方法进行标注。可以使用此方法的返回值(其中这个返回值可以是这个类实现的接口或者父类,不一定必须要是自己)在ApplicationContext中注册Bean定义。默认情况下,Bean名称与方法名称相同。以下示例显示了@Bean法声明:

    @Configuration
    public class Javaconfig {
    
        @Bean
        public User user(){
            User user = new User();
            user.setAge(13);
            user.setName("王五");
            return user;
        }
    }
    

    以上声明Bean的方式等同于下面XML声明的形式:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
        <bean id="user" class="ioc.javabaseconfig.User"></bean>
    </beans>
    
  • 注入Bean的依赖
    @Bean注解的方法可以具有任意数量的参数,这些参数描述构建该bean所需的依赖关系。例如,如果我们的User需要一个Address地址参数,则可以使用方法参数来实现该依赖关系,如以下示例所示:

    @Configuration
    public class Javaconfig {
    
        @Bean
        public User user(Address address){
            User user = new User();
            user.setAge(13);
            user.setName("王五");
            user.setAddress(address);
            return user;
        }
        //也可以不使用方法参数的形式,直接调用方法
    //    @Bean
    //    public User user(){
    //        User user = new User();
    //        user.setAge(13);
    //        user.setName("王五");
    //        user.setAddress(address());
    //        return user;
    //    }
    
        @Bean
        public Address address(){
            Address address = new Address();
            address.setAddress("深圳");
            return address;
        }
    }
    
  • Bean的声明周期回调
    任何使用@Bean注解定义的类都支持常规的生命周期回调,并且可以使用JSR-250中的@PostConstruct@PreDestroy注解。还完全支持常规的Spring生命周期回调。如果bean实现了InitializingBeanDisposableBeanLifecycle,则容器将调用它们各自的方法。 还完全支持标准的Aware接口集(例如BeanFactoryAwareBeanNameAwareMessageSourceAwareApplicationContextAware等)。 @Bean注解支持指定任意的初始化和销毁​​回调方法,非常类似于`bean元素上Spring XML的init-method和destroy-method属性,如以下示例所示:

    @Configuration
    public class Javaconfig {
    
        @Bean(initMethod = "initUser",destroyMethod = "destoryUser")
        public User user(Address address){
            User user = new User();
            user.setAge(13);
            user.setName("王五");
            user.setAddress(address);
            return user;
        }
    
        @Bean
        public Address address(){
            Address address = new Address();
            address.setAddress("深圳");
            return address;
        }
    }
    

    User类中定义好初始化和销毁的方法,这样User对象在被初始化的时候会先调用initUser这个初始化方法在执行后续的操作,最后对象被销毁的时候也会调用destoryUser方法进行销毁,如果不想对象被销毁的时候调用此方法,可以在Configuration类中的destroyMethod不给值就可以了,另外初始方法和销毁方法也可以不通过@Bean的属性指定,可以在new对象后直接用对象调用这两个方法也是可以的:

    public class User {
        private String name = "历史";
        private int age = 12;
        private Address address;
    
        public void initUser(){
            // init
            System.out.println("init User");
        }
        public void destoryUser(){
            //destory
            System.out.println("destory User");
        }
    
        // get/set ...
    
    • 指定Beanscope作用域
      您可以指定使用@Bean批注定义的bean应该具有的作用域。使用Scopes指定任何标准范围。 默认范围是单例,但是您可以使用@Scope注释覆盖它,如以下示例所示:

      @Configuration
      public class Javaconfig {
      
          @Bean
          @Scope(value = "prototype")
          public User user(Address address){
              User user = new User();
              user.setAge(13);
              user.setName("王五");
              user.setAddress(address);
              return user;
          }
      
          @Bean
          public Address address(){
              Address address = new Address();
              address.setAddress("深圳");
              return address;
          }
      }
      
  • Bean自定义一个名称
    默认情况下,配置类使用@Bean方法的名称作为结果bean的名称。但是,可以使用name属性覆盖此功能,如以下示例所示:

    @Configuration
    public class Javaconfig {
    
        @Bean(name = "userName")
        public User user(Address address){
            User user = new User();
            user.setAge(13);
            user.setName("王五");
            user.setAddress(address);
            return user;
        }
    
        @Bean(name = "abc")
        public Address address(){
            Address address = new Address();
            address.setAddress("深圳");
            return address;
        }
    }
    
  • Bean自定义一个别名
    有时希望为单个Bean提供多个名称,也称为Bean别名。为此,@Bean注解的name属性接受一个String数组。以下示例显示了如何为bean设置多个别名:

    @Configuration
    public class Javaconfig {
    
        @Bean(name = {"user","userName"})
        public User user(Address address){
            User user = new User();
            user.setAge(13);
            user.setName("王五");
            user.setAddress(address);
            return user;
        }
    
        @Bean(name = {"address","adb"})
        public Address address(){
            Address address = new Address();
            address.setAddress("深圳");
            return address;
        }
    }
    
  • Bean自定义一段描述
    有时,提供有关bean的更详细的文本描述会很有帮助。对于向外暴露API的时候可以提供描述信息。 要向@Bean添加描述,可以使用@Description注解,如以下示例所示:

    @Configuration
    public class Javaconfig {
    
        @Bean(name = {"user","userName"})
        @Description("这是一个User对象的描述")
        public User user(Address address){
            User user = new User();
            user.setAge(13);
            user.setName("王五");
            user.setAddress(address);
            return user;
        }
    
        @Bean(name = {"address","adb"})
        @Description("这是地址对象的描述")
        public Address address(){
            Address address = new Address();
            address.setAddress("深圳");
            return address;
        }
    }
    

@Configuration注解的使用

@Configuration是类级别的注解,指示对象是Bean定义的源。 @Configuration类通过公共@Bean注解方法声明bean。对@Configuration类的@Bean方法的调用也可以用于定义Bean之间的依赖关系。

  • 注入bean间的依赖关系
    bean彼此依赖时,表达这种依赖就像让一个bean方法调用另一个一样简单,如以下示例所示:

    @Configuration
    	public class Javaconfig {
    	
    	    @Bean
    	    public User user(Address address){
    	        User user = new User();
    	        user.setAge(13);
    	        user.setName("王五");
    	        user.setAddress(address);
    	        return user;
    	    }
    	    //也可以不使用方法参数的形式,直接调用方法
    	//    @Bean
    	//    public User user(){
    	//        User user = new User();
    	//        user.setAge(13);
    	//        user.setName("王五");
    	//        user.setAddress(address());
    	//        return user;
    	//    }
    	
    	    @Bean
    	    public Address address(){
    	        Address address = new Address();
    	        address.setAddress("深圳");
    	        return address;
    	    }
    	}
    

    仅当在@Configuration类中声明@Bean方法时,此声明bean间依赖关系的方法才有效。在使用@Component注解类中声明的Bean的依赖关系不能直接调用。

  • Lookup查找方法注入
    查找方法注入是一项高级功能,一般很少使用。在单例作用域的bean依赖于原型作用域的bean的情况下,这很有用。将Java用于这种类型的配置为实现此模式提供了自然的方法。下面的示例演示如何使用查找方法注入:
    首先定义一个单例的抽象类,其中有个需要原型作用域的抽象方法,最后这个抽象方法在配置类中覆盖就可以了。

    public abstract class LookupMethod {
        public Object process(){
            User user = createdUser();
            return user;
        }
    
        public abstract User createdUser();
    }
    

    配置类中配置需要的对象:

    @Configuration
    public class Javaconfig {
        @Bean
        public LookupMethod lookupMethod(){
            return new LookupMethod() {
                @Override
                public User createdUser() {
                    return user(address());
                }
            };
        }
    
        @Bean(name = {"user","userName"})
        @Scope("prototype")
        public User user(Address address){
            User user = new User();
            user.setAge(13);
            user.setName("王五");
            user.setAddress(address);
            return user;
        }
    
        @Bean(name = {"address","adb"})
        @Description("这是地址对象的描述")
        public Address address(){
            Address address = new Address();
            address.setAddress("深圳");
            return address;
        }
    }
    
  • 有关基于Java的配置如何在内部工作的更多信息
    以下示例,显示了一个@Bean注释方法被调用两次,被调用两次的Bean应该会创建两个对象,但是其实只创建了一个对象:

    @Configuration
    public class Javaconfig {
    
        @Bean
        public User user1(){
            User user = new User();
            user.setAge(13);
            user.setName("王五");
            user.setAddress(address());
            return user;
        }
        @Bean
        public User user2(){
            User user = new User();
            user.setAge(13);
            user.setName("王五");
            user.setAddress(address());
            return user;
        }
    
        @Bean
        public Address address(){
            Address address = new Address();
            address.setAddress("深圳");
            return address;
        }
    }
    

    address()user1()中被调用一次,并在user2()中被调用一次。由于此方法创建了Address的新实例并返回它,因此通常希望有两个实例(每个服务一个)。那肯定是有问题的:在Spring中,实例化的bean默认情况下具有单例作用域。这就是神奇的地方:所有@Configuration类在启动时都使用CGLIB进行了子类化。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存(作用域)的bean,如果有就不会再调用方法创建,也就是只会创建一次单例的对象,如果作用域是原型的就创建两个对象。从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已经在org.springframework.cglib下重新打包并直接包含在spring-core jar中。

结合各个 Java配置类

  • 使用@Import注解
    与在Spring XML文件中使用<import />元素来帮助模块化配置一样,@Import批注允许从另一个配置类加载@Bean定义,如以下示例所示:

    @Configuration
    public class JavaConfigOther {
        @Bean
        public Address address(){
            Address address = new Address();
            address.setAddress("深圳");
            return address;
        }
    }
    
    @Configuration
    @Import(JavaConfigOther.class)
    public class Javaconfig {
    	//也可以使用这种方式
    	//@Autowired
    	//private Address address;
        @Bean
        public User user(Address address){
            User user = new User();
            user.setAge(13);
            user.setName("王五");
            user.setAddress(address);
            return user;
        }
    }
    

    以上也可以不使用@Import注解导入配置类,因为@Configuration注解也会被定义成Bean所以也可以直接使用@Autowired这种的注解注入进来。

  • 有条件地包含@Configuration类或@Bean方法
    根据某些系统状态,有条件地启用或禁用完整的@Configuration类甚至单个@Bean方法通常很有用。一个常见的示例是仅在Spring环境中启用了特定配置文件时才使用@Profile注解来激活Bean@Profile注解实际上是通过使用更灵活的称为@Conditional的注释来实现的。 @Conditional注解指示在注册@Bean之前应咨询的特定org.springframework.context.annotation.Condition实现。 Condition接口的实现提供了一个matches(…)方法,该方法返回truefalse。例如,以下清单显示了用于@Profile的实际Condition实现:

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Read the @Profile annotation attributes
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
        return true;
    }
    
  • 结合JavaXML配置
    Spring@Configuration类支持并非旨在100%完全替代Spring XML。某些工具(例如Spring XML名称空间)仍然是配置容器的理想方法。在使用XML方便或有必要的情况下,您可以选择:使用 ClassPathXmlApplicationContext以以XML为中心的方式实例化容器,或使用AnnotationConfigApplicationContext和“AnnotationConfigApplicationContextJava中心的方式实例化容器。 @ImportResource批注可根据需要导入XML

    • XML为中心的@Configuration类的使用
      在使用Spring XML的大型现有代码库中,可以根据需要轻松创建@Configuration类,并从现有XML文件中包含它们

      • @Configuration类声明为纯Spring <bean />元素
        请记住,@Configuration类最终是容器中的bean定义。在本系列示例中,我们创建一个名为JavaConfig@Configuration类,并将其作为<bean />定义包含在spring-config.xml中。由于<context:annotation-config />处于打开状态,因此容器可以识别@Configuration注解并正确处理JavaConfig中声明的@Bean方法。 以下示例显示了Java中的普通配置类:

        @Configuration
        public class Javaconfig {
            @Autowired
            private DataSource dataSource;
        
            @Bean
            public User user(){
                User user = new User();
                user.setAge(13);
                user.setName("王五");
                return user;
            }
        }
        			
        

        以下是spring-config.xml的配置信息如下:

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:context="http://www.springframework.org/schema/context"
               xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
            <context:annotation-config/>
            <context:property-placeholder location="classpath*:spring.properties" file-encoding="UTF-8"/>
        
            <bean class="ioc.javabaseconfig.Javaconfig"/>
            <bean class="ioc.javabaseconfig.Address">
                <property name="address" value="${address.name}"/>
            </bean>
        </beans>
        

        系统属性文件spring.properties内容如下:

        address.name=广州
        
      • 使用<context:component-scan />扫描@Configuration

        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:context="http://www.springframework.org/schema/context"
               xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
        <!--      这里开启扫描来批量注入Bean,所以基于注解和<bean>都可以不用声明,系统外的jar包的类是需要声明的  -->
            <context:component-scan base-package="ioc.javabaseconfig"/>
        <!--    <context:annotation-config/>-->
            <context:property-placeholder location="classpath*:spring.properties" file-encoding="UTF-8"/>
        
        <!--    <bean class="ioc.javabaseconfig.Javaconfig"/>-->
        <!--    <bean class="ioc.javabaseconfig.Address">-->
        <!--        <property name="address" value="${address.name}"/>-->
        <!--    </bean>-->
        </beans>
        
    • @Configuration以类为中心的XML@ImportResource的使用
      @Configuration类是配置容器的主要机制的应用程序中,仍然有必要至少使用一些XML。在这些情况下,您可以使用@ImportResource并仅定义所需的XML。这样做实现了以Java为中心的方法来配置容器,并使XML保持在最低限度。以下示例(包括配置类,定义BeanXML文件,属性文件和主类)显示了如何使用@ImportResource批注来实现按需使用XML的以Java为中心的配置:

      @Configuration
      @ImportResource(value = "classpath:spring-config.xml")
      public class Javaconfig {
      
          @Value("${address.name}")
          private String addre;
      
          @Bean
          public User user(){
              User user = new User();
              user.setAge(13);
              user.setName("王五");
              user.setAddress(address());
              return user;
          }
      
          @Bean
          public Address address(){
              Address address = new Address();
              address.setAddress(addre);
              return address;
          }
      }
      

      XML的配置如下:

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
      <context:property-placeholder location="classpath*:spring.properties" file-encoding="UTF-8"/>
      </beans>
      

      系统属性文件spring.properties内容如下:

      address.name=广州
      

以上内容就是如何使用Java配置来初始化Spring IOC容器,注入指定Bean的处理方式。

参考文献

【https://docs.spring.io/spring-framework/docs/current/reference/html/core.html】
【1.12. Java-based Container Configuration】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值