2、spring学习笔记

Spring IOC 解决的是 对象管理和对象依赖的问题。
Spring AOP 解决的是 非业务代码抽取的问题。

Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架。以 IoC(Inverse Of Control:控制反转)和AOP(Aspect Oriented Programming:面向切面编程)为内核。

控制反转指的就是:本来是由我们自己new出来的对象,现在交给了IOC容器(Spring为我们提供得IOC容器),把这个对象的控制权给别人了。控制反转更多的是一种思想或者说是设计模式,把原有由自己掌控的事交给别人来处理。

依赖注入(Dependency Injection)是 IOC(控制反转) 的具体实现,对象无需自行创建或管理它们的依赖关系,依赖关系将被自动注入到需要它们的对象当中去。在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。

最简单理解依赖注入和控制反转:本来我们的对象都是由我们自己new出来的,现在我们把这个对象的创建权限和对象之间的依赖关系交由IOC容器来管理。

为什么我们要把对象交给IOC容器来管理呢?就是想将对象集中统一管理,便于修改。 降低耦合度(调用方无需自己组装,也无需关心对象的实现,直接从IOC容器中获取就好了。

无论是创建对象、处理对象之间的依赖关系、对象创建的时间还是对象的数量,我们都是在Spring为我们提供的IOC容器上配置对象的信息就好了。

Spring提供了四种方式来管理IOC容器:注解、XML、JavaConfig、基于Groovy DSL配置 ,掌握注解和XML方式即可。
在这里插入图片描述

Spring优势
方便解耦,简化开发
AOP 编程的支持
声明式事务的支持
方便程序的测试

Spring开发步骤
①导入 Spring 开发的基本包坐标
②编写 Dao 接口和实现类
③创建 Spring 核心配置文件(习惯命名为applicationContext.xml)
④在 Spring 配置文件中配置 UserDaoImpl
⑤使用 Spring 的 API 获得 Bean 实例

获取Bean实例之前需要获取IOC容器,通过IOC容器获得Bean实例,IOC容器有两种类型:
①BeanFactory

Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);

②ApplicationContext(一般使用这个)

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService)ac.getBean("userService");

配置文件applicationContext.xml

<bean id="userDao" class="cn.xuexi.dao.impl.UserDaoImpl" scope="prototype"></bean>
Bean标签基本配置:id  class(配置全限定名)
Bean标签范围配置:scope(singleton  prototype  request  globalsession)
Bean初始化方法和销毁方法配置:init-method  destroy-method
singleton:Bean在加载配置文件创建Bean容器时候创建,只创建一个。只要容器在,对象一直活着。当销毁容器时,对象就被销毁了
prototype:Bean在getBean时候创建,获取一次创建一个。只要对象使用就一直存活。当对象长时间不用时,被 Java GC回收。

Bean实例化三种方式(Bean对象创建方式)
无参构造方法:会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败。

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>

工厂静态方法:先要有工厂类实现静态方法

public class StaticFactoryBean {
        public static UserDao createUserDao(){
            return new UserDaoImpl();
        }
      }

然后xml配置

  <bean id="userDao" class="com.itheima.factory.StaticFactoryBean" factory-method="createUserDao" />

工厂实例方法 :先要有工厂类

public class DynamicFactoryBean {
            public UserDao createUserDao(){
                return new UserDaoImpl();
            }
        }

然后xml配置

<bean id="factoryBean" class="com.itheima.factory.DynamicFactoryBean"/>
<bean id="userDao" factory-bean="factoryBean" factory-method="createUserDao"/>


Bean的依赖注入方式有两种 构造方法注入(``第一个userDao是构造函数参数名,第二个userDao是第一行的对象) ```xml ``` set方法注入(第一个userDao是setUserDao后边部分把首字母小写,第二个userDao是第一行的对象),在实现类中需要有set方法:
     public class UserServiceImpl implements UserService {
            private UserDao userDao;
            public void setUserDao(UserDao userDao) {
                this.userDao = userDao;
            }
            @Override
            public void save() {
                userDao.save();
            }
        }
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"/>
</bean>

P命名空间(由set方法演化而来)

 <bean id="userService" class="com.itheima.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>

除了对象的引用可以注入,普通数据类型,集合也可以在容器中进行注入。
可注入三种数据类型
普通数据类型
引用数据类型
集合

ApplicationContext实现类
ClassPathXmlApplicationContext:从类的根路径下加载配置文件
FileSystemXmlApplicationContext:从磁盘路径上加载配置文件
AnnotationConfifigApplicationContext:注解时使用










Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,使用注解可以简化配置。

使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。

<context:component-scan base-package="cn.xuexi"></context:component-scan>

原始注解
@Component 使用在类上用于实例化Bean
@Controller 使用在web层类上用于实例化Bean
@Service 使用在service层类上用于实例化Bean
@Repository 使用在dao层类上用于实例化Bean
@Autowired 使用在字段上用于根据类型依赖注入
@Qualififier 结合@Autowired一起使用用于根据名称进行依赖注入
@Resource 相当于@Autowired+@Qualififier,按照名称进行注入
@Value 注入普通属性
@Scope 标注Bean的作用范围
@PostConstruct 使用在方法上标注该方法是Bean的初始化方法
@PreDestroy 使用在方法上标注该方法是Bean的销毁方法
新注解
@Configuration 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan 用于指定 Spring 在初始化容器时要扫描的包。 作用和在 Spring 的 xml 配置文件中的 <context:component-scan base-package=“com.itheima”/>一样,需要和@Configuration这个注解配合一起使用。
@Bean 使用在方法上,标注将该方法的返回值存储到 Spring 容器中
@PropertySource 用于加载.properties 文件中的配置
@Import 用于导入其他配置类










静态代理代码如下,静态代理缺点在于接口改了代理需要跟着改。

// 接口
public interface IUserDao {
    void save();
}


//UserDao实现该接口,重写save()方法
public class UserDao implements IUserDao{
    @Override
    public void save() {
        System.out.println("-----已经保存数据!!!------");
    }
}

//代理实现IUserDao接口
public class UserDaoProxy implements IUserDao{
    private IUserDao target;
    public UserDaoProxy(IUserDao target) {
        this.target = target;
    }
    @Override
    public void save() {
        System.out.println("开始事务...");
        target.save();
        System.out.println("提交事务...");
    }
}

//外界调用代理而不是目标
public static void main(String[] args) {
        IUserDao target = new UserDao();
        IUserDao proxy = new UserDaoProxy(target);
        proxy.save();
    }
   

动态代理相对于静态代理,代理对象不需要实现接口,代理对象是利用JDKAPI生成, 动态地在内存中构建代理对象
常用的动态代理技术
JDK 代理 : 基于接口的动态代理技术,缺点是目标对象必须有一个接口,代理对象基于此接口实现。
cglib 代理:基于父类的动态代理技术
在这里插入图片描述

由于静态代理的目标对象和代理对象需要实现相同的接口,因此出现了JDK动态代理,由于JDK动态代理的目标对象一定是要实现接口(增强对象不需要实现该接口),因此出现了cglib动态代理,也叫子类代理。

JDK动态代理代码:

//接口
public interface TargetInterface {
    public void save();
}

//目标对象(需要实现接口)
public class Target implements TargetInterface {
    public void save() {
        System.out.println("save running.....");
    }
}

//增强对象(不需要实现接口)
public class Advice {
    public void before(){
        System.out.println("前置增强....");
    }
    public void afterReturning(){
        System.out.println("后置增强....");
    }
}

//调用
public class ProxyTest {
    public static void main(String[] args) {
        final Target target = new Target();
        final Advice advice = new Advice();
        TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(), //目标对象类加载器
                target.getClass().getInterfaces(), //目标对象相同的接口字节码对象数组
                new InvocationHandler() {
                    //调用代理对象的任何方法  实质执行的都是invoke方法
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        advice.before(); //前置增强
                        Object invoke = method.invoke(target, args);//执行目标方法
                        advice.afterReturning(); //后置增强
                        return invoke;
                    }
                }
        );
        //调用代理对象的方法
        proxy.save();
    }
}

cglib动态代理代码:

//目标对象
public class Target {
    public void save() {
        System.out.println("save running.....");
    }
}

//增强对象
public class Advice {
    public void before(){
        System.out.println("前置增强....");
    }
    public void afterReturning(){
        System.out.println("后置增强....");
    }
}

//调用
public class ProxyTest {
    public static void main(String[] args) {
        final Target target = new Target();
        final Advice advice = new Advice();

        //返回值 就是动态生成的代理对象  基于cglib
        //1、创建增强器
        Enhancer enhancer = new Enhancer();
        //2、设置父类(目标)
        enhancer.setSuperclass(Target.class);
        //3、设置回调
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                advice.before(); //执行前置
                Object invoke = method.invoke(target, args);//执行目标
                advice.afterReturning(); //执行后置
                return invoke;
            }
        });
        //4、创建代理对象
        Target proxy = (Target) enhancer.create()
        //调用代理对象的方法
        proxy.save();
    }
}





AOP :Aspect Oriented Programming(面向切面编程),是通过预编译方式和运行期动态代理(cglib)实现程序功能的统一维护的一种技术。其主要干的事情就是:把重复的代码抽取成切面类代码,在运行的时候在业务方法上动态植入切面类代码。

AOP 在程序运行期间,在不修改源码的情况下对方法进行功能增强。可减少重复代码,提高开发效率,并且便于维护。
AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象的方法,从而完成功能的增强。

使用xml开发AOP步骤:
①导入 AOP 相关坐标

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.13</version>
</dependency>

②创建目标接口和目标类(内部有切点)

public interface TargetInterface {
    public void method();
}
public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

③创建切面类(内部有增强方法)

public class MyAspect {
    //前置增强方法
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

④将目标类和切面类的对象创建权交给 spring(在application.xml中配置)

<bean id="target" class="com.itheima.aop.Target"></bean>
<bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>

⑤在 applicationContext.xml 中配置织入关系
先要导入aop命名空间

<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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

再配置织入关系

<aop:config>
    <!--引用myAspect的Bean为切面对象-->
    <aop:aspect ref="myAspect">
        <!--配置Target的method方法执行时要进行myAspect的before方法前置增强-->
        <aop:before method="before" pointcut="execution(public void com.itheima.aop.Target.method()) "></aop:before>
    </aop:aspect>
</aop:config>

⑥测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    @Autowired
    private TargetInterface target;
    @Test
    public void test1(){
        target.method();
    }
}

上方代码pointcut="execution(public void com.itheima.aop.Target.method())部分是切点表达式,其语法为 execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 如execution(public void com.itheima.aop.Target.method())

  • 访问修饰符可以省略 如execution(void com.itheima.aop.Target.method())
  • 返回值类型、包名、类名、方法名可以使用星号* 代表任意 如execution(public * ..*())
  • 包名与类名之间一个点 . 代表当前包下的类,两个点. . 表示当前包及其子包下的类 如execution(public void com.itheima.aop. . Target.method())
  • 参数列表可以使用两个点. . 表示任意个数,任意类型的参数列表 如execution(public void com.itheima.aop.Target.method())

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。

<aop:config>
    <!--引用myAspect的Bean为切面对象-->
    <aop:aspect ref="myAspect">
        <aop:pointcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"/>
        <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
    </aop:aspect>
</aop:config>

使用注解开发aop步骤
①创建目标接口和目标类(内部有切点)

public interface TargetInterface {
    public void method();
}
@Component("target")
public class Target implements TargetInterface {
    @Override
    public void method() {
        System.out.println("Target running....");
    }
}

②创建切面类(内部有增强方法)

@Component("myAspect")
@Aspect
public class MyAspect {
    //前置增强方法
    @Before("execution(* com.itheima.aop.*.*(..))")
    public void before(){
        System.out.println("前置代码增强.....");
    }
}

③在配置文件中开启组件扫描和 AOP 的自动代理

<context:component-scan base-package="com.itheima.aop"/>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

④测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    @Autowired
    private TargetInterface target;
    @Test
    public void test1(){
        target.method();
    }
}

如果在同一个xml中有配置一样的aop,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在在增强注解中进行引用。具体如下:

@Component("myAspect")
@Aspect
public class MyAspect {
    @Before("MyAspect.myPoint()")
    public void before(){
        System.out.println("前置代码增强.....");
    }
    @Pointcut("execution(* com.itheima.aop.*.*(..))")
    public void myPoint(){}
}





Spring可以直接帮我们获取数据库连接池 配置文件中配置
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd


<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

代码中的dataSource可以直接获取
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“applicationContext.xml”);
DataSource dataSource = (DataSource)applicationContext.getBean(“dataSource”);

JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。
使用JdbcTemplate开发步骤:
①导入spring-jdbc和spring-tx坐标

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

②创建数据库表和实体
③创建JdbcTemplate对象

public void test1() throws PropertyVetoException {
//创建数据源对象
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setDriverClass("com.mysql.jdbc.Driver");
    dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
    dataSource.setUser("root");
    dataSource.setPassword("root");
    JdbcTemplate jdbcTemplate = new JdbcTemplate();
    //设置数据源对象 知道数据库在哪
    jdbcTemplate.setDataSource(dataSource);
    //执行操作
    int row = jdbcTemplate.update("insert into account values(?,?)", "tom", 5000);
    System.out.println(row);
}

一般我们使用JdbcTemplate,会将dataSource和jdbcTemplate对象创建交给spring:

<!--加载jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--数据源对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
<!--jdbc模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>





设置事务的隔离级别,可以解决事务并发产生的脏读、不可重复读和虚读等问题。 ISOLATION_DEFAULT ISOLATION_READ_UNCOMMITTED 读未提交 ISOLATION_READ_COMMITTED 读已提交 ISOLATION_REPEATABLE_READ 可重复读 ISOLATION_SERIALIZABLE 顺序读

事务的传播行为:常用的就是REQUIRED和REQUERS_NEW

REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。是默认选项。
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
是否只读:建议查询时设置为只读

一般地,我们事务控制都是在service层做的。为什么要在service层而不是在dao层呢:service层是业务逻辑层,service的方法一旦执行成功,那么说明该功能没有出错。一个service方法可能要调用dao层的多个方法,如果在dao层做事务控制的话,一个dao方法出错了,仅仅把事务回滚到当前dao的功能,这样是不合适的(因为我们的业务由多个dao方法组成)。如果没有出错,调用完dao方法就commit了事务,这也是不合适的(导致太多的commit操作)。

事务控制分为两种:
①编程式事务控制:自己手动控制事务,事务的控制粒度较细,可以对指定的方法或指定方法的某几行添加事务控制。比较灵活,但开发起来比较繁琐,每次都要开启、 提交、回滚。如Conn.setAutoCommite(false)
②声明式事务控制:Spring提供的事务控制。

声明式事务的优势:事务管理不侵入开发的组件,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可;在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便。

如果用户想要使用Spring的事务控制,只需要配置就行了。当不用Spring事务的时候,直接移除就行了。Spring的事务控制是基于AOP实现的。因此它的耦合度是非常低的。Spring的事务控制是粗粒度的事务控制,只能给整个方法应用事务,不可以对方法的某几行应用事务。(因为aop拦截的是方法)

Spring提供了两种声明式事务管理器类:Jdbc或mybatis的DataSourceTransactionManager和Hibernate的HibernateTransactionManager

声明式事务,顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。

基于xml配置声明式事务步骤
①引入tx命名空间

<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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

②配置事务增强

<!--平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!--事务增强配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

③配置事务 AOP 织入

<!--事务的aop增强-->
<aop:config>
    <aop:pointcut id="myPointcut" expression="execution(* com.itheima.service.impl.*.*(..))"/> //配置完成后com.itheima.service.impl下的所有包中的所有类的所有方法都被Spring的声明式事务控制了
    <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"></aop:advisor>
</aop:config>

基于注解配置声明式事务步骤
①编写 AccoutDao

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void out(String outMan, double money) {
        jdbcTemplate.update("update account set money=money-? where name=?",money,outMan);
    }
    public void in(String inMan, double money) {
        jdbcTemplate.update("update account set money=money+? where name=?",money,inMan);
    }
}

②编写 AccoutService

@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    public void transfer(String outMan, String inMan, double money) {
        accountDao.out(outMan,money);
        int i = 1/0;
        accountDao.in(inMan,money);
    }
}

③编写 applicationContext.xml 配置文件

<!—省略datsSource、jdbcTemplate、平台事务管理器的配置-->
<!--组件扫描-->
<context:component-scan base-package="com.itheima"/>
<!--一定开启事务的注解驱动-->
<tx:annotation-driven/>

注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。使用在方法上,不同的方法可以采用不同的事务参数配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值