Spring学习笔记

Spring 的优点

  • Spring是一个免费的开源的框架(容器)
  • Spring是一个轻量级,非入侵的框架
  • 控制反转(IOC)面向切面编程(AOP)
  • 支持事务的处理,对框架整合的支持

总结:Spring就是一个轻量级的控制反转(IOC)和面向切面(AOP)的框架

控制反转(IOC)

控制反转IOC是一种设计思想,DI(依赖注入)是实现IOC的一种方法

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,其实现方法是IOC依赖注入

IOC创建对象的方式

  1. 使用无参构造创建对象,默认
  2. 使用有参对象创建对象
    • 下标赋值
      <bean id="user" class="com.bin.pojo.User">
          <constructor-arg index="0" value="chen"/>
          <constructor-arg index="1" value="18"/>
      </bean>
      
    • 类型(不建议使用)
      <bean id="user" class="com.bin.pojo.User">
          <constructor-arg type="java.lang.String" value="chen"/>
          <constructor-arg type="int" value="18"/>
      </bean> 
      
    • 直接通过参数名来设置
      <bean id="user" class="com.bin.pojo.User">
          <constructor-arg name="name" value="chen"/>
          <constructor-arg name="age" value="18"/>
      </bean>
      

总结:在配置文件加载的时候,容器中管理的对象就已经初始化了

Spring配置

  1. 别名

    <alias name="user2" alias="user"/>
    
  2. Bean的配置

    <!--name也是别名,且可以取多个-->
    <bean id="user1" class="com.bin.pojo.User" name="us,user3 u">
    <property name="name" value="chena"/>
    </bean>
    
  3. import
    将多个xml配置文件并入到当前配置文件,如果有多个bean(id相同)相同只匹配一个

依赖注入

构造器注入

见前面

set方式注入

  • 依赖注入:Set注入
    • 依赖:bean对象的创建依赖于容器
    • 注入:bean对象中的所有属性,由容器来注入
    <!--普通值注入-->
    <property name="name" value="xiaozhiming"/>
    
    <!--bean注入-->
    <property name="address" ref="address"/>
    
    <!--数组-->
    <property name="books">
        <array>
            <value>红楼梦</value>
            <value>西游记</value>
            <value>水浒传</value>
        </array>
    </property>
    
    <!--Properties-->
    <property name="info">
        <props>
            <prop key="driver">dfsfsd</prop>
            <prop key="url">localhost:3306</prop>
            <prop key="username">root</prop>
            <prop key="password">123456</prop>
        </props>
    </property>
    

扩展方式注入

```xml
<!--p命名空间注入,可以直接注入属性的值:property-->
<bean id="user1" class="com.bin.pojo.User" p:age="18" p:name="chen1"/>
<!--c命名空间注入,通过构造器注入:construct-args-->
<bean id="user2" class="com.bin.pojo.User" c:age="20" c:name="chen2"/>
```
两种方式都需要导入依赖
```xml
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
```

bean的作用域

```
singleton	(默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。
prototype	将单个 bean 定义的作用域限定为任意数量的对象实例。
request	将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext中有效。
session	将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
application	将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
websocket	将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
```

单例模式(spring默认)
```xml
<bean id="user2" class="com.chai.pojo.User" scope="singleton"/>
```

原型模式
每次从容器中get一个bean时都会新创建一个对象
```xml
<bean id="user2" class="com.chai.pojo.User" scope="prototype"/>
```

bean的自动装配

  • 自动装配是spring满足bean依赖的一种方式
  • spring会在上下文中自动寻找,并自动给bean自动装配属性

在Spring中有三种装配的方式

  1. 在xml中显式地配置
  2. 在java中显式配置
  3. 隐式的自动装配bean

byName自动装配

<!--自动在容器上下文查找和对象set方法名后面的值对应的bean id-->
<bean id="cat" class="com.bin.pojo.Cat"/>
<bean id="dog" class="com.bin.pojo.Dog"/>
<bean id="person" class="com.bin.pojo.Person" autowire="byName">
    <property name="name" value="chai"/>
</bean>

byType自动装配

<!--自动在容器上下文查找和对象set方法的参数类型对应的bean,且被引用的bean可不写id-->
<bean class="com.bin.pojo.Cat"/>
<bean class="com.bin.pojo.Dog"/>
<bean id="person" class="com.bin.pojo.Person" autowire="byType">
    <property name="name" value="chai"/>
</bean>

小结:

  • byName,需要保证bean的id唯一,并且要与注入的set方法名set的后面值一致
    大小写遵循驼峰命名法,若开头字母连续大写,那这些字母不需要转小写(完全照写)
    若开头字母大写第二个字母小写,则首字母改成小写,后续字母大小写不变
  • byType,想要保住所有bean的class唯一,并且bean要和set方法参数的类型要一致

使用注解实现自动装配

导入约束

context约束
配置注解的支持

<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/cotext/spring-context.xsd">
    <!--⭐开启注解支持-->
    <context:annotation-config/>
</beans>

@Autowired

默认通过byType进行装配

  • 在属性或者set方法上使用
  • 可以不写set方法,前提ICO(spring)容器中存在符合的bean
  • ICO容器中存在多个相同类型的bean时,通过byName进行装配,默认匹配与属性名相同的bean id
  • 若需要通过byName自动装配与属性名不同的bean id需要使用@Qualifier(value = “”)注解

自动装配中查找符合的bean:

  • 在使用@Autowired时,首先在容器中查询对应类型的bean
  • 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据(byType)
  • 如果查询的结果不止一个,那么@Autowired会根据名称来查找(byName)
  • 如果查询的结果为空,那么会抛出异常。解决方法时,使用required=false(但可能会引起空指针)
  • 多个同类型的bean时,可以和@Qualifier(value = “”)注解搭配使用,可以匹配自定义bean的id
public class Person {
    private String name;
    //表示自动装配没找到bean对象时不报错,且赋值为null
    @Autowired(required = false)
    private Cat cat;
    @Autowired
    //和@Autowired配合使用,可以指定匹配bean的id
    @Qualifier(value = "dog1")
    private Dog dog;
}

@nullable:可以标注在方法、字段、参数之上,表示对应的值可以为空

@Resource

public class Person {
    private String name;
    //如果required属性定义为false,说明这个对象可以为null
    @Resource(name = "cat11")
    private Cat cat;
    @Resource
    private Dog dog;
}

小结:
@Resource和@Autowried的区别

  • 都是用来自动装配的,都可以放在属性字段上
  • @Autowried通过byType的方式实现,而且必须要求这个对象存在(常用)
  • @Resource默认通过byname的方式实现,如果找不到名字,则通过byType实现,如果两个都找不到的情况,就报错
  • 执行顺序不同:@AutoWried通过byType的方式实现。@Resource默认通过byName的方式实现。

使用注解开发

xml文件

需要开启注解支持和组件扫描器

组件扫描器:指定注解所在的包名,让框架找到注解,按照注解的功能,创建对象,赋值属性

<!--声明组件扫描器(context:component),指定包下的注解就会生效-->
<!--base-package:指定注解在项目中的包名,框架会扫描这个包和子包中所有类的注解,按照注解的功能,创建对象,赋值属性。-->
<context:component-scan base-package="com.bin"/>
<!--开启注解支持-->
<context:annotation-config/>

属性如何注入

@Value("")注解可以给属性赋值

@Component注解是创建类的对象

bean 的id是value值,若没有赋值默认是类名首字母小写(遵循驼峰命名法)

//@Component创建类的对象,默认单列
@Component
public class User {
    @Value("chai")
    private String name;
}

衍生的注解

@Component有几个衍生注解,在web开发中会按照mvc三层架构分层

@Repository dao层
@Service service层
@Controller web层
与@Component都是创建对象的注解,多了一些特定的功能

自动装配注解

@Autowired spring提供自动装配

默认byType

指定与属性名不同的bean id时,需要使用@Qualifier(value = “ “)注解

@Resource jdk提供的自动装配

可以给name赋值,指向指定id的bean

作用域的注解

使用@Scope(“ “)注解设置作用域的范围

@Component
@Scope("prototype")    //设置作用域的范围
public class User {
    @Value("chen")
    private String name;
}

小结

  • 注解
    优点:方便、直观、高效(代码少,没有配置文件的书写那么复杂)
    缺点:以硬编码的方式写入到 Java 代码中,修改时需要重新编译代码的
    xml
  • 优点:配置和代码是分离的 在xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载
    缺点是:编写麻烦,效率低,大型项目过于复杂

使用java的方式配置Spring

可以不使用spring的xml配置,用java的形式来实现(配置用java类实现)

  • 使用配置类+bean的方式创建对象
    ChenConfig这个类就相当于xml配置文件
    配置类中的方法加上@bean注解就是配置类中的bean,其中方法名为id,返回类型为class
@Configuration
public class ChenConfig {
    @Bean
    public User abcuser(){
        return new User();
    }
}
  • 使用包扫描+配置类的方式创建对象
    ChenConfig这个类就相当于xml配置文件
    @ComponentScan(“com.bin.pojo”)扫描包内的类以及子包中的类
    在别的类上加上@Component()注解,该类就可以被创建对象
@Configuration
@ComponentScan("com.bin.pojo")
public class ChenConfig {}
@Component()
public class User {
    @Value("chen123234")
    private String name;
}
  • 引入其他配置类
    使用注解@Import(BinConfig.class)引入其他的配置类
@Configuration
@Import(BinConfig.class)
public class ChenConfig {}

静态代理

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决
    真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,一般会做一些附属操作(增加功能)
  • 客户:访问代理对象的人

代理模式的好处

可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
公共业务也就交给了代理角色,实现了业务的分工
公共业务发生拓展的时候,方便集中管理
缺点:

一个真实角色就会产生一个代理角色,开发效率就变低了

动态代理

动态代理和静态代理角色一样
动态代理的代理类是动态生成的,不是直接写好的
动态代理分为两大类:1.基于接口的动态代理 2.基于类的动态代理
基于接口——JDK的动态代理【使用这个】
基于类:cglib
java字节码实现:javalist
需要了解两个类,Proxy,InvocationHandler

动态代理的好处:

可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
公共业务也就交给了代理角色,实现了业务的分工
公共业务发生拓展的时候,方便集中管理
一个动态代理类代理的是一个接口,一般就是对应的一类业务
一个动态代理类可以代理多个类,只要实现了同一个接口即可
代理类:

public class ProxyinvocationHandler implements InvocationHandler {
    //需要代理的目标
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }
    //动态生成代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Logger();
        method.invoke(target,args);
        shouZu();
        return null;
    }
    private void Logger(){
        System.out.println(new Date() + "出租房子");
    }
    private void shouZu(){
        System.out.println("最总房租为***元");
    }
}

测试:

@Test
public void test(){
    ProxyinvocationHandler handler = new ProxyinvocationHandler();
    //注入被代理的对象
    handler.setTarget(new Host());
    //返回代理对象
    Rent proxy = (Rent) handler.getProxy();
    //代理对象调用方法
    proxy.rent();
}

AOP

AOP的概述

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

AOP在spring中的作用

提供声明式事务,允许用户自定义切面

横切关注点:跨越应用程序多个模块的方法或功能。与业务逻辑无关,但需要关注的部分就是横切关注点。如:日志、安全、缓存、事务…
切面(ASPECT):横切关注点被模块化的特殊对象(它是一个类)
通知(Advice):切面必须要完成的工作(它是类中的方法)
目标(Target):被通知对象
代理(Proxy):向目标对象应用通知后创建的对象
切入点(PointCut):切面通知执行的位置
连接点(JointPoint):与切入点匹配的执行点

使用spring实现AOP

使用AOP织入,需要导入依赖包

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

1、方式一:使用spring的API接口 【主要springAPI接口实现】
日志类

前置增强

public class Log implements MethodBeforeAdvice {
    /**
     * @param method    方法
     * @param args      参数
     * @param target    目标对象
     * @throws Throwable
     */
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行了");
    }
}

后置增强

public class AfterLog implements AfterReturningAdvice {
    //returnValue返回值
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + method.getName() + "方法,返回结果:" +returnValue);
    }
}

xml配置

<bean id="userServiceImpl" class="com.bin.service.UserServiceImpl"/>
<bean id="afterLog" class="com.bin.log.AfterLog"/>
<bean id="log" class="com.bin.log.Log"/>
<!--配置aop:需要导入aop约束-->
<aop:config>
    <!--切入点:expression;表达式execution(要执行的位置: 访问权限(可省略) 返回值类型  全类名(可省略).方法(参数) 所抛出的异常(可省略))-->
    <aop:pointcut id="pointcut" expression="execution(* *.*(..))"/>
    <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>

测试类

@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userServiceImpl = (UserService) context.getBean("userServiceImpl");
    userServiceImpl.add();
    System.out.println();
    userServiceImpl.delete();
    System.out.println();
    userServiceImpl.update();
}

2、方式二:自定义来实现AOP 【主要是切面定义】

<bean id="diy" class="com.bin.diy.DiyPointCut"/>
<aop:config>
    <!--自定义切面,ref要引用的类-->
    <aop:aspect ref="diy">
        <!--切入点-->
        <aop:pointcut id="pointcut" expression="execution(* com.bin.service.UserServiceImpl.*(..))"/>
        <!--
            通知:将方法引用到标签中
                根据要求可以设置成:前置通知/后置通知...
            -->
        <aop:after method="hou" pointcut-ref="pointcut"/>
        <aop:before method="qian" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

3、方式三:使用注解实现

@Aspect
public class AnnotationPointCut {
    @Before("execution(* com.bin.service.*.*(..))")
    public void before(){
        System.out.println("执行前");
    }
    @After("execution(* com.bin.service.*.*(..))")
    public void after(){
        System.out.println("执行后");
    }
    //在环绕增强中,可以给定一个参数,代表获取处理切入的点
    @Around("execution(* com.bin.service.UserServiceImpl.*(..))")
    public void surround(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("---------环绕前---------");
        Object proceed = jp.proceed();
        System.out.println(proceed + "\n" + jp.getSignature());
        System.out.println("---------环绕后---------");
    }
}

@Aspect
表示该类为切面
@Before(“execution()”)
前置增强
@After(“execution()”)
后置增强
@Around(“execution()”)
环绕增强
@AfterReturning
@AfterThrowing

execution()表达式内写:修饰符 返回值 包.类.方法(…) 异常

修饰符、包、类、异常可以省略不写*

… 表示任意个

*表示任意的

Spring整合mybatis

导入依赖:

junit
mybatis
mysql-connector-java【mysql驱动】
spring-webmvc【spring】
aspectjweaver【aop织入】
mybatis-spring
spring-jdbc【spring管理jdbc所需依赖】
druid【阿里数据库连接池】
spring-tx【spring事务】

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.9</version>
    </dependency>
    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <!--aop依赖-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>
    <!--mysql驱动依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--mybatis整合依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.6</version>
    </dependency>
    <!--spring事务-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.9</version>
    </dependency>
    <!--数据库连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.6</version>
    </dependency>
    <!--spring管理jdbc的依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.9</version>
    </dependency>
</dependencies>

方式一:由spring得到每个接口的代理对象

xml配置:

<!--读取配置文件location:指定属性配置文件的路径-->
<context:property-placeholder location="classpath:db.properties"/>
<!--声明数据源-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="url" value="${db.url} }"/>
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--数据源-->
    <property name="dataSource" ref="myDataSource"/>
    <!--指定mybatis主配置文件-->
    <property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--创建Dao接口的实现类对象,MapperScannerConfigurer内部调用getMapper()生成每个接口的代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--指定SqlSessionFactory对象,能获取SqlSession-->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <!--指定Dao接口的包名,每个接口都会执行一次getMapper()得到Dao对象-->
    <property name="basePackage" value="com.bin.dao"/>
</bean>
<bean id="userService" class="com.bin.service.UserServiceImpl">
    <property name="userMapper" ref="userMapper"/>
</bean>

方式二:使用SqlSessionTemplate

xml配置:

<!--读取配置文件location:指定属性配置文件的路径-->
<context:property-placeholder location="classpath:db.properties"/>
<!--声明数据源-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="url" value="${db.url} }"/>
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--数据源-->
    <property name="dataSource" ref="myDataSource"/>
    <!--指定mybatis主配置文件-->
    <property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--SqlSessionTemplate线程安全的类,可以直接获得sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<bean id="userMapper" class="com.bin.dao.UserMapperImpl">
    <property name="sqlSession" ref="sqlSession"/>
</bean>
<bean id="userService" class="com.bin.service.UserServiceImpl">
    <property name="userMapper" ref="userMapper"/>
</bean>
实现类:

public class UserMapperImpl implements UserMapper {
    SqlSessionTemplate sqlSession;
    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }
    public List<User> selectUser() {
        return sqlSession.getMapper(UserMapper.class).selectUser();
    }
}

方式三:继承SqlSessionDaoSupport类

实际上和方法二差不多,只是在DAO实现类中少写了一个SqlSessionTemplate属性
xml配置:

<!--读取配置文件location:指定属性配置文件的路径-->
<context:property-placeholder location="classpath:db.properties"/>
<!--声明数据源-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="url" value="${db.url} }"/>
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--数据源-->
    <property name="dataSource" ref="myDataSource"/>
    <!--指定mybatis主配置文件-->
    <property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<bean id="userMapper02" class="com.bin.dao.UserMapperImpl02">
    <!--
        也可以使用SqlSessionTemplate
        但是SqlSessionDaoSupport会自动创建SqlSessionTemplate
        最终使用的还是由SqlSessionTemplate得到的sqlSession
    -->
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<bean id="userService" class="com.bin.service.UserServiceImpl">
    <property name="userMapper" ref="userMapper02"/>
</bean>

实现类:

public class UserMapperImpl02 extends SqlSessionDaoSupport implements UserMapper {
    public List<User> selectUser() {
        return getSqlSession().getMapper(UserMapper.class).selectUser();
    }
    public User selectUserById(int id) {
        return getSqlSession().getMapper(UserMapper.class).selectUserById(id);
    }
}

12、事务
事务ACID原则
原子性
一致性
隔离性
持久性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值