Spring5详解

Spring5详解

文章目录

概念

	Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 SpringMVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。

​ Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。

Spring的特点

1、轻量级

​ 从大小与开销两方面而言Spring都是轻量级的。完整的Spring框架可以只有在一个大小只有1M多的JPA文件里发布,并且Spring所以要的处理开销也是微不足道的。

2、控制反转

​ Spring通过控制反转IOC技术来降低程序间的耦合。当应用了IOC,一个对象依赖的其它对象都会通过被动的方式传递进来,而不是这个对象自己创建或查找依赖对象。

3、面向切面

​ Spring支持面向切面编程,并把应用业务逻辑和系统服务分开。

4、容器

​ Spring使用容器包含并管理应用对象的配置和声明周期,你可以配置你的每一个bean如何被创建----基于一个可配置原型,你的bean可以创建一个单独的实例或者每次需要时都创建一个新的实例。

5、框架

​ Spring可以将简单的组件配置、组合成复杂得应用。在Spring中,应用对象声明式的组合,典型的是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等),将应用逻辑的的开发留给开发者。

Spring的优势

1、方便解耦,简化开发

​ 通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 容器进行控制,避免硬编码所造
成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可
以更专注于上层的应用。

2、AOP 编程的支持

​ 通过 Spring 的 AOP 功能,方便进行面向切面的编程,。

3、声明式事务的支持

​ 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,
提高开发效率和质量。

4、方便程序的测试

​ 可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可
做的事情。

5、方便集成各种优秀框架

​ Spring 可以降低各种框架的使用难度,提供了对各种优秀框架( Struts、 Hibernate、 Hessian、 Quartz
等)的直接支持。

在这里插入图片描述

6、Java 源码是经典学习范例

​ Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以
及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例 。

Spring 的体系结构

在这里插入图片描述

Spring中的常用注解

​ bean注入与装配的方式有很多种,可以通过些xml、get\set、构造函数或者注解等。简单易用的方式就是使用Spring注解了。

​ 1、@Controller:用于标注控制层组件;用于标注在一个类上,使用它标记的类就是一个SpringMVC Controller对象;分发处理器时会扫描使用该注解的类方法,并检查该方法是否使用了@RequestMapping注解;可以把request请求的header绑定到方法的参数上。

​ 2、@RestController:相当于Controller与responseBody的组合效果。

​ 3、@Component:泛指组件,当组件不好归类时,可以使用这个注解进行标注。如:在切面类上可以使用@Component注解标注是一个组件。

​ 4、@Repository:用于注解dao层,在daoImpl上注解。

​ 5、@Service:用于标注业务层组件。

​ 6、@ResponseBody:用于异步请求;该注解用于将Controller的方法返回给对象,通过适当的HttpMessageConverter转化为指定格式后,写入Response对象的Body数据区;返回的数据不是一个HTML标签的页面,而是其他某种格式的数据时(如:json,xml)使用。

​ 7、@RequestMapping:一个用于处理地址请求映射的注解,可以用于类或方法上。用于类上,表示类中所有响应请求的方法都是以该地址作为父路径。

​ 8、@Autowired:它可以对类成员变量、方法、构造函数进行标注,完成自动装配的工作。通过对@Autowired的使用来消除对get/set方法。

​ 9、@PathVariable:用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出URL模板中的变量作为参数。

​ 10、@requestParam:主要用于SpringMVC在后台控制层获取参数,类似一种request.getParameter(“name”)。

​ 11、@RequestHeader:可以把request请求的header部分绑定到方法的参数上。

​ 12、@ModelAttribute:该Controller方法在调用时先执行此@ModelAttribute方法,可用于注解和方法参数,可以把@ModelAttribute特性应用在BaseController当中,所有的Controller都继承BaseController,即可实现在调用Controller时,先调用ModelAttribute。

​ 13、@SessionAttributes:即将值放在session作用域中,写在class上面。

​ 14、@Valid:实体数据校验,可以结合hibernate validator一起使用。

​ 15、@CookieValue:用来获取cookie中的值。

Spring IOC

1、概念

​ 控制反转(Inversion of Control,简称Ioc)把创建对象的权力交给框架,它包括依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。

​ Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化
Bean 并建立 Bean 之间的依赖关系。 Spring 的 IoC 容器在完成这些底层工作的基础上,还提供
了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务。

​ 在使用IOC之前,获取对象都是通过new来实例化的。是主动式。

在这里插入图片描述

​ 使用IOC之后,获取对象时,跟工厂要,工厂为我们查找或者创建对象。

在这里插入图片描述

2、spring 中工厂的类结构图

在这里插入图片描述

BeanFactory是Spring的顶层接口。

ApplicationContext是我们最常用的接口

ClassPathXmlApplicationContext与FileSystemXmlApplicationContext是两个基于xml配置的实现类。一个是基于类路径加载配置文件,一个是基于磁盘路径加载配置文件。

AnnotationConfigApplicationContext是基于注解配置的实现类。

1、BeanFactory 和 ApplicationContext 的区别

ApplicationContext 是它的子接口。
BeanFactory 和 ApplicationContext 的区别:
创建对象的时间点不一样。
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
BeanFactory:什么使用什么时候创建对象。

2、IOC 中 bean 标签和管理对象细节
1、bean标签

作用:用于配置对象,让Spring来管理创建对象。默认情况下,它调用的是类中的无参构造函数。如果没有无参构造函数则对象不能创建成功。

属性:

​ id:给对象在容器中提供一个唯一标识。用于获取对象。

​ class:指定类的全限定类名。用于反射创建对象,默认调用无参构造函数。

​ scop:指定对象的作用范围。

​ singleton:默认,单例对象。

​ prototype:多例对象。

​ request:WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中。

​ session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中 。

​ global session: WEB 项目中,应用在Portlet环境,如果没有Portlet环境,则globalSession 相当于 session。全局对象。

​ init-method: 指定类中的初始化方法名称。
​ destroy-method: 指定类中销毁方法名称。

2、bean 的作用范围和生命周期

​ 单例对象:scop=“singleton”;

​ 一个应用只有一个对象的实例。它的作用范围就是整个引用。

​ 生命周期:

​ 对象出生:当应用加载,创建容器时,对象就被创建了。

​ 对象活着:只要容器在,对象一直活着。

​ 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。

​ 多例对象:scop=“prototype”;

​ 每次访问对象时,都会重新创建对象实例。

​ 生命周期:

​ 对象出生:当使用对象时,创建新的对象实例。

​ 对象活着:只要对象在使用中,就一直活着。

​ 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。

3、实例化 Bean 的三种方式

​ 第一种方式:使用默认无参构造函数

<!--在默认情况下:
它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>

​ 第二种方式: spring 管理静态工厂-使用静态工厂的方法创建对象

/**
* 模拟一个静态工厂,创建业务层实现类
*/
public class StaticFactory {
public static IAccountService createAccountService(){
	return new AccountServiceImpl();
	}
}
<!-- 此种方式是:
使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器
id 属性:指定 bean 的 id,用于从容器中获取
class 属性:指定静态工厂的全限定类名
factory-method 属性:指定生产对象的静态方法
-->
<bean id="accountService"
class="com.itheima.factory.StaticFactory"
factory-method="createAccountService"></bean>

​ 第三种方式: spring 管理实例工厂-使用实例工厂的方法创建对象

/**
* 模拟一个实例工厂,创建业务层实现类
* 此工厂创建对象,必须现有工厂实例对象,再调用方法
*/
public class InstanceFactory {
public IAccountService createAccountService(){
	return new AccountServiceImpl();
	}
}
<!-- 此种方式是:
先把工厂的创建交给 spring 来管理。
然后在使用工厂的 bean 来调用里面的方法
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法。
-->
<bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService"
	factory-bean="instancFactory"
	factory-method="createAccountService"></bean>
3、Spring的依赖注入

依赖注入: Dependency Injection。 它是 spring 框架核心 ioc 的具体实现。我们的程序在编写时, 通过控制反转, 把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。ioc 解耦只是降低他们的依赖关系,但不会消除。 例如:我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系, 在使用 spring 之后, 就让 spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

1、构造函数注入
/**
*就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 *spring 框架来为我们注入。
*/
public class AccountServiceImpl implements IAccountService {
	private String name;
	private Integer age;
	private Date birthday;
	public AccountServiceImpl(String name, Integer age, Date birthday) {
	this.name = name;
	this.age = age;
	this.birthday = birthday;
	}
@Override
public void saveAccount() {
	System.out.println(name+","+age+","+birthday);
	}
}
<!-- 使用构造函数的方式,给 service 中的属性传值
要求:
类中需要提供一个对应参数列表的构造函数。
涉及的标签:
constructor-arg
属性:
index:指定参数在构造函数参数列表的索引位置
type:指定参数在构造函数中的数据类型
name:指定参数在构造函数中的名称 用这个找给谁赋值
=======上面三个都是找给谁赋值,下面两个指的是赋什么值的==============
value:它能赋的值是基本数据类型和 String 类型
ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
2、set 方法注入

/**顾名思义,就是在类中提供需要注入成员的 set 方法。*/
public class AccountServiceImpl implements IAccountService {
	private String name;
	private Integer age;
	private Date birthday;
	public void setName(String name) {
		this.name = name;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}
	@Override
	public void saveAccount() {
		System.out.println(name+","+age+","+birthday);
	}
}
<!-- 通过配置文件给 bean 中的属性传值:使用 set 方法的方式
涉及的标签:
property
属性:
name:找的是类中 set 方法后面的部分
ref:给属性赋值是其他 bean 类型的
value:给属性赋值是基本数据类型和 string 类型的
实际开发中,此种方式用的较多。
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="name" value="test"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
3、使用 p 名称空间注入数据(本质还是调用 set 方法)

​ 此种方式是通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的set 方法实现注入功能。

/**
* 使用 p 名称空间注入,本质还是调用类中的 set 方法
*/
public class AccountServiceImpl4 implements IAccountService {
	private String name;
	private Integer age;
	private Date birthday;
	public void setName(String name) {
		this.name = name;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}
	@Override
	public void saveAccount() {
		System.out.println(name+","+age+","+birthday);
	}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
       	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	     xsi:schemaLocation=" http://www.springframework.org/schema/beans
			http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountService"
class="com.itheima.service.impl.AccountServiceImpl4"
p:name="test" p:age="21" p:birthday-ref="now"/>
</beans>
4、注入集合属性

​ 顾名思义, 就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入数组, List,Set,Map,Properties。

public class AccountServiceImpl implements IAccountService {
	private String[] myStrs;
	private List<String> myList;
	private Set<String> mySet;
	private Map<String,String> myMap;
	private Properties myProps;
	public void setMyStrs(String[] myStrs) {
		this.myStrs = myStrs;
	}
	public void setMyList(List<String> myList) {
		this.myList = myList;
	}
	public void setMySet(Set<String> mySet) {
		this.mySet = mySet;
	}
	public void setMyMap(Map<String, String> myMap) {
		this.myMap = myMap;
	}
	public void setMyProps(Properties myProps) {
		this.myProps = myProps;
	}
	@Override
	public void saveAccount() {
		System.out.println(Arrays.toString(myStrs));
		System.out.println(myList);
		System.out.println(mySet);
		System.out.println(myMap);
		System.out.println(myProps);
	}
}
<!-- 注入集合数据
List 结构的:
array,list,set
Map 结构的
map,entry,props,prop
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
<!-- 给数组注入数据 -->
<property name="myStrs">
	<set>
		<value>AAA</value>
		<value>BBB</value>
		<value>CCC</value>
	</set>
</property>
<!-- 注入 list 集合数据 -->
<property name="myList">
	<array>
		<value>AAA</value>
		<value>BBB</value>
		<value>CCC</value>
	</array>
</property>
<!-- 注入 set 集合数据 -->
<property name="mySet">
	<list>
		<value>AAA</value>
		<value>BBB</value>
		<value>CCC</value>
	</list>
</property>
<!-- 注入 Map 数据 -->
<property name="myMap">
	<props>
		<prop key="testA">aaa</prop>
		<prop key="testB">bbb</prop>
	</props>
</property>
<!-- 注入 properties 数据 -->
<property name="myProps">

	<map>
		<entry key="testA" value="aaa"></entry>
		<entry key="testB">
			<value>bbb</value>
		</entry>
	</map>

</property>
</bean>
3、基于注解IOC配置

注解配置和 xml 配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。

​ 1、导入spring-aop-5.0.2.RELEASE.jar包。

​ 2、使用@Component注解配置管理资源。

/**
* 账户的业务层实现类
*/
@Component("accountService")
public class AccountServiceImpl implements IAccountService {
	private IAccountDao accountDao;
	public void setAccountDao(IAccountDao accountDao) {
		this.accountDao = accountDao;
	}
}
/**
* 账户的持久层实现类
*/
@Component("accountDao")
public class AccountDaoImpl implements IAccountDao {
	private DBAssit dbAssit;
}
//1、 当我们使用注解注入时, set 方法不用写

​ 3、创建 spring 的 xml 配置文件并开启对注解的支持

注意:
基于注解整合时,导入约束时需要多导入一个 context 名称空间下的约束。
由于我们使用了注解配置,此时不能在继承 JdbcDaoSupport,需要自己配置一个 JdbcTemplate
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!-- 告知 spring 创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置 dbAssit -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>
1、常用注解
1、@Component

​ 作用:把资源让给Spring来管理。相当于在xml中配置一个bean。

​ 属性:value:指定bean的id。如果不能指定value属性,默认bean的id就是当前类的类名。首字母小写。

2、@Controller、@Service、@Repository

​ @Controller: 一般用于表现层的注解。

​ @Service: 一般用于业务层的注解。

​ @Repository: 一般用于持久层的注解。 细节:如果注解中有且只有一个属性要赋值时,且名称是 value, value 在赋值是可以不写。

用于注入数据的相当于
<property name="" ref="">
<property name="" value="">  
3、@Autowired

​ 作用:自动按照类型注入。当使用注解注入属性时, set 方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。

4、@Qualifier

​ 作用:在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和@Autowire 一起使用;但是给方法参数注入时,可以独立使用。

​ 属性:value:指定 bean 的 id。

5、@Resource

​ 作用: 直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。

​ 属性: name:指定 bean 的 id。

​ 6、@Value

​ 作用: 注入基本数据类型和 String 类型数据的

​ 属性: value:用于指定值 。

用于改变作用范围的:
相当于: <bean id="" class="" scope="">

​ 7、@Scope

​ 作用:指定 bean 的作用范围。

​ 属性: value:指定范围的值。 取值: singleton prototype request session globalsession 。

和生命周期相关的: 
相当于: <bean id="" class="" init-method="" destroy-method="" />

​ 8、@PostConstruct

​ 作用:用于指定初始化方法。

​ 9、@PreDestroy

​ 作用: 用于指定销毁方法。

关于 Spring 注解和 XML 的选择问题

​ 1、注解的配置简单,维护方便。xml修改时不用修改源码,不涉及重新编译和部署。

2、Spring管理Bean方式的比较

在这里插入图片描述

问题
我们发现,之所以我们现在离不开 xml 配置文件,是因为我们有一句很关键的配置:
<!-- 告知spring框架在,读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
如果他要也能用注解配置,那么我们就离脱离 xml 文件又进了一步。
另外,数据源和 JdbcTemplate 的配置也需要靠注解来实现。
<!-- 配置 dbAssit -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
3、新注解@Configuration

​ 作用:用于指定当前类是一个 spring 配置类, 当创建容器时会从该类上加载注解。 获取容器时需要使用AnnotationApplicationContext(有@Configuration 注解的类.class)。

​ 属性:value:用于指定配置类的字节码 。

/**
* spring 的配置类,相当于 bean.xml 文件
*/
@Configuration
public class SpringConfiguration {
}
4、@ComponentScan

​ 作用:用于指定 spring 在初始化容器时要扫描的包。 作用和在 spring 的 xml 配置文件中的作用是一样的。

<context:component-scan base-package="com.itheima"/>

​ 属性:basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。

/**
* spring 的配置类,相当于 bean.xml 文件
*/
@Configuration
@ComponentScan("com.itheima")
public class SpringConfiguration {
}

​ 5、@Bean

​ 作用:该注解只能写在方法上,表明使用该方法创建一个对象,并放入Spring容器。

​ 属性:name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。

/**
* 连接数据库的配置类
*/
public class JdbcConfig {
/**
* 创建一个数据源,并存入 spring 容器中
* @return
*/
	@Bean(name="dataSource")
	public DataSource createDataSource() {
	try {
		ComboPooledDataSource ds = new ComboPooledDataSource();
		ds.setUser("root");
		ds.setPassword("1234");
		ds.setDriverClass("com.mysql.jdbc.Driver");
		ds.setJdbcUrl("jdbc:mysql:///spring_day02");
		return ds;
	} catch (Exception e) {
		throw new RuntimeException(e);
	}
}
/**
* 创建一个 DBAssit,并且也存入 spring 容器中
* @param dataSource
* @return
*/
@Bean(name="dbAssit")
public DBAssit createDBAssit(DataSource dataSource) {
	return new DBAssit(dataSource);
	}
}
注意:
我们已经把数据源和 DBAssit 从配置文件中移除了,此时可以删除 bean.xml 了。
但是由于没有了配置文件,创建数据源的配置又都写死在类中了。如何把它们配置出来呢?
请看下一个注解。

​ 5、@PropertySource

​ 作用:用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。

​ 属性:value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:

配置:
/**
* 连接数据库的配置类
*/
public class JdbcConfig {
	@Value("${jdbc.driver}")
	private String driver;
	@Value("${jdbc.url}")
	private String url;
	@Value("${jdbc.username}")
	private String username;
	@Value("${jdbc.password}")
	private String password;
/**
* 创建一个数据源,并存入 spring 容器中
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
	try {
		ComboPooledDataSource ds = new ComboPooledDataSource();
		ds.setDriverClass(driver);
		ds.setJdbcUrl(url);
		ds.setUser(username);
		ds.setPassword(password);
		return ds;
	} catch (Exception e) {
		throw new RuntimeException(e);
		}
	}
}
jdbc.properties 文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/MysqlName
jdbc.username=root
jdbc.password=1234
注意:
此时我们已经有了两个配置类,但是他们还没有关系。如何建立他们的关系呢?
5、@Import

​ 作用:用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。

​ 属性:value[]:用于指定其他配置类的字节码。

@Configuration
@ComponentScan(basePackages = "com.itheima.spring")
@Import({ JdbcConfig.class})
public class SpringConfiguration {
}
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig{
}
由于没有配置文件了,如何获取容器呢?

​ 6、通过注解获取容器:

ApplicationContext ac =new AnnotationConfigApplicationContext(SpringConfiguration.class);

​ 配置类和配置文件可以写在类路径下的任意位置。

Spring 整合 Junit

1、测试类中的问题和解决思路
1、问题

​ 在测试类中,每个测试方法都有以下两行代码:

ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);

​ 这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。

2、解决思路分析

​ 我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建 spring 容器,我们就无须手动创建了,问题也就解决了。

spring 框架给我们提供了一个运行器,可以读取配置文件(或注解)来创建容器。我们只需要告诉它配置文件在哪就行了 。

3、步骤
1、导入jar包

​ spring-test-5.0.2.RELEAES.jar、spring-aop-5.0.2.RELEAES.jar

2、使用@RunWith 注解替换原有运行器
/**
* 测试类
*/
@RunWith(SpringJUnit4ClassRunner.class)
public class AccountServiceTest {
}
3、使用@ContextConfiguration 指定 spring 配置文件的位置
/**
* 测试类
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:bean.xml"})
public class AccountServiceTest {
}
@ContextConfiguration 注解:
locations 属性: 用于指定配置文件的位置。如果是类路径下,需要用 classpath:表明
classes 属性: 用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。
4、使用@Autowired 给测试类中的变量注入数据
5、为什么不把测试类配到 xml 中

​ 在解释这个问题之前,先解除大家的疑虑,配到 XML 中能不能用呢?

​ 答案是肯定的,没问题,可以使用。

​ 那么为什么不采用配置到 xml 中的方式呢?

​ 这个原因是这样的:

​ 第一:当我们在 xml 中配置了一个 bean, spring 加载配置文件创建容器时,就会创建对象。

​ 第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。

所以,基于以上两点,我们不应该把测试配置到 xml 文件中 。

Spring AOP

1、概念

​ AOP: 全称是 Aspect Oriented Programming 即: 面向切面编程 。通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

​ “横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect”,即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

​ 使用"横切"技术, AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似, 比如权限认证、日志、事物。 AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来 。

​ AOP 的实现方式 :动态代理技术。

2、AOP 主要应用场景

1、Authentication 权限

  1. Caching 缓存
  2. Context passing 内容传递
  3. Error handling 错误处理
  4. Lazy loading 懒加载
  5. Debugging 调试
  6. logging, tracing, profiling and monitoring 记录跟踪 优化 校准
  7. Performance optimization 性能优化
  8. Persistence 持久化
  9. Resource pooling 资源池
  10. Synchronization 同步
  11. Transactions 事务
3、AOP核心概念

1、切面(aspect) : 类是对物体特征的抽象,切面就是对横切关注点的抽象。一个关注点的模块化,这个关注点 可能会横切多个对象(如:事务管理)。在Spring AOP中可以基于xml配置和@Aspect注解来实现。

2、横切关注点: 对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。

3、连接点(joinpoint) : 在程序执行过程中某个特定的点(如:某个方法调用的时候或者处理异常的时候)。被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。在Spring AOP中,一个连接点总是表示一个方法的执行。

4、切入点(pointcut) : 对连接点进行拦截的定义。通知和一个切入点表达式关联,并在满足这个切入点的连接上运行(如:当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用Aspect切入语法。

5、引入(introduction) : 用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象(如:可以使用引入来使一个bean来实现IsModified接口,以便简化缓存机制)。在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。

6、目标对象(Target Object): 代理的目标对象。一个或者多个切面所通知的对象,也称为通知(advice)对象。既然SpringAOP是通过动态代理实现的,这个对象永远是一个被代理(proxied)对象。

7、AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面锲约(如:通知方法执行)。在Spring中AOP可以是JDK动态代理或CGLIB代理。

7、织入(Weaving) : 将切面应用到目标对象并导致代理对象创建的过程 。把切面连接到其它应用程序类型或对象上,并创建一个被通知的对象。这些可以在编译时、类加载时、运行时完成。

8、 通知(advice) : 在切面的某个特定的连接点上执行的动作。指的是指拦截到连接点之后要执行的代码, 通知分为前置、后置、异常、最终、环绕通知五类。

  • 前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程。除非抛出异常。
  • 后置通知(After returning advice):在某连接点正常执行流程后执行的通知。如:一个方法没有抛出任何异常,正常返回。
  • 异常通知(After throwing advice):在方法抛出异常退出时,执行的通知。
  • 最终通知(After finally advice):某连接点退出的时候执行的通知(无论是正常返回还是抛出异常)。
  • 环绕通知(Around advice):包围一个连接点的通知,如方法调用。环绕通知可以在方法前后完成自定义的行为。它也会选择是否继续执行连接点或者直接返回它自己的返回值或抛出异常来结束执行。
4、Spring AOP的实现
1、基于XML的配置
1、导入jar包

​ AOP的jar包:aopalliance-1.0.jar、aspectjweaver-1.8.7.jar、spring-aop-5.0.2.RELEASE.jar、spring-aspects-5.0.2.RELEASE.jar

​ IOC的jar包:commons-logging-1.2.jar、log4j-1.2.16.jar、spring-beans-5.0.2.RELEASE.jar、spring-context-5.0.2.RELEASE.jar、spring-core-5.0.2.RELEASE.jar、spring-expression-5.0.2.RELEASE.jar

2.创建Spring的配置文件并导入约束
此处要导入 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: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">
</beans>
3、配置Spring的IOC
<!-- 配置 service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
	<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置 dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
    <property name="dbAssit" ref="dbAssit"></property>
</bean>
<!-- 配置数据库操作对象 -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
	<property name="dataSource" ref="dataSource"></property>
<!-- 指定 connection 和线程绑定 -->
	<property name="useCurrentConnection" value="true"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
	<property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property>
	<property name="user" value="root"></property>
	<property name="password" value="1234"></property>
</bean>
4、抽取公共代码制作成通知
/**
* 事务控制类
*/
public class TransactionManager {
	//定义一个 DBAssit
	private DBAssit dbAssit ;
	public void setDbAssit(DBAssit dbAssit) {
		this.dbAssit = dbAssit;
	}
	//开启事务
	public void beginTransaction() {
	try {
		dbAssit.getCurrentConnection().setAutoCommit(false);
	} catch (SQLException e) {
		e.printStackTrace();
		}
	}
	//提交事务
	public void commit() {
	try {
		dbAssit.getCurrentConnection().commit();
		} catch (SQLException e) {
		e.printStackTrace();
		}
	}
	//回滚事务
	public void rollback() {
	try {
		dbAssit.getCurrentConnection().rollback();
		} catch (SQLException e) {
		e.printStackTrace();
		}
	}
	//释放资源
	public void release() {
	try {
		dbAssit.releaseConnection();
		} catch (Exception e) {
		e.printStackTrace();
		}
	}
}
5、把通知类用 bean 标签配置起来
<!-- 配置通知 -->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
	<property name="dbAssit" ref="dbAssit"></property>
</bean>
6、使用 aop:config 声明 aop 配置
aop:config:
作用: 用于声明开始 aop 的配置
<aop:config>
		<!-- 配置的代码都写在此处 -->
</aop:config>
7、使用 aop:aspect 配置切面
aop:aspect:
作用:
用于配置切面。
属性:
id: 给切面提供一个唯一标识。
ref: 引用配置好的通知类 bean 的 id。
<aop:aspect id="txAdvice" ref="txManager">
<!--配置通知的类型要写在此处-->
</aop:aspect>
8、使用 aop:pointcut 配置切入点表达式
aop:pointcut:
作用:
用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
属性:
expression:用于定义切入点表达式。
id: 用于给切入点表达式提供一个唯一标识
<aop:pointcut expression="execution(
public void com.itheima.service.impl.AccountServiceImpl.transfer(
java.lang.String, java.lang.String, java.lang.Float)
)" id="pt1"/>
9、使用 aop:xxx 配置对应的通知类型
aop:before
作用:
	用于配置前置通知。 指定增强的方法在切入点方法之前执行
属性:
	method:用于指定通知类中的增强方法名称
	ponitcut-ref:用于指定切入点的表达式的引用
	poinitcut:用于指定切入点表达式
执行时间点:
	切入点方法执行之前执行
<aop:before method="beginTransaction" pointcut-ref="pt1"/>
aop:after-returning
作用:
	用于配置后置通知
属性:
	method: 指定通知中方法的名称。
	pointct: 定义切入点表达式
	pointcut-ref: 指定切入点表达式的引用
执行时间点:
	切入点方法正常执行之后。它和异常通知只能有一个执行
<aop:after-returning method="commit" pointcut-ref="pt1"/>
aop:after-throwing
作用:
	用于配置异常通知
属性:
	method: 指定通知中方法的名称。
	pointct: 定义切入点表达式
	pointcut-ref: 指定切入点表达式的引用
执行时间点:
	切入点方法执行产生异常后执行。它和后置通知只能执行一个
<aop:after-throwing method="rollback" pointcut-ref="pt1"/>
aop:after
作用:
	用于配置最终通知
属性:
	method: 指定通知中方法的名称。
	pointct: 定义切入点表达式
	pointcut-ref: 指定切入点表达式的引用
执行时间点:
	无论切入点方法执行时是否有异常,它都会在其后面执行。
<aop:after method="release" pointcut-ref="pt1"/>
10、切入点表达式说明
execution:匹配方法的执行(常用)
	execution(表达式)
表达式语法: execution([修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明:
	全匹配方式:
		public void
com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
	访问修饰符可以省略
		void
com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
	返回值可以使用*号,表示任意返回值
		*
com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
	包名可以使用*号,表示任意包,但是有几级包,需要写几个*
* *.*.*.*.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
	使用..来表示当前包,及其子包
* com..AccountServiceImpl.saveAccount(com.itheima.domain.Account)
	类名可以使用*号,表示任意类
* com..*.saveAccount(com.itheima.domain.Account)
	方法名可以使用*号,表示任意方法
* com..*.*( com.itheima.domain.Account)
	参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* com..*.*(*)
	参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* com..*.*(..)
	全通配方式:
* *..*.*(..)
注:
通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* com.itheima.service.impl.*.*(..))
11、环绕通知
配置方式:
<aop:config>
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))"
id="pt1"/>
<aop:aspect id="txAdvice" ref="txManager">
<!-- 配置环绕通知 -->
<aop:around method="transactionAround" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
aop:around:
作用:
	用于配置环绕通知
属性:
	method:指定通知中方法的名称。
	pointct:定义切入点表达式
	pointcut-ref:指定切入点表达式的引用
说明:
	它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
注意:
	通常情况下,环绕通知都是独立使用的
/**
* 环绕通知
* @param pjp
* spring 框架为我们提供了一个接口: ProceedingJoinPoint,它可以作为环绕通知的方法参数。
* 在环绕通知执行时, spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
* @return
*/
public Object transactionAround(ProceedingJoinPoint pjp) {
//定义返回值
	Object rtValue = null;
	try {
		//获取方法执行所需的参数
		Object[] args = pjp.getArgs();
		//前置通知:开启事务
		beginTransaction();
		//执行方法
		rtValue = pjp.proceed(args);
		//后置通知:提交事务
		commit();
		}catch(Throwable e) {
		//异常通知:回滚事务
			rollback();
			e.printStackTrace();
			}finally {
        //最终通知:释放资源
			release();
		}
	return rtValue;
}
2、基于注解的配置
1、导入jar包
2、在配置文件中导入 context 的名称空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!-- 配置数据库操作对象 -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
	<property name="dataSource" ref="dataSource"></property>
	<!-- 指定 connection 和线程绑定 -->
	<property name="useCurrentConnection" value="true"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
	<property name="jdbcUrl" value="jdbc:mysql:///spring_day02"></property>
	<property name="user" value="root"></property>
	<property name="password" value="1234"></property>
</bean>
</beans>
3、把资源使用注解配置
//业务层实现类
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
	@Autowired
	private IAccountDao accountDao;
}
//持久层实现类
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
	@Autowired
	private DBAssit dbAssit ;
}
4、在配置文件中指定 spring 要扫描的包
<!-- 告知 spring,在创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
5、把通知类也使用注解配置
//事务控制类
@Component("txManager")
@Aspect//表明当前类是一个切面类
public class TransactionManager {
//定义一个 DBAssit
@Autowired
private DBAssit dbAssit ;
}
6、在增强的方法上使用注解配置通知
@Before
作用:
	把当前方法看成是前置通知。
属性:
	value:用于指定切入点表达式,还可以指定切入点表达式的引用。
//开启事务
@Before("execution(* com.itheima.service.impl.*.*(..))")
public void beginTransaction() {
	try {
		dbAssit.getCurrentConnection().setAutoCommit(false);
	} catch (SQLException e) {
		e.printStackTrace();
	}
}
@AfterReturning
作用:
	把当前方法看成是后置通知。
属性:
	value:用于指定切入点表达式,还可以指定切入点表达式的引用
//提交事务
@AfterReturning("execution(* com.itheima.service.impl.*.*(..))")
public void commit() {
    try {
		dbAssit.getCurrentConnection().commit();
	} catch (SQLException e) {
		e.printStackTrace();
	}
}
@AfterThrowing
作用:
	把当前方法看成是异常通知。
属性:
	value:用于指定切入点表达式,还可以指定切入点表达式的引用
//回滚事务
@AfterThrowing("execution(* com.itheima.service.impl.*.*(..))")
public void rollback() {
	try {
		dbAssit.getCurrentConnection().rollback();
	} catch (SQLException e) {
		e.printStackTrace();
	}
}
@After
作用:
	把当前方法看成是最终通知。
属性:
	value:用于指定切入点表达式,还可以指定切入点表达式的引用
//释放资源
@After("execution(* com.itheima.service.impl.*.*(..))")
public void release() {
	try {
		dbAssit.releaseConnection();
	} catch (Exception e) {
		e.printStackTrace();
	}
}
7、在 spring 配置文件中开启 spring 对注解 AOP 的支持
<!-- 开启 spring 对注解 AOP 的支持 -->
<aop:aspectj-autoproxy>
8、环绕通知注解配置
@Around
作用:
	把当前方法看成是环绕通知。
属性:
	value:用于指定切入点表达式,还可以指定切入点表达式的引用。
/**
* 环绕通知
* @param pjp
* @return
*/
@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object transactionAround(ProceedingJoinPoint pjp) {
	//定义返回值
	Object rtValue = null;
	try {
		//获取方法执行所需的参数
		Object[] args = pjp.getArgs();
		//前置通知:开启事务
		beginTransaction();
		//执行方法
		rtValue = pjp.proceed(args);
		//后置通知:提交事务
		commit();
	}catch(Throwable e) {
		//异常通知:回滚事务
		rollback();
		e.printStackTrace();
	}finally {
		//最终通知:释放资源
		release();
	}
    return rtValue;
}
9、切入点表达式注解
@Pointcut
作用:
	指定切入点表达式
属性:
	value:指定表达式的内容
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1() {}

引用方式:
/**
* 环绕通知
* @param pjp
* @return
*/
@Around("pt1()")//注意:千万别忘了写括号
public Object transactionAround(ProceedingJoinPoint pjp) {
	//定义返回值
	Object rtValue = null;
	try {
		//获取方法执行所需的参数
		Object[] args = pjp.getArgs();
		//前置通知:开启事务
		beginTransaction();
		//执行方法
		rtValue = pjp.proceed(args);
		//后置通知:提交事务
		commit();
	}catch(Throwable e) {
		//异常通知:回滚事务
		rollback();
		e.printStackTrace();
    }finally {
		//最终通知:释放资源
		release();
	}
	return rtValue;
}
10、不使用 XML 的配置方式
@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

Spring 事务

@Around
作用:
	把当前方法看成是环绕通知。
属性:
	value:用于指定切入点表达式,还可以指定切入点表达式的引用。
/**
* 环绕通知
* @param pjp
* @return
*/
@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object transactionAround(ProceedingJoinPoint pjp) {
	//定义返回值
	Object rtValue = null;
	try {
		//获取方法执行所需的参数
		Object[] args = pjp.getArgs();
		//前置通知:开启事务
		beginTransaction();
		//执行方法
		rtValue = pjp.proceed(args);
		//后置通知:提交事务
		commit();
	}catch(Throwable e) {
		//异常通知:回滚事务
		rollback();
		e.printStackTrace();
	}finally {
		//最终通知:释放资源
		release();
	}
    return rtValue;
}
9、切入点表达式注解
@Pointcut
作用:
	指定切入点表达式
属性:
	value:指定表达式的内容
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1() {}

引用方式:
/**
* 环绕通知
* @param pjp
* @return
*/
@Around("pt1()")//注意:千万别忘了写括号
public Object transactionAround(ProceedingJoinPoint pjp) {
	//定义返回值
	Object rtValue = null;
	try {
		//获取方法执行所需的参数
		Object[] args = pjp.getArgs();
		//前置通知:开启事务
		beginTransaction();
		//执行方法
		rtValue = pjp.proceed(args);
		//后置通知:提交事务
		commit();
	}catch(Throwable e) {
		//异常通知:回滚事务
		rollback();
		e.printStackTrace();
    }finally {
		//最终通知:释放资源
		release();
	}
	return rtValue;
}
10、不使用 XML 的配置方式
@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

Spring 事务

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值