08ssm_Spring AOP

一 、Spring AOP介绍

AOP的全称是Aspect Oriented Programming,即面向切面编程

AOP将重复的业务逻辑抽取到一个独立的模块中,提供了将其切入到其它业务逻辑中的方法AOP的使用使开发人员专心于核心业务的编写,不必关注其他业务逻辑的

 

AOP提高了开发效率,且

增强了代码的可维护性

二 、Spring AOP的实现机制

AOP的常用术语。

切面(Aspect):指关注点形成的类(关注点是指类中重复的代码)。通常是指封装的、用 于横向插入系统的功能类(如事务管理、日志记录等)。

**连接点(Joinpoint):**指程序执行过程中某个特定的节点。

切入点(Pointcut):当连接点满足预先指定的条件时,AOP在连接点处插入切面,此时的 连接点称为切入点

通知/增强处理(Advice):插入的切面程序代码。即:切面中的方法。 目标对象(Target):目标对象是指被插入切面的方法

织入(Weaving):将切面代码插入到目标对象上,从而生成代理对象的过程

代理(Proxy):将通知应用到目标对象之后,程序动态创建的通知对象就称为代理引介(Introduction):引介是一种特殊的通知,它为目标对象添加一些属性和方法

 

AOP是基于代理机制基础上的,有JDK和CGLib动态代理两种机制

实验环境准备

第一步:在ssm_spring项目下建spring-chap08-aop模块,目录结构如下:

 

第二步:为项目的模块添加依赖,依赖06单元配置好的lib库

 

  1. JDK动态代理

Spring AOP默认使用JDK动态代理,在不修改源代码的情况下增强某些方法

  1. JDK动态代理实现步骤

第一步:创建代理类,此代理类要实现InvocationHandler接口

第二步:在此代理类中创建返回目标类代理对象的方法,此方法中用java.lang.reflect.Proxy 类的newProxyInstance()方法生成代理对象,其下3个参数说明

第1个参数是classLoader,表示当前类的类加载器

第2个参数是classes,表示被代理对象实现的所有接口 第3个参数是this,表示代理类JdkProxy本身

第三步:实现InvocationHandler接口的invoke()方法,其中完成通知的织入

  1. 示例

【示例】阅读代码,理解JDK动态代理实现过程。

  1. 在main\java目录中,创建com.aop.demo01包
  2. 创建接口UserDao,编写添加和删除的方法**(放com.aop.demo01包)**

package com.aop.demo01;

public interface UserDao { void addUser();

void deleteUser();

}

  1. 创建类UserDaoImpl,实现UserDao接口**(放com.aop.demo01包)**

package com.aop.demo01;

public class UserDaoImpl implements UserDao { @Override

public void addUser() { System.out.println("添加用户");

}

@Override

public void deleteUser() { System.out.println("删除用户");

}

}

  1. 创建切面类MyAspect,在该类中定义一个模拟权限检查的方法和一个模拟日志记录的方法**(两个方法就是切面中的通知,放com.aop.demo01包)**

package com.aop.demo01;

public class MyAspect {

public void check_Permissions(){ System.out.println("模拟检查权限...");

}

public void log(){

System.out.println("模拟记录日志...");

}

}

  1. 创建代理类MyJDKProxy,实现InvocationHandler接口设置代理类的调用处理程序,通过

newProxyInstance()生成代理方法 (放com.aop.demo01包)

注意:InvocationHandler接口是java.lang.reflect包下的!!

package com.aop.demo01;

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

public class MyJDKProxy implements InvocationHandler { private UserDao userDao; //声明目标对象(被代理对象)

//创建代理方法

public Object createProxy(UserDao userDao){ this.userDao=userDao;

//获取代理对象

//第一步,获取类加载器

ClassLoader classLoader=MyProxy.class.getClassLoader();

//第二步,获取被代理对象实现的所有接口

Class<?>[] interfaces = userDao.getClass().getInterfaces();

//第三步,获取代理对象并返回

return Proxy.newProxyInstance(classLoader,interfaces,this);

}

/**

  • 所有动态代理方法的调用,均会交由下面的invoke方法处理

  • proxy -- 被代理对象

  • method -- 将要被执行的方法信息

  • args -- 执行方法时需要的参数

*/ @Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

MyAspect aspect=new MyAspect(); //实例化切面对象

aspect.check_Permissions(); //前增强 -- 切入权限检查方法

Object obj=method.invoke(userDao,args); //目标对象(被代理对象)上调用方法,传入

参数

aspect.log(); //后增强 -- 切入记录日志方法

return obj;

}

}

  1. 创建测试类TestJDKProxy

import com.aop.demo01.MyJDKProxy; import com.aop.demo01.UserDao; import com.aop.demo01.UserDaoImpl;

public class TestJDKProxy {

public static void main(String[] args) { MyJDKProxy proxy = new MyJDKProxy(); UserDao userDao = new UserDaoImpl();

System.out.println("================代理前================"); userDao.addUser();

System.out.println(" ");

userDao.deleteUser();

userDao = (UserDao) proxy.createProxy(userDao); //为userDao生成代理对象,实

现增强

System.out.println("================代理后================"); userDao.addUser();

System.out.println(" ");

userDao.deleteUser();

}

}

运行结果如下图所示。

  1. CGLib动态代理

**JDK动态代理缺陷:**只能为接口创建代理对象

CGLib动态代理可以为类创建代理对象,采用底层的字节码技术,通过继承的方式动态创建代理对象

CGLib动态代理通过实现MethodInterceptor接口的intercept()方法完成代理CGLib动态代理已在spring核心包中集成,无需导入

CGLib动态代理实现步骤

第一步:创建代理类,此代理类要实现MethodInterceptor接口

第二步:在此代理类中创建返回目标类代理对象的方法,此方法中用Enhancer类的create()方法 目标类生成代理对象

第三步:实现MethodInterceptor接口的intercept()方法,其中完成通知的织入

  1. 示例

【示例】阅读代码,理解CGLib动态代理实现过程。

  1. 在main\java目录中,创建com.aop.demo02包
  2. 创建目标类UserDao,在该类中编写添加用户和删除用户的方法。(放com.aop.demo02包)

package com.aop.demo02;

public class UserDao { public void addUser() {

System.out.println("添加用户");

}

public void deleteUser() { System.out.println("删除用户");

}

}

  1. 创建切面类MyAspect,内容同上例**(放com.aop.demo02包)**

  2. 创建代理类MyCglibProxy,该代理类需要实现MethodInterceptor接口用于设置代理类的调用处理程序,并实现intercept()方法(放com.aop.demo02包)

package com.aop.demo02;

import org.springframework.cglib.proxy.Enhancer;

import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyCglibProxy implements MethodInterceptor {

//创建代理方法

public Object createProxy(Object target){

//创建动态对象

Enhancer enhancer = new Enhancer();

//确定需要增强的类,设置其为父类enhancer.setSuperclass(target.getClass());

//添加回调函数enhancer.setCallback(this);

//返回创建的代理类

return enhancer.create();

}

/**

  • proxy -- cglib根据指定父类生成的代理对象
  • method -- 拦截的方法
  • args -- 拦截方法的参数
  • methodProxy -- 方法的代理对象,用于执行父类的方法

*/ @Override

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

MyAspect aspect=new MyAspect(); //实例化切面对象

aspect.check_Permissions(); //前增强 -- 切入权限检查方法

Object obj=methodProxy.invokeSuper(proxy,args); //执行目标方法,并传入参数aspect.log(); //后增强 -- 切入记录日志方法

return obj;

}

}

创建测试类TestCglibProxy

import com.aop.demo01.UserDao; import com.aop.demo01.UserDaoImpl; import com.aop.demo02.MyCglibProxy;

public class TestCgllibProxy {

public static void main(String[] args) { MyCglibProxy proxy = new MyCglibProxy(); UserDao userDao = new UserDaoImpl();

System.out.println("================代理前================"); userDao.addUser();

System.out.println(" ");

userDao.deleteUser();

userDao = (UserDao) proxy.createProxy(userDao); //为userDao生成代理对象,实

现增强

System.out.println("================代理后================");

userDao.addUser(); System.out.println(" ");

userDao.deleteUser();

}

}

运行结果同上例。

** ****、基于XML的AOP实现 **

Spring AOP中的代理对象由IoC容器自动生成

在Spring配置文件中做相关配置:目标类(被代理类)的bean,切面类的bean,切面,连接点, 各类通知等

Spring AOP的配置元素如下

元素<br>描述<br>
aop:config<br>Spring AOP配置的根元素<br>
aop:aspect<br>配置切面<br>
aop:advisor<br>配置通知器<br>
aop:pointcut<br>配置切点<br>
aop:before<br>配置前置通知,在目标方法执行前实施增强,可以应用于权限管理等功能<br>
<br>aop:after<br>配置后置通知,在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能<br>
<br>aop:around<br>配置环绕方式,在目标方法执行前后实施增强,可以应用于日志、事务管理等功能<br>
<aop:after- returning><br>配置返回通知,在目标方法成功执行之后调用通知<br>
<aop:after- throwing><br>配置异常通知,在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能<br>
  1. 基于XML的Spring AOP配置实现步骤

第一步:导入相关依赖包spring-aop.jar、aopalliance.jar、aspectjweaver.jar,并引入aop约束

lib中的jar包**(必须包含下图中的jar包)**

 

引入aop约束

 

第二步:配置目标类(被代理类)的Bean**(在spring配置文件中完成) 第三步:配置切面类的Bean(在spring配置文件中完成)**

第四步:配置切面**(在spring配置文件中完成)**

使用aop:aspect元素配置切面,其属性ref指向切面类bean的id值第五步:配置切入点**(在spring配置文件中完成)**

使用aop:config元素下的子元素aop:pointcut配置切入点时,表示该切入点是全局的,它可被 多个切面共享

使用aop:aspect元素下的子元素aop:pointcut配置切入点时,表示该切入点只对当前切面有效 子元素aop:pointcut的属性有id和expression

属性名称<br>描述<br>
id<br>用于指定切入点的唯一标识<br>
<br>用于指定切入点关联的切入点表达式。示例:expression="切入点表达式"<br>
<br>**切入点表达式通用语法:**execution(modifiers-pattern?ret-type-pattern<br>
<br>declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)<br>
<br>说明:<br>
<br>modifiers-pattern:定义的目标方法的访问修饰符,如public、private等。<br>
<br>ret-type-pattern:定义的目标方法的返回值类型,如void、String等。<br>
<br>declaring-type-pattern:定义的目标方法的类路径,如<br>
<br>com.itheima.jdk.UserDaoImpl。<br>
<br>name-pattern:表示具体需要被代理的目标方法,如add()方法。<br>
<br>param-pattern:表示需要被代理目标方法包含的参数,本章示例中目标方法参数<br>
<br>都为空。<br>
<br>throws-pattern:表示需要被代理的目标方法抛出的异常类型。<br>
<br>----------------------------------------------------------------------------------------------------------------<br>
<br>--------<br>
<br>**切入点表达式常用语法:*execution( 包名..(..))<br>
expression<br>**说明:**表达式分5个部分。<br>
<br>(1)execution(): 表达式主体。<br>
<br>(2)第一个*号:方法返回类型,*号表示所有的类型。<br>
<br>(3)包名:表示需要拦截的包名。<br>
<br>(4)第二个*号:表示类名,*号表示所有的类。<br>
<br>(5)*(..):最后这个星号表示方法名,*号表示所有的方法;括号里面表示方法的参<br>
<br>数,两个句点表示任何参数 其中除了返回类型模式、方法名模式和参数模式外,<br>
<br>其它项都是可选的。<br>
<br>示例:<br>
<br>execution(public * *(..)) 匹配所有的public修饰符的方法。<br>
<br>execution(* set*(..)) 匹配所有”set”开头的方法。<br>
<br>execution(* com.xyz.service.AccountService.*(..)) 匹配AccountService 接口/类<br>
<br>的所有方法。<br>
<br>---------------------------------------------------------------------------------------------------<br>
<br>**切入点表达多个时,之间用逻辑符
<br>**示例:*execution( *.addUser(..))

第六步:配置通知

有5种常用通知:前置通知、后置通知、环绕通知、返回通知和异常通知。

使用元素aop:aspect的子元素**aop:before配置前置通知**、aop:after后置通知

aop:around配置环绕通知aop:after-returning配置返回通知aop:after-throwing

配置异常通知

各类通知元素属性均相同,具体如下。

属性<br>描述<br>
<br>pointcut<br>该属性用于指定一个切入点表达式,Spring将在匹配该表达式的连接点时织入该通知。<br>
pointcut- ref<br>该属性指定一个已经存在的切入点名称,如配置代码中的myPointCut。通常<br>pointcut和pointcut-ref两个属性只需要使用其中一个即可。<br>
method<br>该属性指定一个方法名,指定将切面Bean中的该方法转换为增强处理。<br>
<br>throwing<br>属性只对<after-throwing>元素有效,它用于指定一个形参名,异常通知方法可 以通过该形参访问目标方法所抛出的异常。<br>
<br>returning<br>属性只对<after-returning>元素有效,它用于指定一个形参名,后置通知方法 可以通过该形参访问目标方法的返回值。<br>
  1. 示例

【示例】阅读代码,理解基于XML的AOP配置过程。

  1. 在main\java目录中,创建com.aop.demo03包
  2. 导入依赖包spring-aop-5.3.12.jar、aopalliance-1.0.jar、aspectjweaver-1.8.10.jar
  3. 创建接口UserDao,编写添加、删除、修改和查询的方法。(放com.aop.demo03包)

package com.aop.demo03;

public interface UserDao { void addUser();

void deleteUser(); void updateUser(); void selectUser();

}

  1. 创建类UserDaoImpl,实现UserDao接口**(放com.aop.demo03包)**

package com.aop.demo03;

public class UserDaoImpl implements UserDao { @Override

public void addUser() { System.out.println("--添加用户");

}

@Override

public void deleteUser() { System.out.println("--删除用户");

}

@Override

public void updateUser() { System.out.println("--修改用户");

}

@Override

public void selectUser() {

System.out.println("--查询用户");

}

}

  1. 创建XmlAdvice类,用于定义通知。(此类即为切面,放com.aop.demo03包)

package com.aop.demo03;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint;

public class XmlAdvice {

//前置通知

public void before(JoinPoint point){ System.out.println(" 这 是 前 置 通 知 ..."); System.out.println("目标对象:"+point.getTarget());

System.out.println("被增强的方法:"+point.getSignature().getName());

}

//返回通知

public void afterReturning(JoinPoint point){ System.out.println("这是返回通知...(方法不抛出异常时调用)");

}

/**

  • 环绕通知要求:
  • (1)必须返回Object类型值;
  • (2)必须接收一个参数,类型为ProceedingJoinPoint
  • (3)必须throws Throwable
  • ProceedingJoinPoint -- 是JoinPoint的子接口,可以执行目标方法

*/

public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("这是环绕通知之前的部分...");

Object obj = point.proceed(); System.out.println("这是环绕通知之后的部分..."); return obj;

}

//异常通知

public void afterException(){

System.out.println("异常通知...(抛出异常时调用!)");

}

//后置通知

public void after(){ System.out.println("这是后置通知...");

}

}

创建spring的配置文件spring-config01.xml,在该文件中引入aop约束,完成AOP配置(放

resources文件夹)

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="Index of /schema/aop"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd Index of /schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<!--配置UserDao类的Bean-->

<bean id="userDao" class="com.aop.demo03.UserDaoImpl"/>

<!--配置切面类的Bean-->

<bean id="aspect" class="com.aop.demo03.XmlAdvice"/>

<!--配置Spring AOP-->

aop:config

<!--指定切入点-->

<aop:pointcut id="pointcut" expression="execution(* com.aop.demo03.UserDaoImpl.*(..))"/>

<!--指定切面-->

<aop:aspect id="aspect" ref="aspect">

<!--指定前置通知-->

<aop:before method="before" pointcut-ref="pointcut"/>

<!--指定环绕通知-->

<aop:around method="around" pointcut-ref="pointcut"/>

<!--指定返回通知-->

<aop:after-returning method="afterReturning" pointcut- ref="pointcut"/>

<!--指定异常通知-->

<aop:after-throwing method="afterException" pointcut- ref="pointcut"/>

<!--指定后置通知-->

<aop:after method="after" pointcut-ref="pointcut"/>

</aop:aspect>

</aop:config>

</beans>

创建测试类TestXML

import com.aop.demo03.UserDao;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestXml {

public static void main(String[] args) {

ApplicationContext context = new ClassPathXmlApplicationContext("spring- config01.xml");

UserDao userDao = context.getBean(UserDao.class);

userDao.addUser(); System.out.println("=========================="); userDao.deleteUser(); System.out.println("=========================="); userDao.updateUser(); System.out.println("=========================="); userDao.selectUser();

}

}

运行结果如下图所示。

 

**思考:**如果只对addUser和selectUser()两个方法增强,该怎么办?

** ****、基于注解的AOP实现 **

Spring AOP允许使用注解方式实现AOP,以简化Spring配置文件Spring AOP的注解

元素<br>描述<br>
@Aspect<br>配置切面<br>
@Pointcut<br>配置切点<br>
@Before<br>配置前置通知<br>
@After<br>配置后置通知<br>
@Around<br>配置环绕方式<br>
@AfterReturning<br>配置返回通知<br>
@AfterThrowing<br>配置异常通知<br>
  1. 基于XML的Spring AOP配置实现步骤

第一步:导入相关依赖包spring-aop.jar、aopalliance.jar、aspectjweaver.jar,并引入aop约束 第二步:创建带注解的切面类

第三步:在spring配置文件进行如下配置

引入aop约束

配置目标类的bean 配置切面类的Bean

开启@aspectj的自动代理支持

示例如下。

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="Index of /schema/aop"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd Index of /schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 注册Bean -->

<bean name="userDao" class="com.aop.demo03.UserDaoImpl"/>

<bean name="AnnoAdvice" class="com.aop.demo04.AnnoAdvice"/>

<!-- 开启@aspectj的自动代理支持 -->

aop:aspectj-autoproxy/

</beans>

  1. 示例

【示例】阅读代码,理解基于注解L的AOP配置过程。

  1. 在main\java目录中,创建com.aop.demo04包
  2. 导入依赖包spring-aop-5.3.12.jar、aopalliance-1.0.jar、aspectjweaver-1.8.10.jar (3)创建接口UserDao,内容同上例**(放com.aop.demo04包)**
  3. 创建类UserDaoImpl,实现UserDao接口,内容同上例**(放com.aop.demo04包)**

创建带注解的AnnoAdvice类,用于定义通知(此类即为切面,放com.aop.demo04包)

package com.aop.demo04;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*;

@Aspect

public class AnnoAdvice {

//切点

@Pointcut("execution(* com.aop.demo04.UserDaoImpl.*(..))") public void pointCut(){}

//前置通知

//通知的切入点:为之前定义的pointCut切点@Before("pointCut()")

public void before(JoinPoint point){ System.out.println("这是前置通知...");

}

//返回通知

//临时定义通知的切点:切入点为任何包下任何类的addUser()方法和selectUser()方法@AfterReturning("execution(* .addUser(..))||execution( *.selectUser(..))") public void afterReturning(JoinPoint point){

System.out.println("这是返回通知...(方法不抛出异常时调用)");

}

/**

  • 环绕通知要求:
  • (1)必须返回Object类型值;
  • (2)必须接收一个参数,类型为ProceedingJoinPoint
  • (3)必须throws Throwable
  • ProceedingJoinPoint -- 是JoinPoint的子接口,可以执行目标方法

*/

//临时定义通知的切点:切入点为任何包中任何类的deleteUser()方法@Around("execution(* *.deleteUser(..))")

public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("这是环绕通知之前的部分...");

Object obj = point.proceed(); System.out.println("这是环绕通知之后的部分..."); return obj;

}

//异常通知

//临时定义通知的切点:切入点为com.aop.demo03包下任何类的任何方法@AfterThrowing("execution(* com.aop.demo03..(..))") public void afterException(){

System.out.println("异常通知...(抛出异常时调用!)");

}

//后置通知

//临时定义通知的切点:切入点为任何包下任何类的updateUser()方法@After("execution(* *.updateUser(..))")

public void after(){ System.out.println("这是后置通知...");

}

}

创建spring配置文件spring-config02.xml,引入aop约束,配置目标类和切面类的bean,开启@aspectj的自动代理支持。

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

<!--配置UserDao类的Bean-->

<bean id="userDao" class="com.aop.demo04.UserDaoImpl"/>

<!--配置切面类的Bean-->

<bean id="aspect" class="com.aop.demo04.AnnoAdvice"/>

<!--开启@Aspect自动扫描支持-->

<aop:aspectj-autoproxy />

</beans>

  1. 创建试类TestAnnotation

import com.aop.demo04.UserDao;

import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestAnnotation {

public static void main(String[] args) {

ApplicationContext context = new ClassPathXmlApplicationContext("spring- config02.xml");

UserDao userDao = context.getBean(UserDao.class);

userDao.addUser(); System.out.println("=========================="); userDao.deleteUser(); System.out.println("=========================="); userDao.updateUser(); System.out.println("=========================="); userDao.selectUser();

}

}

运行结果如下图所示。

 

** ****、课后练习 **

上机:考察知识点为Spring AOP概述、Spring AOP术语、JDK动态代理、CGLib动态代理、基于XML的AOP实现、基于注解的AOP实现)

形式:单独完成题目:

代码演示CGLib动态代理的实现过程。具体要求如下:

  1. ssm_spring项目下,创建模块名称为spring-chap08-aop;
  2. 创建包名为com.aop.task01;
  3. 创建代理类名称为CglibProxy;
  4. 创建测试类名称为CglibTest; 在控制台输出如下结果:

 

  1. 代码演示基于注解的AOP的实现过程。
  2. ssm_spring项目下,创建模块名称为spring-chap08-aop;
  3. 创建包名为com.aop.task02;
  4. 创建配置文件名称为applicationContext-Anno.xml。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

TechLens

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

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

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

打赏作者

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

抵扣说明:

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

余额充值