Spring5之AOP(含底层原理)

AOP

AOP 概述

  • AOP:面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  • 通俗理解:不使用修改源代码的方式,在主干功能当中添加新功能(可结合下图进行理解)

在这里插入图片描述

AOP 底层原理

AOP 底层使用了动态代理

有两种情况的动态代理

  1. 有接口的情况,使用 JDK 动态代理(创建接口实现类的代理对象,增强类的方法)

    在这里插入图片描述

  2. 没有接口的情况,使用 CGLIB 动态代理(创建子类的代理对象,增强类的方法)

    在这里插入图片描述

AOP 动态代理实现

JDK 动态代理
  • 调用 Proxy 类下的 newProxyInstans 方法

    该方法中有三个参数:
    第一个参数:类加载器
    第二个参数:增强方法所在的类,这个类实现接口,支持多个接口
    第三个参数:实现这个接口 InvocationHandler,创建代理对象,写增强的方法
    
  • JDK 动态代理代码实现

    第一步:创建接口,定义方法

    package com.laoyang.spring.dao;
    
    public interface UserDao {
        public int add(int i, int j);
    
        public void update(String id);
    }
    

    第二步:创建接口实现类,实现方法

    package com.laoyang.spring.dao;
    
    public class UserDaoImpl implements UserDao {
        @Override
        public int add(int i, int j) {
            System.out.println("add 执行了...");
            return i + j;
        }
    
        @Override
        public void update(String id) {
            System.out.println("update 执行了...");
            System.out.println(id);
        }
    }
    

    使用 Proxy 类创建接口代理对象

    package com.laoyang.spring.dao;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Arrays;
    
    /**
     * @ClassName JDKProxy
     * @Description: JDK动态代理
     * @Author Laoyang
     * @Date 2021/12/18 22:07
     */
    public class JDKProxy  {
        public static void main(String[] args) {
            // 创建接口实现类代理对象
            Class[] interfaces = {UserDao.class};
    
            // 方式一:使用匿名类
    //        Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
    //            @Override
    //            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //                return null;
    //            }
    //        });
    
            // 方式二:使用自定义类
            UserDaoImpl userDao = new UserDaoImpl();
            UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
            int add = dao.add(12, 34);
            System.out.println("sum = " + add);
    
            dao.update("12138");
            
            /*
            最后的打印效果:
                方法之前执行...add:传递的参数=[12, 34]
                add被增强了
                add 执行了...
                方法之后执行...com.laoyang.spring.dao.UserDaoImpl@355da254
                sum = 46
                方法之前执行...update:传递的参数=[12138]
                update被增强了
                update 执行了...
                12138
                方法之后执行...com.laoyang.spring.dao.UserDaoImpl@355da254
             */
        }
    }
    
    /**
     * 创建代理对象
     */
    class UserDaoProxy implements InvocationHandler {
        // 创建的是谁的代理对象,就把谁传递过来(通过有参构造器进行传递)
        private Object object;
        public UserDaoProxy(Object object) {
            this.object = object;
        }
    
        /**
         * 增强的逻辑代码
         * @param proxy 代理对象
         * @param method 方法
         * @param args 参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            /*
             在方法之前的处理
             method.getName():获取当前要执行的方法名
             */
            System.out.println("方法之前执行..." + method.getName() + ":传递的参数=" + Arrays.toString(args));
    
            // 被增强的方法执行
            Object res = null;
            // 可以写个判断,判断一下哪些方法需要增强,哪些不需要(也可以不加,根据需求而定)
            if ("update".equals(method.getName())) {
                System.out.println("update被增强了");
                res = method.invoke(object, args);
            }
            if ("add".equals(method.getName())) {
                System.out.println("add被增强了");
                res = method.invoke(object, args);
            }
    
            // 在方法之后的处理
            System.out.println("方法之后执行..." + object);
            return res;
        }
    }
    

AOP 操作术语

主要的几个术语

  1. 连接点:类里面哪些方法可以被增强,这些可以被增强的方法就是连接点

  2. 切入点:实际被真正增强的方法,就称为切入点

  3. 通知(增强)

    ① 实际增强的逻辑部分就称为通知(增强)

    ② 通知有很多种类型:

    (1)前置通知
    (2)后置通知
    (3)环绕通知
    (4)异常通知
    (5)最终通知(类似 finally)

  4. 切面:把通知应用到切入点的过程,就被称为切面

图文理解:

在这里插入图片描述

AOP 操作

了解

  • Spring 框架一般基于 AspetJ 实现 AOP 操作
  • AspetJ:AspetJ 不是 Spring 的组成部分,而是独立的 AOP 框架,一般把 AspetJ 和 Spring 框架一起使用,进行 AOP 操作。
  • 基于 AspetJ 实现 AOP 操作的两种方式:
    • 基于 xml 配置文件实现
    • 基于注解方式实现(推荐)
需要用到的资源

一、在项目工程中引入 AOP 相关依赖

在这里插入图片描述

spring-aspects-5.2.13.RELEASE.jar 可以在下载的 Spring/libs 目录下找到,而其它三个大家可以在网上找到

二、切入点表达式

  • 切入表达式的作用:让 AOP 知道对哪个类里面的那个方法进行增强

  • 语法结构execution([权限修饰符] [返回类型] [方法名称]([参数列表]))

常见案例

案例一:对 com.laoyang.spring.BookDao 类里面的 add 进行增强
execution(* com.laoyang.spring.BookDao.add(..))

说明:
1、权限修饰符可以是 public、private 等,不过一般都写 *,因为 * 表示所有修饰符
2、返回值是可选的
3、add(..) 里面的 .. 是参数
案例二:对 com.laoyang.spring.BookDao 类里面的所有方法进行增强
execution(* com.laoyang.spring.BookDao.*(..))

说明:
1、com.laoyang.spring.BookDao.*(..) 表示 BookDao 中所有的方法
案例三:对 com.laoyang.spring 包下所有类里面的所有方法进行增强
execution(* com.laoyang.spring.*.*(..))

说明:
1、com.laoyang.spring.*.*(..) 表示 spring 包下的所有类和类中所有的方法

基于 AspectJ 注解实现

基本使用(以前置通知为例)
  • 创建类,在类里面定义方法
package com.laoyang.spring.aopanno;

/**
 * @ClassName User
 * @Description: 被增强的类
 * @Author Laoyang
 * @Date 2021/12/19 17:04
 */
public class User {
    public void add() {
        System.out.println("add....");
    }
}
  • 创建增强类(编写增强逻辑)

    在增强类中创建方法,让不同方法代表不同通知类型

package com.laoyang.spring.aopanno;

/**
 * @ClassName UserProxy
 * @Description: 增强类
 * @Author Laoyang
 * @Date 2021/12/19 21:05
 */
public class UserProxy {
    /**
     * 前置通知(增强前执行)
     */
    public void before() {
        System.out.println("before...");
    }
}
  • 进行通知的配置

    1、在 Spring 配置文件中,开启注解扫描

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 引入 context 和 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/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"
    >
        <!-- 开启注解扫描 -->
        <context:component-scan base-package="com.laoyang.spring.aopanno" />
    </beans>
    

    2、使用 @Component 注解创建 User 和 UserProxy 对象

    package com.laoyang.spring.aopanno;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class User {
        public void add() {
            System.out.println("add....");
        }
    }
    
    package com.laoyang.spring.aopanno;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    @Component
    public class UserProxy {
        /**
         * 前置通知(增强前执行)
         */
        public void before() {
            System.out.println("before...");
        }
    }
    

    3、在增强类上添加注解 @Aspect

    package com.laoyang.spring.aopanno;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class UserProxy {
        /**
         * 前置通知(增强前执行)
         */
        public void before() {
            System.out.println("before...");
        }
    }
    

    4、在 Spring 配置文件中开启 Aspect 生成代理对象

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 引入 context 和 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/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"
    >
        <!-- 开启注解扫描 -->
        <context:component-scan base-package="com.laoyang.spring.aopanno" />
    
        <!-- 开启 Aspect 生成代理对象 -->
        <aop:aspectj-autoproxy />
    </beans>
    
  • 配置前置通知

    在增强类中的通知方法上添加通知类型注解,使用切入点表达式配置

package com.laoyang.spring.aopanno;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class UserProxy {
    /**
     * 前置通知(增强前执行)
     * @Before: 表示作为前置通知
     */
    @Before(value = "execution(* com.laoyang.spring.aopanno.User.add(..))")
    public void before() {
        System.out.println("before...");
    }
}

这里我们先暂时只使用前置通知查看对应的效果,后面在演示其它通知的执行效果

  • 测试效果
package com.laoyang.spring.test;

import com.laoyang.spring.aopanno.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {
    @Test
    public void testAdd() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        User user = context.getBean("user", User.class);

        /*
        正确执行结果为:
            before...
            add...
         */
        user.add();
    }
}
其它通知类型的使用
  • 配置不同类型的通知
package com.laoyang.spring.aopanno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class UserProxy {
    /**
     * 前置通知(增强前执行)
     * @Before: 表示作为前置通知
     */
    @Before(value = "execution(* com.laoyang.spring.aopanno.User.add(..))")
    public void before() {
        System.out.println("before...");
    }

    /**
     * 后置通知(也可称为 返回通知)
     * 说明:增强方法后执行,如果增强方法出现异常,则不执行
     */
    @AfterReturning(value = "execution(* com.laoyang.spring.aopanno.User.add(..))")
    public void afterReturning() {
        System.out.println("afterReturning...");
    }

    /**
     * 最终通知
     * 说明:增强方法后执行,且不管增强方法有没有异常,都会被执行
     */
    @After(value = "execution(* com.laoyang.spring.aopanno.User.add(..))")
    public void after() {
        System.out.println("after...");
    }

    /**
     * 异常通知
     * 说明:当增强方法出现异常的时候才会被执行
     */
    @AfterThrowing(value = "execution(* com.laoyang.spring.aopanno.User.add(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing...");
    }

    /**
     * 环绕通知
     * 说明:在增强方法执行前和执行后都会被执行
     */
    @Around(value = "execution(* com.laoyang.spring.aopanno.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕之前...");

        /*
         被增强的方法
         proceedingJoinPoint.proceed():执行被增强的方法
         */
        proceedingJoinPoint.proceed();

        System.out.println("环绕之后...");
    }
}
  • 测试效果
package com.laoyang.spring.test;

import com.laoyang.spring.aopanno.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {
    @Test
    public void testAdd() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        User user = context.getBean("user", User.class);

        /*
        正确执行结果为:
            环绕之前...
            before...
            add....
            afterReturning...
            after...
            环绕之后...
         */
        user.add();
    }
}

测试 异常通知 的时候可以在 User 的 add() 方法(也就是被增强方法)中,手动制造一个异常,比如 int i = 10/0 之类的,然后在测试查看对应的效果(报错后部分通知就不会再被执行了)

如何将相同的切入点抽取出来?
  • 虽然每个通知使用的注解不一样,但是后面的增强方法路径地址是一样的(这样比较冗余),所以接下来为了优化,我们可以将方法路径地址抽取出来,单做一个公共的资源给各个注解使用。
package com.laoyang.spring.aopanno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class UserProxy {
    /**
     * 将相同的切入点进行抽取
     * @Pointcut: 切入点注解
     */
    @Pointcut(value = "execution(* com.laoyang.spring.aopanno.User.add(..))")
    public void pointDemo() {
    }

    /**
     * 前置通知(增强前执行)
     * @Before: 表示作为前置通知
     */
    @Before(value = "pointDemo()")
    public void before() {
        System.out.println("before...");
    }
}

抽取完可自行进行测试查看对应的效果

如果有多个增强类对同一个方法进行增强,如何设置优先级?
  • 可以在增强类上添加一个注解:@Order(数字类型值),数字类型值越小,优先级就越高。

原增强类

package com.laoyang.spring.aopanno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Order(1)
public class UserProxy {
    /**
     * 将相同的切入点进行抽取
     * @Pointcut: 切入点注解
     */
    @Pointcut(value = "execution(* com.laoyang.spring.aopanno.User.add(..))")
    public void pointDemo() {

    }

    /**
     * 前置通知(增强前执行)
     * @Before: 表示作为前置通知
     */
    @Before(value = "pointDemo()")
    public void before() {
        System.out.println("before...");
    }
    
    // 为了节约空间...此处省略其它的通知配置
}

新增强类

package com.laoyang.spring.aopanno;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Order(2)
public class PersonProxy {
    /**
     * 前置通知(增强前执行)
     * @Before: 表示作为前置通知
     */
    @Before(value = "execution(* com.laoyang.spring.aopanno.User.add(..))")
    public void before() {
        System.out.println("PersonProxy before...");
    }
}

因为我执行了多次,一直都是新增强类的优先级比较高,所以我就设置它的 @Order > 原增强类的 @Order 了,这样就能看到先执行原增强类,在执行新增强类了(如果大家是原增强类的优先级比较高,那么就可以把原增强类的 @Order 设置大一点,这样就会先去执行 新增强类了)

测试效果

package com.laoyang.spring.test;

import com.laoyang.spring.aopanno.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {
    @Test
    public void testAdd() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        User user = context.getBean("user", User.class);

        /*
        正确执行结果为:
            环绕之前...
            before...
            PersonProxy before...
            add....
            afterReturning...
            after...
            环绕之后...
         */
        user.add();
    }
}

完全注解开发

  • 将配置文件中的配置注释掉

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 引入 context 和 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/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"
    >
    
        <!-- 开启注解扫描 -->
    <!--    <context:component-scan base-package="com.laoyang.spring.aopanno" />-->
    
        <!-- 开启 Aspect 生成代理对象 -->
    <!--    <aop:aspectj-autoproxy />-->
    </beans>
    
  • 使用配置类替代 xml 配置文件

    package com.laoyang.spring.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    /**
     * @ClassName AopConfig
     * @Description: 配置类
     * @Author Laoyang
     * @Date 2021/12/20 11:23
     */
    @Configuration
    @ComponentScan(basePackages = {"com.laoyang.spring"})
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class AopConfig {
        /*
        @Configuration:表示当前类是配置类
    
        @ComponentScan(basePackages = {"com.laoyang.spring"}):开启组件扫描
        > 该注解相当于配置文件中的:<context:component-scan base-package="com.laoyang.spring" />
    
        @EnableAspectJAutoProxy:默认为 false,所以需要手动设置
        > 该注解就相当于配置文件中的:<aop:aspectj-autoproxy />(作用:开启 Aspect 生成代理对象)
         */
    }
    
  • 测试效果

    package com.laoyang.spring.test;
    
    import com.laoyang.spring.aopanno.User;
    import com.laoyang.spring.aopxml.Book;
    import com.laoyang.spring.config.AopConfig;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class AOPTest {
        @Test
        public void testConfig() {
            ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
            User user = context.getBean("user", User.class);
            user.add();
            
            /*
            正确执行结果为:
                环绕之前...
                before...
                PersonProxy before...
                add....
                afterReturning...
                after...
                环绕之后...
             */
        }
    }
    

AsoectJ 配置文件

实现步骤

1、创建两个类,一个增强类,一个被增强类,创建方法

2、在 Spring 配置文件中创建两个类对象

3、在 Spring 配置文件中配置切入点

具体实现
  • 创建两个类,一个增强类,一个被增强类,并创建需要增强的方法

    package com.laoyang.spring.aopxml;
    
    /**
     * @ClassName Book
     * @Description: 被增强类
     * @Author Laoyang
     * @Date 2021/12/20 11:05
     */
    public class Book {
        public void buy() {
            System.out.println("buy...");
        }
    }
    
    package com.laoyang.spring.aopxml;
    
    /**
     * @ClassName BookProxy
     * @Description: 增强类
     * @Author Laoyang
     * @Date 2021/12/20 11:06
     */
    public class BookProxy {
        public void before() {
            System.out.println("before...");
        }
    }
    
  • 在 Spring 配置文件中创建两个类对象

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 引入 aop 名称空间 -->
    <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"
           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
    ">
        <!-- 创建对象 -->
        <bean id="book" class="com.laoyang.spring.aopxml.Book" />
    
        <bean id="bookProxy" class="com.laoyang.spring.aopxml.BookProxy" />
    </beans>
    
  • 在 Spring 配置文件中配置切入点

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 引入 aop 名称空间 -->
    <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"
           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
    ">
        <!-- 创建对象 -->
        <bean id="book" class="com.laoyang.spring.aopxml.Book" />
    
        <bean id="bookProxy" class="com.laoyang.spring.aopxml.BookProxy" />
    
        <!-- 配置 AOP 增强 -->
        <aop:config>
            <!-- 切入点 -->
            <aop:pointcut id="p" expression="execution(* com.laoyang.spring.aopxml.Book.buy(..))"/>
    
            <!-- 配置切面 -->
            <aop:aspect ref="bookProxy">
                <!--
                    增强作用在具体的方法上
                    aop:*:可以在 * 处设置对应的通知类型,这里使用的是前置通知
                    method:增强方法
                    pointcut-ref:被增强的方法
                 -->
                <aop:before method="before" pointcut-ref="p" />
            </aop:aspect>
        </aop:config>
    </beans>
    
  • 测试效果

    package com.laoyang.spring.test;
    
    import com.laoyang.spring.aopanno.User;
    import com.laoyang.spring.aopxml.Book;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class AOPTest {
        @Test
        public void testBefore() {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
            Book book = context.getBean("book", Book.class);
    
            book.buy();
             /*
              正确执行结果为:
                before...
                buy...
            */
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值