Java学习之「Spring + AspectJ 」

15 篇文章 0 订阅
7 篇文章 0 订阅

目录

■前言

■省略部分

■代码构造

 ■运行效果

■具体代码

AspectTest

MyAspect   ★★★ @Aspect

AppConf   ★★★ @EnableAspectJAutoProxy

  ★★★★★★ @EnableAspectJAutoProxy

Child

Parent

Person

■其他配置

Maven

BackLog

■AOP相关术语说明

42.【Spring中这两个核心思想」都是一种设计模式(工厂,代理)

代理

43.静态代理:

44.动态代理

50.织入方式

49.AspectJ三种织入方式

■编写代码时,遇到的错误「com.sun.proxy.$Proxy21 cannot be cast」

但是,如果修改代理类的属性【EnableAspectJAutoProxy】指定为true

则不会出错。下面的这个类会被加载 CGLIB

@EnableAspectJAutoProxy

■更多 AOP 内容了解

Spring AOP 和AspectJ

ProceedingJoinPoint和JoinPoint



■前言

【Spring中这两个核心思想」都是一种设计模式(工厂,代理)

  IoC的思想基于工厂模式
  
AOP的思想则是基于代理模式

AspectJ是一个面向切面的框架,是目前最好用,最方便的AOP框架

【静态代理】编译时织入(特殊编译器实现)、类加载时织入(特殊的类加载器实现

(常见的实现:JDK静态代理,AspectJ 。)

【动态代理】运行时增强(运行时织入),它不修改代理类的字节码,而是在程序运行时,运用反射机制,在【内存中】临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
( 常见的实现:JDK、CGLIB、Javassist(Hibernate中使用的动态代理))

■省略部分

安装maven

安装lombook

■代码构造

 ■运行效果

14:09:56.035 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4361bd48
14:09:56.064 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
14:09:56.154 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\dev\MyJava001\target\classes\com\sxz\study\classload\Child.class]
14:09:56.155 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\dev\MyJava001\target\classes\com\sxz\study\classload\Parent.class]
14:09:56.157 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\dev\MyJava001\target\classes\com\sxz\study\aop\AspectTest.class]
14:09:56.161 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\dev\MyJava001\target\classes\com\sxz\study\aop\MyAspect.class]
14:09:56.233 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
14:09:56.234 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
14:09:56.235 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
14:09:56.235 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
14:09:56.238 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.aop.config.internalAutoProxyCreator'
14:09:56.347 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConf'
14:09:56.387 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public java.lang.Object com.sxz.study.aop.MyAspect.around(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable
14:09:56.390 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public void com.sxz.study.aop.MyAspect.before()
14:09:56.390 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public void com.sxz.study.aop.MyAspect.after()
14:09:56.391 [main] DEBUG org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory - Found AspectJ method: public void com.sxz.study.aop.MyAspect.afterReturning()
14:09:56.557 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'child'
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
父类--变量
父类--初始化块
父类--构造器
父类--变量
父类--初始化块
父类--构造器
14:09:56.589 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'parent'
父类--变量
父类--初始化块
父类--构造器
14:09:56.590 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'aspectTest'
14:09:56.591 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'myAspect'
14:09:56.600 [main] INFO com.sxz.study.aop.AspectTest - ===============================================
14:09:56.604 [main] INFO com.sxz.study.aop.MyAspect - ★★★★★around_1 startTime:0,endTime:0
14:09:56.604 [main] INFO com.sxz.study.aop.MyAspect - ★★before startTime:1642831796604,endTime:0
14:09:56.604 [main] INFO com.sxz.study.classload.Child - Children is playing
14:09:56.607 [main] INFO com.sxz.study.aop.MyAspect - ★★★afterReturning startTime:1642831796604,endTime:1642831796607,程序执行时间->3ms
14:09:56.607 [main] INFO com.sxz.study.aop.MyAspect - ★★★★after startTime:1642831796604,endTime:1642831796607,程序执行时间->3ms
14:09:56.607 [main] INFO com.sxz.study.aop.MyAspect - ★★★★★around_2 startTime:1642831796604,endTime:1642831796607
14:09:56.607 [main] INFO com.sxz.study.aop.AspectTest - ===============================================
14:09:56.607 [main] INFO com.sxz.study.classload.Parent - Parent is playing

---

■具体代码

AspectTest

package com.sxz.study.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

import com.sxz.study.classload.Person;
import com.sxz.study.conf.AppConf;

import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class AspectTest {

	public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConf.class);
        Person child = (Person)context.getBean("child");
        log.info("===============================================");
        child.play();
        log.info("===============================================");
        Person parent = (Person)context.getBean("parent");
        parent.play();
        

	}
	

}

MyAspect   ★★★ @Aspect

package com.sxz.study.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Aspect
@Component
@Slf4j
public class MyAspect {
    private long startTime;
    private long endTime;

    /**
     * 声明一个切点
     */
    //@Pointcut("execution(* com.sxz.study.classload...*(..))")
    @Pointcut("execution(* com.sxz.study.classload.Child.play(..))")
    private void joinPoint(){
    	// 这里的方法不会被执行
        log.info("●●●Pointcut startTime:{},endTime:{}" ,startTime, endTime);
    }

    /**
     * 程序执行之前
     */
    @Before("joinPoint()")
    public void before(){
        this.startTime = System.currentTimeMillis();
        log.info("★★before startTime:{},endTime:{}" ,startTime, endTime);
    }

    /**
     * 程序执行之后
     */
    @After("joinPoint()")
    public void after(){
        this.endTime = System.currentTimeMillis();
        log.info("★★★★after startTime:{},endTime:{},程序执行时间->{}ms" ,startTime, endTime,endTime-startTime);
    }
    
    /**
     * 程序执Ruturn之后
     */
    @AfterReturning("joinPoint()")
    public void afterReturning(){
        this.endTime = System.currentTimeMillis();
        log.info("★★★afterReturning startTime:{},endTime:{},程序执行时间->{}ms" ,startTime, endTime,endTime-startTime);
    }


    @Around("joinPoint()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        log.info("★★★★★around_1 startTime:{},endTime:{}" ,startTime, endTime);
        // return proceedingJoinPoint.proceed();
        proceedingJoinPoint.proceed();
        log.info("★★★★★around_2 startTime:{},endTime:{}" ,startTime, endTime);
        return null;
    }
    

}


AppConf   ★★★ @EnableAspectJAutoProxy

package com.sxz.study.conf;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan(basePackages={"com.sxz.study.classload",
		"com.sxz.study.aop"   
})
@EnableAspectJAutoProxy
@Import({com.sxz.study.aop.MyAspect.class})/*@Aspect可以生效,相当于Configuration类作用,都是配置类*/  
public class AppConf {

}

  ★★★★★★ @EnableAspectJAutoProxy

1.在配置类上添加@EnableAspectJAutoProxy注解,开启注解版的AOP功能

2.声明切面类:@Aspect表明这是一个切面类,
注:@Aspect是spring-annotation2/libs/com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar的注解。

EnableAspectJAutoProxy (Spring Framework 5.3.18 API)

ーーーー

Child

package com.sxz.study.classload;

import org.springframework.stereotype.Component;

import com.sxz.study.aop.MyAspect;

import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class Child extends Parent {        
    // 静态变量        
    public static String c_StaticField = "子类--静态变量";        
    // 变量        
    public String c_Field = "子类--变量";        
       
    // 静态初始化块        
    static {        
        System.out.println(c_StaticField);        
        System.out.println("子类--静态初始化块");        
    }        
       
    // 初始化块        
    {        
        System.out.println(p_Field);        
        System.out.println("父类--初始化块");        
    }        
       
    // 构造器        
    public Child() {        
        System.out.println("父类--构造器");        
    }
    
    public void play(){
        log.info("Children is playing");
    }
}

Parent

package com.sxz.study.classload;

import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class Parent implements Person {        
    // 静态变量        
    public static String p_StaticField = "父类--静态变量";        
    // 变量        
    public String p_Field = "父类--变量";        
       
    // 静态初始化块        
    static {        
        System.out.println(p_StaticField);        
        System.out.println("父类--静态初始化块");        
    }        
       
    // 初始化块        
    {        
        System.out.println(p_Field);        
        System.out.println("父类--初始化块");        
    }        
       
    // 构造器        
    public Parent() {        
        System.out.println("父类--构造器");        
    }

	@Override
	public void play() {
        log.info("Parent is playing");
		
	}       
    
    
}

Person

package com.sxz.study.classload;

public interface Person {
   void play();

}

---

■其他配置

Maven

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cn.sxz123</groupId>
  <artifactId>Test001</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Test001</name>

  
  <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.10.RELEASE</version>
  </parent>
  
  
    <dependencies>
	  	<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
	  	</dependency>
	  	
	  	
	  <dependency>
    	<groupId>org.projectlombok</groupId>
    	<artifactId>lombok</artifactId>
	  </dependency>
	
  	</dependencies>

</project>

BackLog

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern><![CDATA[%n[%d{yyyy-MM-dd HH:mm:ss.SSS}] [level: %p] [Thread: %t] [ Class:%c >> Method: %M:%L ]%n%p:%m%n]]></pattern>
        </encoder>
    </appender>
    <appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern><![CDATA[%n[%d{yyyy-MM-dd HH:mm:ss.SSS}] [level: %p] [Thread: %t] [ Class:%c >> Method: %M:%L ]%n%p:%m%n]]></pattern>
        </encoder>
        <file>J:/logs/sxz-web.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/sports-web.-%d{yyyyMMdd}.%i.log</fileNamePattern>
            <!-- 每天一个日志文件,当天的日志文件超过10MB时,生成新的日志文件,当天的日志文件数量超过totalSizeCap/maxFileSize,日志文件就会被回滚覆盖。 -->
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy> 
    </appender>
    <logger name="com.sxz" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="LOG_FILE"/>
        <!--<appender-ref ref="myAppender"/>-->
    </logger>
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="LOG_FILE"/>
        <!--<appender-ref ref="mqAppender"/>-->
    </root>
</configuration>

---

■AOP相关术语说明

切点Pointcut,切点代表了一个关于目标函数的过滤规则,后续的通知是基于切点来跟目标函数关联起来的。

  然后要围绕该切点定义一系列的通知Advice,如@Before、@After、@AfterReturning、@AfterThrowing、@Around等等定义的方法都是通知。其含义是在切点定义的函数执行之前、完成之后、正常返回之后、抛出异常之后以及环绕前后执行对应的切面逻辑。

  一个切点和针对该切点的一个通知共同构成了一个切面Advisor对于一个方法,我们可以定义多个切点都隐含它,并且对于每个切点都可定义多个通知来形成多个切面,SpringAOP底层框架会保证在该方法调用时候将所有符合条件的切面都切入到其执行之前或之后或环绕。通知Advice的子类Interceptor或MethodInterceptor的类名更具体一些,包含了拦截器的概念。
---

一 概念理解
■  1     AOP的术语重在理解。  =======   
Join Point:(连接点) Spring AOP中,join point就是一个方法。(通俗来讲就是起作用的那个方法,具体执行的方法)
 
Pointcut:(切入点)  用来指定join point(通俗来讲就是描述的一组符合某个条件的join point)。通常使用pointcut表达式来限定joint point,Spring默认使用AspectJ pointcut expression language。
 
Advice: 在join point上特定的时刻执行的操作,Advice有几种不同类型,下文将会讨论(通俗地来讲就是起作用的内容和时间点)。
 
Introduction:给对象增加方法或者属性。
 
Target object: Advice起作用的那个对象。
 
AOP proxy: 为实现AOP所生成的代理。在Spring中有两种方式生成代理:JDK代理和CGLIB代理。
 
Aspect: 组合了Pointcut与Advice,在Spring中有时候也称为Advisor。某些资料说Advisor是一种特殊的Aspect,其区别是Advisor只能包含一对pointcut和advice,但是aspect可以包含多对。AOP中的aspect可以类比于OOP中的class。
 
Weaving:将Advice织入join point的这个过程。
 
■ 2  Advice的类型 =======  
Before advice:  执行在join point之前的advice,但是它不能阻止joint point的执行流程,除非抛出了一个异常(exception)。
 
After returning advice: 执行在join point这个方法返回之后的advice。
 
After throwing advice: 执行在join point抛出异常之后的advice。
 
After(finally) advice: 执行在join point返回之后或者抛出异常之后的advice,通常用来释放所使用的资源。
 
Around advice: 执行在join point这个方法执行之前与之后的advice。
 
■ 3 两种代理 =======  
Spring AOP是基于代理机制的。Spring AOP通过JDK Proxy和CGLIB Proxy两种方法实现代理。
 
如果target object没有实现任何接口,那么Spring将使用CGLIB来实现代理。CGLIB是一个开源项目,它是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
 
如果target object实现了一个以上的接口,那么Spring将使用JDK Proxy来实现代理,因为Spring默认使用的就是JDK Proxy,并且JDK Proxy是基于接口的。这也是Spring提倡的面向接口编程。当然,你也可以强制使用CGLIB来进行代理,但是这样可能会造成性能上的下降。

---

42.【Spring中这两个核心思想」都是一种设计模式(工厂,代理)

  IoC的思想基于工厂模式
  AOP的思想则是基于代理模式。

工作中使用到的单词(软件开发)_sun0322-CSDN博客

代理

AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强

43.静态代理:

编译时增强,AOP 框架会在编译阶段生成 AOP 代理类,在程序运行前代理类的.class文件就已经存在了。

常见的实现:JDK静态代理,AspectJ 。

45.【静态代理】有:编译时织入(特殊编译器实现)、类加载时织入(特殊的类加载器实现)。

44.动态代理

运行时增强(运行时织入),它不修改代理类的字节码,而是在程序运行时,运用反射机制,
     在【内存中】临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,
     并且在特定的切点做了增强处理,并回调原对象的方法。
     常见的实现:JDK、CGLIB、Javassist(Hibernate中使用的动态代理)

46.【动态代理】有:jdk动态代理(基于接口来实现)、CGlib(基于类实现)。

47.JDK动态代理特点:

代理对象必须实现一个或多个【接口】
以接口形式接收代理实例,而不是代理类

48.CGLIB动态代理特点:

代理对象不能被final修饰
以类或接口形式接收代理实例

50.织入方式

---

49.AspectJ三种织入方式

---

■编写代码时,遇到的错误「com.sun.proxy.$Proxy21 cannot be cast

java.lang.ClassCastException: com.sun.proxy.$Proxy21 cannot be cast to com.sxz.study.classload.Child
	at com.sxz.study.aop.AspectTest.main(AspectTest.java:19)

---

错误原因,没有使用接口接收

	public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConf.class);
        // Person child = (Person)context.getBean("parent");
        Person child = (Child)context.getBean("child");
        log.info("===============================================");
        child.play();
        log.info("===============================================");
        Person parent = (Person)context.getBean("parent");
        parent.play();
	}

---

但是,如果修改代理类的属性【EnableAspectJAutoProxy】指定为true

package com.sxz.study.conf;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan(basePackages={"com.sxz.study.classload",
		"com.sxz.study.aop"   
})
@EnableAspectJAutoProxy(proxyTargetClass=true)
@Import({com.sxz.study.aop.MyAspect.class})/*@Aspect可以生效,相当于Configuration类作用,都是配置类*/  
public class AppConf {

}

则不会出错。下面的这个类会被加载 CGLIB

[Loaded com.sxz.study.classload.Child$$EnhancerBySpringCGLIB$$bcc03a27$$FastClassBySpringCGLIB$$c23bec39 from file:/C:/dev/MyJava001/target/classes/]

@EnableAspectJAutoProxy

@EnableAspectJAutoProxy引入了AspectJAutoProxyRegister.class对象 ,AspectJAutoProxyRegister给容器中注册一个AnnotationAwareAspectJAutoProxyCreator:

---

表示开启AOP代理自动配置,如果配@EnableAspectJAutoProxy表示使用cglib进行代理对象的生成;设置@EnableAspectJAutoProxy(exposeProxy=true)表示通过aop框架暴露该代理对象,aopContext能够访问.

从@EnableAspectJAutoProxy的定义可以看得出,它引入AspectJAutoProxyRegister.class对象,该对象是基于注解@EnableAspectJAutoProxy注册一个AnnotationAwareAspectJAutoProxyCreator,该对象通过调用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);注册一个aop代理对象生成器。

---

springboot的注解@EnableAspectJAutoProxy讲解_码农的世界你不懂-CSDN博客_enableaspectjautoproxy

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	/**
	 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
	 * to standard Java interface-based proxies. The default is {@code false}.
	 */
	boolean proxyTargetClass() default false;

	/**
	 * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
	 * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
	 * Off by default, i.e. no guarantees that {@code AopContext} access will work.
	 * @since 4.3.1
	 */
	boolean exposeProxy() default false;

}

---

■更多 AOP 内容了解

Spring AOP 和AspectJ

https://www.baeldung.com/spring-aop-vs-aspectj

ProceedingJoinPoint和JoinPoint

JointPoint用法及与ProceedingJoinPoint 的关系 - 云+社区 - 腾讯云

ーーー

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值