sping aop

一、Spring的AOP编程



在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。【百度百科】


AOP的好处:在不修改源代码的情况下,可以实现功能的增强。


AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

 

而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

 

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

 

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码


AOP的思想有几种实现方式:

http://blog.csdn.net/csujiangyu/article/details/53455094

1. JDK动态代理

在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中

Java1.3引入动态代理。实现原理是为被代理的业务接口生成代理类,将AOP逻辑写入到代理类中,在运行时动态织入AOP,使用反射执行织入的逻辑.

优点:Java标准库原生支持,使用简单,无需引用额外的包。相对于静态AOP更灵活。

缺点:带代理的类必须是接口,灵活性受到一些限制;使用反射会影响一些性能。

2. 动态代码字节生成

在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中。

CGLib是动态代码字节生成的实现,它封装字节码生成工具Asm,原理是在运行期间目标字节码加载后,生成目标类的子类,将切面逻辑加入到子类中,所以使用Cglib实现AOP不需要基于接口。

优点:没有接口也可以织入,灵活性高。

缺点:扩展类的实例方法为final时,则无法进行织入。

3. 自定义类加载器

在运行前,目标加载前,将切面逻辑加到目标字节码中。

可以考虑javassist来实现。Javassist 是一个编辑字节码的框架,可以让你很简单地操作字节码。它可以在运行期定义或修改Class。使用Javassist实现AOP的原理是在字节码加载前直接修改需要切入的方法。用字符串写的代码强行插入到方法之前;


以下代码来自 kevin【非本人编写】

1.1. AOP的实现原理

1.1.1. JDK动态代理

注意:JDK动态代理只能对实现了接口的类产生代理

/**

 * Jdk的动态代理

 * @author kevin

 */

public class JdkProxy implements InvocationHandler{

 

//要代理的对象

private CustomerDao customerDao;

public JdkProxy(CustomerDaocustomerDao){

this.customerDao =customerDao;

}

/**

 * 生成代理对象的方法

 * @return

 */

public CustomerDao createProxy(){

CustomerDao proxy = (CustomerDao) Proxy.newProxyInstance(customerDao.getClass().getClassLoader(),customerDao.getClass().getInterfaces(), this);

return proxy;

}

 

/**

 * 增强的方法

 */

@Override

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

System.out.println("权限校验...");

return method.invoke(customerDao,args);

}

}

编写测试代码:

@Test

public void test1(){

CustomerDao customerDao = new CustomerDaoImpl();

JdkProxy jdkProxy = new JdkProxy(customerDao);

CustomerDao proxy = jdkProxy.createProxy();

proxy.save();

}

1.1.2. Cglib动态代理

注意:Cglib可以对没有实现接口的类产生代理,生成子类来实现功能的增强

public class CglibProxyimplements MethodInterceptor{

 

//要代理的对象

private LinkManDaolinkManDao;

public CglibProxy(LinkManDaolinkManDao) {

this.linkManDao =linkManDao;

}

public LinkManDao createProxy(){

//创建Cglib核心类

Enhancer enhancer = new Enhancer();

//设置父类

enhancer.setSuperclass(linkManDao.getClass());

//设置回调

enhancer.setCallback(this);

//生成代理

LinkManDao proxy = (LinkManDao) enhancer.create();

return proxy;

}

 

@Override

public Object intercept(Objectproxy, Methodmethod, Object[] args, MethodProxy methodProxy) throws Throwable {

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

Object obj = methodProxy.invokeSuper(proxy,args);

return obj;

}

}

编写测试代码

@Test

public void test2(){

LinkManDao linkManDao = new LinkManDao();

CglibProxy cglibProxy = new CglibProxy(linkManDao);

LinkManDao proxy = cglibProxy.createProxy();

proxy.save();

}

2. SpringAOP开发方式(xml方式)

2.1. SpringAOP中相关术语的介绍

 

2.2. SpringAOP开发入门

2.2.1. 导入AOP开发所需的包

AOP联盟的jar包:com.springsource.org.aopalliance-1.0.0.jar

Spring提供的AOPjar包:spring-aop-4.2.4.RELEASE.jar

AspectJjar包:com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

Spring整合AspectJjar包:spring-aspects-4.2.4.RELEASE.jar

 

2.2.2. 编写接口和实现类

ProductDao接口:

public interface ProductDao {

 

/**

 * 持久层:产品保存

 */

public void save();

}

ProductDaoImpl实现类:

public class ProductDaoImplimplements ProductDao {

 

@Override

public void save() {

System.out.println("持久层:产品保存...");

}

 

}

2.2.3. 配置相关类到Spring

<?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

        http://www.springframework.org/schema/context/spring-context.xsd">

  

   <bean id="productDao" class="cn.itcast.dao.impl.ProductDaoImpl"></bean>

    

</beans>

2.2.4. 编写切面类

/**

 * 自定义切面类

 * @author kevin

 */

public class MyAspectXml {

 

public void checkPrivilege(){

System.out.println("权限校验...");

}

}

2.2.5. 配置切面类

<?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

        http://www.springframework.org/schema/context/spring-context.xsd">

  

   <bean id="productDao" class="cn.itcast.dao.impl.ProductDaoImpl"></bean>

   <bean id="myAspectXml" class="cn.itcast.aspect.MyAspectXml"></bean>

</beans>

2.2.6. 进行AOP配置

<?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"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        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">

  

   <bean id="productDao" class="cn.itcast.dao.impl.ProductDaoImpl"></bean>

   <bean id="myAspectXml" class="cn.itcast.aspect.MyAspectXml"></bean>

   <!-- AOP配置 -->

   <aop:config>

   <!-- 配置切入点 -->

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.save(..))" id="pointcut1"/>

   <!-- 配置切面 -->

   <aop:aspect ref="myAspectXml">

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

   </aop:aspect>

   </aop:config>

</beans>

2.2.7. 编写测试类

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration("classpath:applicationContext.xml")

public class TestAOP {

@Autowired

private ProductDaoproductDao;

 

@Test

public void test1(){

productDao.save();

}

}

观察结果,发现save方法被增强了:

 

2.3. 切入点表达式的语法

语法:[修饰符]返回类型 包名.类名.方法名(形式参数)

常见写法:

execution(public * *(..)) 所有的public方法

execution(* set(..)) 所有set开头的方法

execution(* com.xyz.service.AccountService.*(..))    AccountService类中的所有方法

execution(* com.xyz.service.*.*(..))com.xyz.service包下所有的方法

execution(* com.xyz.service..*.*(..))    com.xyz.service包及其子包下所有的方法

2.4. SpringAOP的通知类型

2.4.1. 前置通知:在方法执行之前增强。可以获得切入点信息。

<!-- AOP配置 -->

<aop:config>

   <!-- 配置切入点 -->

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.save(..))" id="pointcut1"/>

   <!-- 配置切面 -->

   <aop:aspect ref="myAspectXml">

   <!-- 前置通知 -->

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

   </aop:aspect>

</aop:config>

 

public class MyAspectXml {

 

public void checkPrivilege(JoinPointpoint){

System.out.println("权限校验..." +point);

}

 

}

2.4.2. 后置通知:在方法执行完之后增强。可以获取返回值信息。

/**

 * 自定义切面类

 * @author kevin

 */

public class MyAspectXml {

 

public void checkPrivilege(JoinPointpoint){

System.out.println("权限校验..." +point);

}

public void afterReturn(Objectresult){

System.out.println("后置通知:" +result);

}

}

 

public class ProductDaoImplimplements ProductDao {

 

@Override

public void save() {

System.out.println("持久层:产品保存...");

}

public int delete(){

System.out.println("持久层:产品删除...");

return 100;

}

 

}

 

<!-- AOP配置 -->

   <aop:config>

   <!-- 配置切入点 -->

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.save(..))" id="pointcut1"/>

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.delete(..))" id="pointcut2"/>

   <!-- 配置切面 -->

   <aop:aspect ref="myAspectXml">

   <!-- 前置通知 -->

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

   <!-- 后置通知 -->

   <aop:after-returning method="afterReturn" pointcut-ref="pointcut2"returning="result"/>

   </aop:aspect>

   </aop:config>

 

2.4.3. 环绕通知:在方法执行前后都进行增强。可以阻止方法的执行。

public class ProductDaoImplimplements ProductDao {

 

@Override

public void save() {

System.out.println("持久层:产品保存...");

}

public int delete(){

System.out.println("持久层:产品删除...");

return 100;

}

 

@Override

public void update() {

System.out.println("持久层:产品更新");

}

 

}

 

public class MyAspectXml {

 

public void checkPrivilege(JoinPointpoint){

System.out.println("权限校验..." +point);

}

public void afterReturn(Objectresult){

System.out.println("后置通知:" +result);

}

public Object around(ProceedingJoinPointjoinpoint){

System.out.println("环绕前执行");

Object obj = null;

try {

 obj = joinpoint.proceed();

} catch (Throwablee) {

e.printStackTrace();

}

System.out.println("环绕后执行");

return obj;

}

}

 

<!-- AOP配置 -->

<aop:config>

   <!-- 配置切入点 -->

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.save(..))" id="pointcut1"/>

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.delete(..))" id="pointcut2"/>

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.update(..))" id="pointcut3"/>

   <!-- 配置切面 -->

   <aop:aspect ref="myAspectXml">

   <!-- 前置通知 -->

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

   <!-- 后置通知 -->

   <aop:after-returning method="afterReturn" pointcut-ref="pointcut2" returning="result"/>

   <!-- 环绕通知 -->

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

   </aop:aspect>

</aop:config>

2.4.4. 异常抛出通知:当发生异常之后增强,可以获取异常信息。

public class ProductDaoImplimplements ProductDao {

 

@Override

public void save() {

System.out.println("持久层:产品保存...");

}

public int delete(){

System.out.println("持久层:产品删除...");

return 100;

}

 

@Override

public void update() {

System.out.println("持久层:产品更新");

}

 

@Override

public void find() {

System.out.println("持久层:查询");

int i = 10/0;

}

 

}

 

public class MyAspectXml {

 

public void checkPrivilege(JoinPointpoint){

System.out.println("权限校验..." +point);

}

public void afterReturn(Objectresult){

System.out.println("后置通知:" +result);

}

public Object around(ProceedingJoinPointjoinpoint){

System.out.println("环绕前执行");

Object obj = null;

try {

 obj =joinpoint.proceed();

} catch (Throwablee) {

e.printStackTrace();

}

System.out.println("环绕后执行");

return obj;

}

public void afterThrowing(Exceptionex){

System.out.println("抛出异常通知:" +ex.getMessage());

}

}

 

 

<!-- AOP配置 -->

 <aop:config>

   <!-- 配置切入点 -->

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.save(..))" id="pointcut1"/>

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.delete(..))" id="pointcut2"/>

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.update(..))" id="pointcut3"/>

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.find(..))" id="pointcut4"/>

   <!-- 配置切面 -->

   <aop:aspect ref="myAspectXml">

   <!-- 前置通知 -->

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

   <!-- 后置通知 -->

   <aop:after-returning method="afterReturn" pointcut-ref="pointcut2" returning="result"/>

   <!-- 环绕通知 -->

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

   <!-- 抛出异常通知 -->

   <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/>

   </aop:aspect>

</aop:config>

2.4.5. 最终通知:不管是否有异常,都会执行的

public class MyAspectXml {

 

public void checkPrivilege(JoinPointpoint){

System.out.println("权限校验..." +point);

}

public void afterReturn(Objectresult){

System.out.println("后置通知:" +result);

}

public Object around(ProceedingJoinPointjoinpoint){

System.out.println("环绕前执行");

Object obj = null;

try {

 obj =joinpoint.proceed();

} catch (Throwablee) {

e.printStackTrace();

}

System.out.println("环绕后执行");

return obj;

}

public void afterThrowing(Exceptionex){

System.out.println("抛出异常通知:" +ex.getMessage());

}

public void after(){

System.out.println("最终通知");

}

}

 

<!-- AOP配置 -->

   <aop:config>

   <!-- 配置切入点 -->

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.save(..))" id="pointcut1"/>

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.delete(..))" id="pointcut2"/>

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.update(..))" id="pointcut3"/>

   <aop:pointcut expression="execution(* cn.itcast.dao.impl.ProductDaoImpl.find(..))" id="pointcut4"/>

   <!-- 配置切面 -->

   <aop:aspect ref="myAspectXml">

   <!-- 前置通知 -->

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

   <!-- 后置通知 -->

   <aop:after-returning method="afterReturn" pointcut-ref="pointcut2" returning="result"/>

   <!-- 环绕通知 -->

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

   <!-- 抛出异常通知 -->

   <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/>

   <!-- 最终通知 -->

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

   </aop:aspect>

   </aop:config>

最终通知和后置通知的区别:最终通知,不管异常与否,都执行;而后置通知在异常时不执行。

3. Spring注解形式的AOP入门

3.1. 创建工程,引入jar包,创建核心配置文件

 

<?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="http://www.springframework.org/schema/aop"

    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/aop

        http://www.springframework.org/schema/aop/spring-aop.xsd

        http://www.springframework.org/schema/context

        http://www.springframework.org/schema/context/spring-context.xsd">

        

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

        <!-- 开启自动代理注解 -->

       <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

        

</beans>

3.2. 创建接口和实现类

public interface ProductDao {

 

/**

 * 持久层:产品保存

 */

public void save();

public int delete();

public void update();

public void find();

}

 

@Repository("productDao")

public class ProductDaoImplimplements ProductDao {

 

@Override

public void save() {

System.out.println("持久层:产品保存...");

}

public int delete(){

System.out.println("持久层:产品删除...");

return 100;

}

 

@Override

public void update() {

System.out.println("持久层:产品更新");

}

 

@Override

public void find() {

System.out.println("持久层:查询");

}

 

}

3.3. 编写切面类

@Component("myAspectAnnotation")

@Aspect

public class MyAspect {

 

@Before("execution(* cn.itcast.dao.impl.ProductDaoImpl.save(..))")

public void checkPrivilege(JoinPointjoinPoint){

System.out.println("权限校验..." +joinPoint.toString());

}

 

}

提示:此处的切面类可以不取id.

4. SpringAOP中注解通知

4.1. 前置通知

/**

 * 前置通知

 * @param joinPoint

 */

@Before("execution(* cn.itcast.dao.impl.ProductDaoImpl.save(..))")

public void checkPrivilege(JoinPointjoinPoint){

System.out.println("权限校验..." +joinPoint.toString());

}

4.2. 后置通知

@Aspect

public class MyAspect {

 

/**

 * 前置通知

 * @param joinPoint

 */

@Before("execution(* cn.itcast.dao.impl.ProductDaoImpl.save(..))")

public void checkPrivilege(JoinPointjoinPoint){

System.out.println("权限校验..." +joinPoint.toString());

}

 

@AfterReturning(value="execution(* cn.itcast.dao.impl.ProductDaoImpl.delete(..))",returning="result")

public void afterReturning(Objectresult){

System.out.println("后置通知:" +result);

}

}

4.3. 环绕通知

@Around("execution(* cn.itcast.dao.impl.ProductDaoImpl.update(..))")

public Object after(ProceedingJoinPointjoinpoint) throws Throwable{

System.out.println("环绕通知前增强");

Object obj = joinpoint.proceed();

System.out.println("环绕通知后增强");

return obj;

}

4.4. 异常通知

@AfterThrowing(value="execution(* cn.itcast.dao.impl.ProductDaoImpl.find(..))",throwing="ex")

public void afterThrowing(Exceptionex){

System.out.println("抛出异常通知");

}

 

4.5. 最终通知

@After("execution(* cn.itcast.dao.impl.ProductDaoImpl.find(..))")

public void after(){

System.out.println("最终通知");

}

4.6. PointCut注解(了解)

作用:用于定义切入点表达式的一个注解。

@Component("myAspectAnnotation")

@Aspect

public class MyAspect {

 

/**

 * 前置通知

 * @param joinPoint

 */

@Before("execution(* cn.itcast.dao.impl.ProductDaoImpl.save(..))")

public void checkPrivilege(JoinPointjoinPoint){

System.out.println("权限校验..." +joinPoint.toString());

}

@AfterReturning(value="execution(* cn.itcast.dao.impl.ProductDaoImpl.delete(..))",returning="result")

public void afterReturning(Objectresult){

System.out.println("后置通知:" +result);

}

@Around("execution(* cn.itcast.dao.impl.ProductDaoImpl.update(..))")

public Object aroung(ProceedingJoinPointjoinpoint) throws Throwable{

System.out.println("环绕通知前增强");

Object obj = joinpoint.proceed();

System.out.println("环绕通知后增强");

return obj;

}

//@AfterThrowing(value="execution(* cn.itcast.dao.impl.ProductDaoImpl.find(..))",throwing="ex")

@AfterThrowing(value="MyAspect.pointcut()",throwing="ex")

public void afterThrowing(Exceptionex){

System.out.println("抛出异常通知");

}

//@After("execution(* cn.itcast.dao.impl.ProductDaoImpl.find(..))")

@After("MyAspect.pointcut()")

public void after(){

System.out.println("最终通知");

}

@Pointcut("execution(* cn.itcast.dao.impl.ProductDaoImpl.find(..))")

public void pointcut(){

}

}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值