面向切面(AOP)之Spring接口方式 schema配置方式 aspectj注解方式

一、初识AOP

  关于AOP的学习可以参看帮助文档:spring-3.2.0.M2\docs\reference\html目录下index.html的相关章节  
   1、AOP:Aspect-OrientedProgramming。AOP是OOP的补充,是GOF的延续。说到AOP,我们就不得不来提一下软件的纵向和横向问题。从纵向结构来看就是我们软件系统的各个模块,它主要负责处理我们的核心业务(例如商品订购、购物车查看);而从横向结构来看,我们几乎每个系统又包含一些公共模块(例如权限、日志模块等)。这些公共模块分布于我们各个核心业务之中(例如订购和查看商品明细的过程都需要检查用户权限、记录系统日志等)。这样一来不仅在开发过程中要处处关注公共模块的处理而且开发后维护起来也是十分麻烦。而有了AOP之后将应用程序中的商业逻辑同对其提供支持的通用服务进行分离,使得开发人员可以更多的关注核心业务开发。
Spring学习4-面向切面(AOP)之Spring接口方式
 2、AOP术语
 
    切面(aspect):用来切插业务方法的类。
  连接点(joinpoint):是切面类和业务类的连接点,其实就是封装了业务方法的一些基本属性,作为通知的参数来解析。
  通知(advice):在切面类中,声明对业务方法做额外处理的方法。
  切入点(pointcut):业务类中指定的方法,作为切面切入的点。其实就是指定某个方法作为切面切的地方。
  目标对象(target object):被代理对象。
  AOP代理(aop proxy):代理对象。
  通知:
  前置通知(before advice):在切入点之前执行。
  后置通知(after returning advice):在切入点执行完成后,执行通知。
  环绕通知(around advice):包围切入点,调用方法前后完成自定义行为。
  异常通知(after throwing advice):在切入点抛出异常后,执行通知。
  AOP是基于代理模式,了解了jdk动态代理和cglib的用法,对我们学习大有裨益。

Spring学习4-面向切面(AOP)之Spring接口方式

Spring学习4-面向切面(AOP)之Spring接口方式

Spring学习4-面向切面(AOP)之Spring接口方式

Spring学习4-面向切面(AOP)之Spring接口方式

Spring学习4-面向切面(AOP)之Spring接口方式

二、Spring AOP环境
  要在项目中使用Spring AOP 则需要在项目中导入除了springjar包之外,还有aspectjrt.jar,aspectjweaver.jar,aopalliance.jar,spring-aop-3.2.0.M2.jar和cglib.jar 。
   好了,前提工作准备完成,Spring提供了很多的实现AOP的方式:Spring接口方式,schema配置方式和注解等,好了废话不多说了,开始springaop学习之旅,这篇先以Spring接口的方式学起!

三、Spring接口方式实现AOP步骤
 

  利用Spring AOP接口实现AOP,主要是为了指定自定义通知来供spring AOP机制识别。主要接口:前置通知MethodBeforeAdvice,后置通知:AfterReturningAdvice,环绕通知:MethodInterceptor,异常通知:ThrowsAdvice。见例子代码:
 
步骤一、业务接口的编写
 //代理类接口,也是业务类接口<br>
 // 利用接口的方式,spring aop 将默认通过jdk动态代理来实现代理类<br>
 // 不利用接口,则spring aop 将通过cglib 来实现代理类

public interface IBaseBusiness {

   // 用作代理的切入点方法
     public String delete(String obj)

 

   //这方法不被切面切
    public String add(String obj);
  
   
//这方法切不切呢?可以设置
   public String modify(String obj);
}

步骤二、业务类:

 //业务类,也是目标对象

public class BaseBusiness implements IBaseBusiness {
    //切入点
     public String delete(String obj) {
       System.out.println("==========调用切入点:" + obj +"说:你敢删除我!===========\n");
       return obj + ":瞄~";
    }

    publicString add(String obj) {
       System.out.println("================这个方法不能被切。。。==============\n");
       return obj + ":瞄~ 嘿嘿!";
    }

    publicString modify(String obj) {
       System.out.println("=================这个也设置加入切吧====================\n");
       return obj + ":瞄改瞄啊!";
    }

}

步骤三、通知类:
   1、前置通知:

public class BaseBeforeAdvice implements MethodBeforeAdvice {

    // method : 切入的方法 <br>
    //args :切入方法的参数 <br>
    // target :目标对象
   
   @Override
    public voidbefore(Method method, Object[] args, Object target) throwsThrowable {
       System.out.println("===========进入beforeAdvice()============\n");

       System.out.print("准备在" + target + "对象上用");
       System.out.print(method + "方法进行对 '");
       System.out.print(args[0] + "'进行删除!\n\n");

       System.out.println("要进入切入点方法了 \n");
    }

}

 2、后置通知:

public class BaseAfterReturnAdvice implements AfterReturningAdvice{
    //returnValue :切入点执行完方法的返回值,但不能修改<br>
    // method :切入点方法 <br>
    // args:切入点方法的参数数组 <br>
    // target :目标对象
    
   @Override
    public voidafterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable {
       System.out.println("==========进入afterReturning()===========\n");
       System.out.println("切入点方法执行完了 \n");

       System.out.print(args[0] + "在");
       System.out.print(target + "对象上被");
       System.out.print(method + "方法删除了");
       System.out.print("只留下:" + returnValue + "\n\n");
    }

}

  3、环绕通知:

public class BaseAroundAdvice implements MethodInterceptor {

    // invocation :连接点
    
   @Override
    publicObject invoke(MethodInvocation invocation) throws Throwable {
       System.out.println("===========进入around环绕方法!=========== \n");

       // 调用目标方法之前执行的动作
       System.out.println("调用方法之前: 执行!\n");

       // 调用方法的参数
       Object[] args = invocation.getArguments();
       // 调用的方法
       Method method = invocation.getMethod();
       // 获取目标对象
       Object target = invocation.getThis();
       // 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
       Object returnValue = invocation.proceed();

       System.out.println("===========结束进入around环绕方法!===========\n");
      
       System.out.println("输出:" + args[0] + ";" + method + ";" + target +";" + returnValue + "\n");

       System.out.println("调用方法结束:之后执行!\n");

       return returnValue;
    }

}

 4、异常通知:


// 异常通知,接口没有包含任何方法。通知方法自定义

public class BaseAfterThrowsAdvice implements ThrowsAdvice {
    // 通知方法,需要按照这种格式书写
    // @param method
    //         可选:切入的方法
    //@param args
    //         可选:切入的方法的参数
    // @paramtarget
    //        可选:目标对象
    // @param throwable
    //  必填 : 异常子类,出现这个异常类的子类,则会进入这个通知。
   
    public voidafterThrowing(Method method, Object[] args, Object target,Throwable throwable) {
       System.out.println("删除出错啦");
    }

}

步骤四、定义指定切点:

 //定义一个切点,指定对应方法匹配。来供切面来针对方法进行处理<br>
 // 继承NameMatchMethodPointcut类,来用方法名匹配

public class Pointcut extends NameMatchMethodPointcut {

    privatestatic final long serialVersionUID = 3990456017285944475L;

   @SuppressWarnings("rawtypes")
   @Override
    publicboolean matches(Method method, Class targetClass) {
       // 设置单个方法匹配
       this.setMappedName("delete");
       // 设置多个方法匹配
       String[] methods = { "delete", "modify" };
      
       //也可以用“ * ” 来做匹配符号
       // this.setMappedName("get*");
      
       this.setMappedNames(methods);

       return super.matches(method, targetClass);
    }

}

步骤五、配置xml文件:
<?xml version="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xsi:schemaLocation="   
         http://www.springframework.org/schema/beans   
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
          
<!-- 基于注解导入的-->       
        http://www.springframework.org/schema/context   
         http://www.springframework.org/schema/context/spring-context-3.0.xsd
       
<!-- 基于aop导入的-->
        http://www.springframework.org/schema/aop   
         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
   default->

   <!--==============================利用spring自己的aop配置================================-->
   <!-- 声明一个业务类 -->
   <bean id="baseBusiness"class="aop.base.BaseBusiness" />
  
   <!-- 声明通知类 -->
   <bean id="baseBefore"class="aop.base.advice.BaseBeforeAdvice" />
   <bean id="baseAfterReturn"class="aop.base.advice.BaseAfterReturnAdvice"/>
   <bean id="baseAfterThrows"class="aop.base.advice.BaseAfterThrowsAdvice"/>
   <bean id="baseAround"class="aop.base.advice.BaseAroundAdvice" />

   <!-- 指定切点匹配类 -->
   <bean id="pointcut"class="aop.base.pointcut.Pointcut" />

   <!-- 包装通知,指定切点 -->
   <bean id="matchBeforeAdvisor"class="org.springframework.aop.support.DefaultPointcutAdvisor">
       <property name="pointcut">
           <ref bean="pointcut" />
       </property>
       <property name="advice">
           <ref bean="baseBefore" />
       </property>
   </bean>

   <!-- 使用ProxyFactoryBean 产生代理对象-->
   <bean id="businessProxy"class="org.springframework.aop.framework.ProxyFactoryBean">
       <!-- 代理对象所实现的接口 ,如果有接口可以这样设置-->
       <propertyname="proxyInterfaces">
           <value>aop.base.IBaseBusiness</value>
       </property>

       <!-- 设置目标对象 -->
       <property name="target">
           <ref local="baseBusiness" />
       </property>
       <!-- 代理对象所使用的拦截器 -->
       <propertyname="interceptorNames">
           <list>
               <value>matchBeforeAdvisor</value>
               <value>baseAfterReturn</value>
               <value>baseAround</value>
           </list>
       </property>
   </bean>
</beans>

步骤六、测试类:

public class Debug {

    publicstatic void main(String[] args) {
       ApplicationContext context = newClassPathXmlApplicationContext("aop/schema_aop.xml");
       AspectBusiness business = (AspectBusiness)context.getBean("aspectBusiness");
       business.delete("猫");
    }

}

g、测试结果:运行下测试类,清晰明了。由于结果呈现太长就不贴了。
  具体的代码实现可以从代码注释中很容易理解接口方式的实现。结果也可想而知,前置方法会在切入点方法之前执行,后置会在切入点方法执行之后执行,环绕则会在切入点方法执行前执行同事方法结束也会执行对应的部分。主要是调用proceed()方法来执行切入点方法。来作为环绕通知前后方法的分水岭。然后在实现的过程中,有几点却是可以细揣摩一下的。
  可以看出在xml 配置businessProxy这个bean的时候,ProxyFactoryBean类中指定了,proxyInterfaces参数。这里我把他配置了IBaseBusiness接口。因为在项目开发过程中,往往业务类都会有对应的接口,以方便利用IOC解耦。但SpringAOP却也能支持没有接口的代理。这就是为什么需要导入cglib.jar的包了。看过spring的源码,知道在目标切入对象如果有实现接口,spring会默认使用jdk动态代理来实现代理类。如果没有接口,则会通过cglib来实现代理类。
  这个业务类现在有前置通知,后置通知,环绕三个通知同时作用,可能以及更多的通知进行作用。那么这些通知的执行顺序是怎么样的?就这个例子而言,同时实现了三个通知。在例子xml中,则显示执行before通知,然后执行around的前处理,执行切点方法,再执行return处理。最后执行around的后处理。经过测试,知道spring处理顺序是按照xml配置顺序依次处理通知,以队列的方式存放前通知,以压栈的方式存放后通知。所以是前通知依次执行,后通知到切入点执行完之后,从栈里在后进先出的形式把后通知执行。
  在实现过程中发现通知执行对应目标对象的整个类中的方法,如何精确到某个方法,则需要定义一个切点匹配的方式:spring提供了方法名匹配或正则方式来匹配。然后通过DefaultPointcutAdvisor来包装通知,指定切点。

 

 利用方式一的配置起来,可见代码还是非常的厚重的,定义一个切面就要定义一个切面类,然而切面类中,就一个通知方法,着实没有必要。所以Spring提供了,依赖aspectj的schema配置和基于aspectj注解方式。这两种方式非常简介方便使用,也是项目中普遍的使用方式。






一、通过Scheme配置实现AOP步骤( SpringAOP环境的环境与上篇博文

Spring接口方式相同


  步骤一、编写业务类:

public class AspectBusiness {
   //
切入点
   public String delete(String obj) {
       System.out.println("==========调用切入点:" + obj +"说:你敢删除我!===========\n");
       return obj + ":瞄~";
    }

    publicString add(String obj) {
       System.out.println("================这个方法不能被切。。。==============\n");
       return obj + ":瞄~ 嘿嘿!";
    }

    publicString modify(String obj) {
       System.out.println("=================这个也设置加入切吧====================\n");
       return obj + ":瞄改瞄啊!";
    }

}

步骤二、编写切面类:切面类中,包含了所有的通知

public class AspectAdvice {

    //
前置通知
  public void doBefore(JoinPoint jp) {
       System.out.println("===========进入before advice============\n");

       System.out.print("准备在" + jp.getTarget().getClass() + "对象上用");
       System.out.print(jp.getSignature().getName() + "方法进行对 '");
       System.out.print(jp.getArgs()[0] + "'进行删除!\n\n");

       System.out.println("要进入切入点方法了 \n");
    }


    // 后置通知
    // @param jp
    //          连接点
    // @param result
    //        返回值
    
    public voiddoAfter(JoinPoint jp, String result) {
       System.out.println("==========进入after advice=========== \n");
       System.out.println("切入点方法执行完了 \n");

       System.out.print(jp.getArgs()[0] + "在");
       System.out.print(jp.getTarget().getClass() + "对象上被");
       System.out.print(jp.getSignature().getName() + "方法删除了");
       System.out.print("只留下:" + result + "\n\n");
    }


    // 环绕通知
    // @parampjp
    //         连接点
   
    publicobjectdoAround(ProceedingJoinPoint pjp) throws Throwable {
       System.out.println("===========进入around环绕方法!=========== \n");

       // 调用目标方法之前执行的动作
       System.out.println("调用方法之前: 执行!\n");

       // 调用方法的参数
       Object[] args = pjp.getArgs();
       // 调用的方法名
       String method = pjp.getSignature().getName();
       // 获取目标对象
       Object target = pjp.getTarget();
       // 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
       Object result = pjp.proceed();

       System.out.println("输出:" + args[0] + ";" + method + ";" + target +";" + result + "\n");
       System.out.println("调用方法结束:之后执行!\n");
    return result;
    }

 
    //异常通知
  
    public voiddoThrow(JoinPoint jp, Throwable e) {
       System.out.println("删除出错啦");
    }

}

步骤四、配置文件的编写:

<?xml version="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xsi:schemaLocation="   
         http://www.springframework.org/schema/beans   
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
         http://www.springframework.org/schema/context   
         http://www.springframework.org/schema/context/spring-context-3.0.xsd
         http://www.springframework.org/schema/aop   
         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
   default->

   <!-- ==============================利用spring利用aspectj来配置AOP================================-->

   <!-- 声明一个业务类 -->
   <bean id="aspectBusiness"class="aop.schema.AspectBusiness" />

    <!-- 声明通知类 -->
   <bean id="aspectAdvice"class="aop.schema.advice.AspectAdvice"/>


   <aop:config>
       <aop:aspect id="businessAspect"ref="aspectAdvice">
           <!-- 配置指定切入的对象 -->
           <aop:pointcut id="point_cut" expression="execution(*aop.schema.*.*(..))" />
           <!-- 只匹配add方法作为切入点
           <aop:pointcut id="except_add"expression="execution(* aop.schema.*.add(..))"/>
            -->

           <!-- 前置通知 -->
           <aop:before method="doBefore"pointcut-ref="point_cut" />
           <!-- 后置通知 returning指定返回参数 -->
           <aop:after-returning method="doAfter"
               pointcut-ref="point_cut" returning="result" />
           <aop:around method="doAround"pointcut-ref="point_cut"/>
           <aop:after-throwing method="doThrow"pointcut-ref="point_cut" throwing="e"/>
       </aop:aspect>
   </aop:config>

</beans>

步骤五、测试类:

public class Debug {

    publicstatic void main(String[] args) {
       ApplicationContext context = newClassPathXmlApplicationContext("aop/schema_aop.xml");
       AspectBusiness business = (AspectBusiness)context.getBean("aspectBusiness");
       business.delete("猫");
    }

}




一、简介
  
1、AOP用在哪些方面:AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制,异常处理等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
   2、AOP中的概念:
   Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面是横切性关注点的抽象.

 joinpoint(连接点):所谓连接点是指那些被拦截到的点(可以是方法、属性、或者类的初始化时机(可以是Action层、Service层、dao层))。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器)

Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义,也即
joinpoint的集合.

Advice(通知):
所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知

Target(目标对象):代理的目标对象

Weave(织入)
:指将aspects应用到target对象并导致proxy对象创建的过程称为织入.

Introduction(引入):
在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field.

  3、AOP带来的好处::降低模块的耦合度;使系统容易扩展;更好的代码复用性


二、通过注解方式实现Spring的AOP
SpringAOP环境的环境与上篇博文

Spring接口方式相同

 注解在项目中已经到处都是了,撇开一些优劣不提,开发的便利性和可读性是非常的方便的。用来配置SpringAOP也非常简单便利
步骤一、编写业务类:

@Component   //使用自动注解的方式实例化并初始化该类
public class Business {
   //
切入点
   public String delete(String obj) {
       System.out.println("==========调用切入点:" + obj +"说:你敢删除我!===========\n");
       return obj + ":瞄~";
    }

    publicString add(String obj) {
       System.out.println("================这个方法不能被切。。。==============\n");
       return obj + ":瞄~ 嘿嘿!";
    }

    publicString modify(String obj) {
       System.out.println("=================这个也设置加入切吧====================\n");
       return obj + ":瞄改瞄啊!";
    }

}

步骤二、切面类:

 // @Aspect : 标记为切面类
 // @Pointcut : 指定匹配切点集合
 // @Before : 指定前置通知,value中指定切入点匹配
 // @AfterReturning :后置通知,具有可以指定返回值
// @AfterThrowing :异常通知
 //注意:前置/后置/异常通知的函数都没有返回值,只有环绕通知有返回值
@Component   //首先初始化切面类
@Aspect     //声明为切面类,底层使用动态代理实现AOP
public class AspectAdvice {

    //指定切入点匹配表达式,注意它是以方法的形式进行声明的。
    //即切点集合是:
aop.annotation包下所有类所有方法
  //第一个*代表返回值类型

//如果要设置多个切点可以使用 || 拼接
 @Pointcut("execution(* aop.annotation.*.*(..))|| execution(*com.action.admin.*.*update*(..))")
    public voidanyMethod() {
    }

    //前置通知
   //在切点方法集合执行前,执行前置通知
   @Before("execution(*aop.annotation.*.*(..))")
    public voiddoBefore(JoinPoint jp) {
       System.out.println("===========进入before advice============\n");

       System.out.print("准备在" + jp.getTarget().getClass() + "对象上用");
       System.out.print(jp.getSignature().getName() + "方法进行对 '");
       System.out.print(jp.getArgs()[0] + "'进行删除!\n\n");

       System.out.println("要进入切入点方法了 \n");
    }

   //后置通知
  
 @AfterReturning(value = "anyMethod()",returning = "result")
    public voiddoAfter(JoinPoint jp, String result) {
       System.out.println("==========进入after advice=========== \n");
       System.out.println("切入点方法执行完了 \n");

       System.out.print(jp.getArgs()[0] + "在");
       System.out.print(jp.getTarget().getClass() + "对象上被");
       System.out.print(jp.getSignature().getName() + "方法删除了");
       System.out.print("只留下:" + result + "\n\n");
    }

    // 环绕通知
(##环绕通知的方法中一定要有ProceedingJoinPoint参数,
   //
Filter中的 doFilter方法类似
   
   @Around("execution(*aop.annotation.*.*(..))")
    publicObject doAround(ProceedingJoinPointpjp) throws Throwable {
       System.out.println("===========进入around环绕方法!=========== \n");
 HttpSession session =ServletActionContext.getRequest().getSession();
    Emp login =(Emp)session.getAttribute("login")
;//ssh2整合后AOP也可以得到request、response、session等
       // 调用目标方法之前执行的动作
       System.out.println("调用方法之前: 执行!\n");

       // 调用方法的参数
       Object[] args = pjp.getArgs();
       // 调用的方法名
       String method = pjp.getSignature().getName();
       // 获取目标对象(形如:com.action.admin.LoginAction@1a2467a)
       Object target = pjp.getTarget();
      //获取目标对象的类名(形如:com.action.admin.LoginAction)
     String targetName = pjp.getTarget().getClass().getName();
       // 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
       Object result =pjp.proceed();//result的值就是被拦截方法的返回值

       System.out.println("输出:" + args[0] + ";" + method + ";" + target +";" + result + "\n");
       System.out.println("调用方法结束:之后执行!\n");
     return result;
    }

    // 异常通知
   
   @AfterThrowing(value= "execution(* aop.annotation.*.*(..))", throwing = "e")
    public voiddoThrow(JoinPoint jp, Throwable e) {
       System.out.println("删除出错啦");
    }

}

 步骤三、xml配置:


<?xml version="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xsi:schemaLocation="   
         http://www.springframework.org/schema/beans   
         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
         http://www.springframework.org/schema/context   
         http://www.springframework.org/schema/context/spring-context-3.0.xsd
         http://www.springframework.org/schema/aop   
         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
   default->

   <context:component-scanbase-package="aop.annotation" />
    <!-- 打开aop 注解 -->
   <aop:aspectj-autoproxyproxy-target-class="true"/>


</beans>

步骤四、测试类:

public class Debug {

    publicstatic void main(String[] args) {

       ApplicationContext context = newClassPathXmlApplicationContext("aop/annotation_aop.xml");
       Business business = (Business) context.getBean("business");
       business.delete("猫");
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值