(源码链接:https://pan.baidu.com/s/1i-afXQ2tBGded6t8Psea8g 提取码:bh0r)
一、What is the Spring?
Spring框架是由于软件开发的复杂性而创建的,换句话说:Spring是为简化软件开发而存在的。Spring主要的作用可以概况为:简化开发,最大程度的松耦合。
为什么可以松耦合?这个要从下面的模型讲起:
什么是耦合?比如对象A的在实现其功能时需要用到对象B,那就说A依赖B,A和B之间就有耦合关系。耦合又会带来啥?那就是如果修改其中一方,另一方就需要同步修改,如上图Object A、Object B、Object C就是互相耦合的,3个齿轮环环相扣,缺一不可,若其中一个齿轮出现故障或改动,会直接影响其它2个齿轮的运转。耦合度高的项目很不利于自身的更新、扩展、维护、部署,举个不利于部署的例子:如果客户只需要某个很简单的功能,但因为你的项目耦合度太高无法独立划分出来部署,那你就需要部署维护一整套资源,成本就太高了。其实,松耦合是我们一直追求的目标。
Spring实际上就是一种松耦合的方案,它作为第三方,为其他各自独立的对象建立联系。如上图中间的图,Object A、Object B、Object C彼此独立,他们的联系是由第三方Spring维系的,这种模式的好处就是Object A、Object B、Object C无论哪个改变都不会影响其它的两个。下面从代码的角度来说明:
如果不使用Spring,按照原来的模式,service层调用DAO层,需要在service层new一个peopleDAO对象,再去调用该对象的方法,如下:
public class PeopleDAO {
public void add(People p){}
}
public class PeopleService {
public void insert(People p){
PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.add(p);
}
}
很显然,若这样处理service层和DAO层就有了很强的耦合关系,且service的每个需要调用DAO层对象的方法都需要new一个这个对象,那你将看到service层到处是new DAO层对象的语句。
若使用了Spring,那么DAO层的对象只需要一次注入即可到处使用,这个对象交由Spring管理,如下:
public class PeopleService {
private PeopleDAO peopleDAO;
public void add(People p){
peopleDAO.add(p);
}
}
这里Spring作为第三方实现了解耦:
若未使用spring进行管理,service层在使用DAO层的对象时就是传统的new这个对象,一旦DAO中的这个对象发生改变,比如构造方法等改变,那service中所有使用new这个构造方法的代码都需要同步修改,这样耦合性很大;若使用了spring,则service中要使用的对象是由spring注入的,即使DAO层对象改变,service层的代码不需要修改,就实现了解耦。
二、Spring 架构
Spring框架是一种一站式框架,封装了Web应用所需的所有层面。尤其是Spring对Bean对象,Dao组件对象,Service组件对象等管理能力是它的核心功能(虽然也具有处理持久层和web层的功能,但是一般交由更加专项的如SpringMVC、Mybatis等框架处理)。
spring也是一种容器,专门装对象,贯穿web层、service层和dao层的,创建struts2中的action、springMVC中的controller、service中的javaBean、dao层hibernate中的session等。
三、Spring中的核心概念
1、IOC
(Inversion of Control),即控制反转。简单的来说,控制反转就是反转了对象的创建方式,由我们自己new创建反转给了Spring。控制的方式有两种:配置文件或注解,本文后面会详细介绍这两种实现IOC的方式。IOC是Spring的核心,贯穿始终。
2、DI
(Dependency Injection),即依赖注入。IOC时被调用对象交由Spring来创建,被调用对象注入调用者中的过程就是DI。在理解上DI不应该和IOC区别的太远,两者可以理解成一个概念,只是IOC偏重于原理,DI偏重于实现,或者说DI是实现IOC的方式。IOC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的。
所以这两个概念可以理解成是一回事,IOC是目的,DI是实现方式。
3、AOP
(Aspect Oriented Programming),即面向切面编程。听起来很抽象,但不要被这个吓坏,通俗的讲,比如:一个组件A,不关心其他常用的服务组件B,但是这个组件A使用组件B的时候,不是组件A自身调用,而是通过配置等其他方式,比如Spring中通过xml配置文件。这样就使得A压根就不需要知道服务组件B是怎样的。A只关系自己的业务逻辑,具体A使用B的时候,配置文件去做,与具体的A组件无关。AOP就是实现上面所述过程的方式。
实际上Spring主要有两个层面的动态:一是动态创建所需的对象,二是动态为某个类去动态添加一些方法。第一种就是IOC,而第二种就是AOP。AOP的主要作用就是:不修改目标类的前提下,使用动态代理技术,去增强目标类的功能。
例如:你要在类A的某个方法被执行时添加事务的启动与提交功能。若不使用AOP方式,唯一的实现方式就是在这个方法中加入事务功能代码,这样肯定能实现功能,但如果项目中有很多类似的方法都要添加一样的事务功能,你就需要每个方法中都加上类似的事务相关代码,项目中会出现大量重复代码并且增加了开发工作量。
这时你会想,要是像添加事务、添加日志这类频繁出现的功能不要一遍一遍重复的在业务代码里面写就好了,而是某个类的某个方法需要的时候就动态自动添加,这样这个业务类就和事务等实现了解耦,业务类只关心业务。
AOP就是来干这样的事的,可以不用修改这个类,就能为这个类增加额外的功能。AOP专门用于处理系统中分布于各个模块中的交叉关注问题,或者说来处理具有横切性质的系统级服务,如事务管理、安全检查、日志记录、缓存、对象池管理等。利用AOP可以对业务逻辑的各个部分进行隔离,使得业务逻辑各个部分之间的耦合度降低。
本文后面会详细介绍AOP是如何实现这样的过程的,这里只需要知道AOP能干什么就行了。
以上IOC和AOP可以通过xml配置文件实现,也可以通过注解实现。
四、Spring创建bean的过程
Spring中有两种容器对象:ApplicationContext和BeanFactory
BeanFactory已经过时,且ApplicationContext已经覆盖BeanFactory所有接口,因此实际开发时,ApplicationContext来创建bean对象。
下面通过xml配置文件的形式演示下流程:
- 将要创建bean对象的实体类写进.xml文件
- 根据配置创建Spring容器对象,从容器对象中取得需要的bean对象
- 结果
五、DI(依赖注入)的方式
Sping的注入方式可以分为三种:
1.set注入
- 基本数据类型
- 引用类型(对象)
- 复杂类型(数组,List,Map,Properties)
2.构造方法注入
- 基本数据类型
- 引用类型(对象)
- 复杂类型(数组,List,Map,Properties)
3.接口注入
在类中,对于依赖的接口,通过具体的实现类名,动态的加载并强制转换成其接口类型。接口注入不常用。接口注入模式因为历史较为悠久,在很多容器中都已经得到应用。但由于其在灵活性、易用性上不如其他两种注入模式,因而在 IOC 的专题世界内并不被看好。不是重点,不需去研究。
注意:构造器注入和setter注入都是通过java的反射技术得以实现的。
1、set注入
set注入是spring中最主流的注入方式, setter注入是通过setter方法注入,首先将构造方法设置为无参的构造方法,然后利用setter注入为其设置新的值,实际就是利用java的反射技术实现的。
1)基本类型注入
ApplicationContext.xml,如
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<!--
name属性: 填写你要注入的字段名称
value属性:你要注入的字段名称对应的值
-->
<bean name="people" class="com.wo.domain.People">
<property name="name" value="熊大"></property>
<property name="age" value="18"></property>
</bean>
</beans>
public class DemoTest {
@Test
public void test2() {
// 1、默认从src下加载.xml,根据配置文件创建 容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2、从容器对象中取得 需要的 bean对象
People people = (People) context.getBean("people");
System.out.println(people);
}
}
People{age=18, name='熊大'}
注意:set注入要求这个属性set方法要存在不然会。报错,如下把People类中name属性的set方法注释之后,ApplicationContext.xml就会报错:0
public class People {
private int age;
private String name;
private Ball ball;
public String getName() {
return name;
}
// public void setName(String name) {
// this.name = name;
// }
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Ball getBall() {
return ball;
}
public void setBall(Ball ball) {
this.ball = ball;
}
@Override
public String toString() {
return "People{" +
"age=" + age +
", name='" + name + '\'' +
", ball=" + ball +
'}';
}
}
2)引用类型注入
总体来说分为两步:
- 需要先将要注入的引用对象使用Spring容器创建出来;
- 再将该引用对象注入进来,使用ref
public class Ball {
private String name;
private int size;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
@Override
public String toString() {
return "Ball{" + "name='" + name + '\'' + ", size=" + size + '}';
}
}
public class People {
private int age;
private String name;
private Ball ball;
public Ball getBall() {
return ball;
}
public void setBall(Ball ball) {
this.ball = ball;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "People{" + "age=" + age + ", name='" + name + '\'' + '}';
}
}
3)复杂类型注入(数组、List、Map、Properties)
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* Feng, Ge 2020/2/19 19:59
*/
public class CollectionDemo {
private Object[] arr;
private List list;
private Map map;
private Properties properties;
public Object[] getArr() {
return arr;
}
public void setArr(Object[] arr) {
this.arr = arr;
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
public Map getMap() {
return map;
}
public void setMap(Map map) {
this.map = map;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "CollectionDemo{" +
"arr=" + Arrays.toString(arr) +
", list=" + list +
", map=" + map +
", properties=" + properties +
'}';
}
}
xml文件:
<bean name="collection" class="com.wo.domain.CollectionDemo">
<property name="arr">
<array>
<value>你大爷</value>
<value>你二大爷</value>
</array>
</property>
<property name="list">
<list>
<value>DOTA</value>
<value>war</value>
<ref bean="myBall"></ref>
</list>
</property>
<property name="map">
<map>
<entry key="price" value="9.9"></entry>
<entry key="address" value="地球"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="id">ksodsodkosdosodko</prop>
</props>
</property>
</bean>
@Test
public void test() {
// 1、默认从src下加载.xml,根据配置文件创建 容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2、从容器对象中取得 需要的 bean对象
CollectionDemo collectionDemo = (CollectionDemo) context.getBean("collection");
System.out.println(collectionDemo);
}
CollectionDemo{arr=[你大爷, 你二大爷], list=[DOTA, war, Ball{name='FootBall', size=12}], map={price=9.9, address=地球}, properties={id=ksodsodkosdosodko}}
注意:Properties本质就是Map
2、构造方法注入
构造器注入是通过构造方法注入。不需要被注入类实现set方法,只需要有构造方法,下面展示基本类型、引用类型、复杂类型的构造器注入过程:
import java.util.List;
/**
* Feng, Ge 2020/2/19 20:36
*/
public class ConStructorDemo {
private String name;
private List list;
private Ball ball;
public ConStructorDemo(String name, List list, Ball ball) {
this.name = name;
this.list = list;
this.ball = ball;
}
@Override
public String toString() {
return "ConStructorDemo{" +
"name='" + name + '\'' +
", list=" + list +
", ball=" + ball +
'}';
}
}
以上并未实现set方法,只有构造方法。
<bean name="construction" class="com.wo.domain.ConStructorDemo">
<constructor-arg index="0" type="java.lang.String" value="构造器注入"></constructor-arg>
<constructor-arg index="1" type="java.util.List">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</constructor-arg>
<constructor-arg index="2" type="com.wo.domain.Ball" ref="myBall"></constructor-arg>
</bean>
@Test
public void testConstruction() {
// 1、默认从src下加载.xml,根据配置文件创建 容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2、从容器对象中取得 需要的 bean对象
ConStructorDemo conStructorDemo = (ConStructorDemo) context.getBean("construction");
System.out.println(conStructorDemo);
}
ConStructorDemo{name='构造器注入', list=[1, 2, 3], ball=Ball{name='FootBall', size=12}}
注意:这里index指定构造方法参数顺序,要和构造方法定义一致。
set注入和构造方法注入的优缺点比较:
-
使用set注入的理由:
- 1.若Bean有很多的依赖,那么构造方法的参数列表会变的很长
- 2.若一个对象有多种构造方法,构造方法会造成代码量增加
- 3.若构造方法中有两个以上的参数类型相同,那么将很难确定参数的用途
-
使用构造方法注入的理由:
- 1.构造方法注入使用强依赖规定,如果不给足够的参数,对象则无法创建
- 2.对于依赖关系无须变化的Bean,构造注入更有用处;因为没有setter方法,所有的依赖关系全部在构造器内设定,因此,不用担心后续代码对依赖关系的破坏。
建议:
这两种依赖注入的方式,并没有绝对的好坏,只是适应的场景有所不同。但Spring官方更推荐使用set注入,即采用以设值注入为主,构造注入为辅的注入策略。对于依赖关系无需变化的注入,尽量采用构造注入;而其他的依赖关系的注入,则考虑采用设值注入。
六、AOP的7个专业术语
前面已经介绍过AOP的概念,AOP的主要作用就是在不修改目标类的基础上,使用动态代理技术,去增强目标类的功能 。
这样直接说可能不能很快的去理解,下面看一个需求场景:
假如现在有一个类,要求你在这个执行这个类里的每个方法的前后都加上事务开启与事务提交。例如下面的类:
/**
* Feng, Ge 2020/2/19 21:32
*/
public class AopDemo {
public void add(){
System.out.println("执行增加!");
}
}
现在要求你在执行add()方法前开启事务,执行add()后提交事务,当然你可以如下面的方式来实现:
public class AopDemo {
public void add(){
System.out.println("开启事务!");
System.out.println("执行增加!");
System.out.println("提交事务!");
}
}
以上虽然可以完成我们的需求,但是仔细考虑就会有问题:(1)现在AopDemo类只有一个add()方法,假如AopDemo类有很多方法或者别的类的很多方法也要实现同样的添加事务的功能,像这样在每个方法里面写"开启事务!"和"提交事务!"的方式会造成大量重复代码,带来很大的工作量;(2)直接在AopDemo中编写代码破坏了AopDemo的独立性,使得这个类与事务相关的类产生耦合,一方改动会影响另一方。
基于以上分析,我们想要是在不修改AopDemo类代码的前提下,就能给AopDemo类增加事务功能就好了,既不影响AopDemo类的业务,使其保持独立,又能添加想要的功能。AOP就是来干这件事的,AOP把上面的场景分成三方:目标类、增强(通知)、代理类
上面场景中的AopDemo类就是目标类(Target),这里事务相关的代码就是增强,代理类由Spring提供。AOP中重要的专业术语如下:
- 目标类(Target):增强逻辑的织入目标类,例如:AopDemo类
- 连接点(Joinpoint):可能被Spring拦截的点(方法),例如:AopDemo类中的update()方法
- 切入点(Pointcut):已经被增强的连接点(方法),例如:AopDemo类中的add()方法
- 增强/通知(Advice):那些增强的代码,例如:AdviceDemo类中的方法
- 织入(Weaving):把增强advice应用到目标类target来创建新的代理对象procxy的过程
- 代理(Proxy):一个类被AOP织入增强后,就产出了一个结果类,这个结果类就是融合了原类和增强逻辑的代理类
- 切面(Aspect):切入点和增强的结合。通知说明了干什么和什么时候干(什么时候通过方法名中的befor,after,around等就能知道),切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
七、Spring实现AOP的过程
1、 编写目标类
/**
- 目标类
- Feng, Ge 2020/2/19 21:32
*/
public class AopDemo {
public void add() {
System.out.println("执行增加!");
}
public void update() {
System.out.println("执行修改!");
}
public void delete() {
System.out.println("删除!");
}
}
2、 编写增强
/**
- 增强
- Feng, Ge 2020/2/19 21:57
*/
public class AdviceDemo {
public void before(){
System.out.println("开启事务!");
}
public void after(){
System.out.println("提交事务!");
}
}
3、配置xml
结果:
开启事务!
执行增加!
提交事务!
注意:
1、使用AOP功能时,ApplicationContext.xml头文件要增加Aop相关规范及资源
- xmlns XML NameSpace的缩写,初始化bean的格式文件地址,来区分此xml文件不同于别的xml
- xmlns:xsi 指定了xml所使用的Schema(模式)定义的语言及需要遵循的规范
- xmlns:aop 使用spring框架的aop 面向切面编程时,在xml文件中引入aop资源
- xsi:context:关于spring上下文,包括加载资源文件
- xmlns:tx spring 事务配置
- xsi:schemaLocation 引入所有xml本文档需要遵循的规范 解析器在需要的情况下对该属性引入的文档进行校验,第一个值表示命名空间,第二个值表示该命名空间的模式文档的具体位置,其中命名空间和对应的 xsd 文件的路径,必须成对出现。比如用到的 、、 等命名空间,都需要在这里声明其 xsd 文件地址。
2、需要导入aspectjweaver.jar包
3、AOP的切入点表达式
<aop:pointcut expression=“execution(表达式)” id=“pCut”/>
- public void com.wo.domain.AopDemo.add() ==》具体的
- public void com.wo.domain.AopDemo.add(…) ==》切入点的方法参数不固定
- public void com.wo.domain.AopDemo.*add(…) ==》切入点的方法开头不确定
八、AOP增强/通知的五种类型
- before 前置通知
目标方法运行之前调用 - after-returning 后置通知
目标方法运行之后调用,但出现异常则不会调用 - around 环绕通知
在目标方法之前和之后都调用 - after-throwing 异常拦截通知
出现异常就会调用 - after 最终通知
目标方法运行之后调用,无论有无异常都会调用
/**
* 目标类
* Feng, Ge 2020/2/19 21:32
*/
public class AopDemo {
public void add() {
// 手动产生异常
int i = 6/0;
System.out.println("执行增加!");
}
public void update() {
System.out.println("执行修改!");
}
public void delete() {
System.out.println("删除!");
}
}
/**
* 增强
* Feng, Ge 2020/2/19 21:57
*/
public class AdviceDemo {
public void before() {
System.out.println("开启事务!");
}
public void after() {
System.out.println("提交事务!");
}
public void afterExp() {
System.out.println("出现异常会执行我!");
}
}
@Test
public void testAop() {
// 1、默认从src下加载.xml,根据配置文件创建 容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2、从容器对象中取得 需要的 bean对象
AopDemo aopDemo = (AopDemo) context.getBean("aopDemo");
aopDemo.add();
}
结果:
jionpoint参数:
在每个增强方法中,都可以接收一个Joinpoint类型参数,主要包含两个方法:
- getTarget():获得被代理的目标对象
- getSignature():获取被代理目标类中的目标方法
/**
* 增强
* Feng, Ge 2020/2/19 21:57
*/
public class AdviceDemo {
public void before(JoinPoint joinPoint) {
System.out.println(joinPoint.getTarget());
System.out.println(joinPoint.getSignature());
System.out.println(joinPoint.getSignature().getName());
System.out.println("开启事务!");
}
}
com.wo.domain.AopDemo@395b56bb
void com.wo.domain.AopDemo.add()
add
开启事务!
执行增加!
提交事务!
九、Spring中的注解
利用注解也可以实现一下目标:
- IOC
- DI
- AOP
就是利用注解取代xml配置。
但是并不是xml文件就不需要了,使用注解时需要先在xml文件中添加注解相关约束加载相关资源,并开启注解扫描,即告诉Spring要以注解的方式创建bean。
1、注解实现IOC
具体配置说明如下:
@Component(value = "people")
public class People {
private int age;
private String name;
private Ball ball;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Ball getBall() {
return ball;
}
public void setBall(Ball ball) {
this.ball = ball;
}
@Override
public String toString() {
return "People{" +
"age=" + age +
", name='" + name + '\'' +
", ball=" + ball +
'}';
}
}
@Test
public void test2() {
// 1、默认从src下加载.xml,根据配置文件创建 容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2、从容器对象中取得 需要的 bean对象
People people = (People) context.getBean("people");
System.out.println(people);
}
People{age=0, name='null', ball=null}
这里xml中并没有配置标签,但是依然可以实现bean的创建,这就是注解方式。@Componment就是把普通pojo实例化到spring容器中,相当于配置文件中的
注意:
Spring中的@Componment有三个衍生注解:
- @Controller ----> Web层
- @Service ----> 业务层
- @Repository ----> 持久层(DAO)
这3个和@Componment的作用是一样的,这4个注解都可以完成bean的创建,但是后3个可以更清晰的展示各层级,利于项目的机构清晰和代码可读性,项目开发中各层最好使用各层专有的注解形式。
2、注解实现DI
注解实现DI就可以不再提供set或者构造器,分为以下几种:
- 普通类型注入,使用@Value
- 对象注入,使用@AutoWired(按类型自动装配)或者@Qualifier(强制使用名称注入);还可以使用@Resource,它是Java提供的,但是Spring也支持这种注解形式,相当于@AutoWired和@Qualifier一起使用
@Component(value = "people")
public class People {
@Value("16")
private int age;
@Value("ohou")
private String name;
@Autowired
private Ball ball;
// 不在需要set()方法
// public String getName() {
// return name;
// }
//
// public void setName(String name) {
// this.name = name;
// }
//
// public int getAge() {
// return age;
// }
//
// public void setAge(int age) {
// this.age = age;
// }
//
// public Ball getBall() {
// return ball;
// }
//
// public void setBall(Ball ball) {
// this.ball = ball;
// }
@Override
public String toString() {
return "People{" +
"age=" + age +
", name='" + name + '\'' +
", ball=" + ball +
'}';
}
}
@Component
public class Ball {
@Value("好球")
private String name;
private int size;
@Override
public String toString() {
return "Ball{" + "name='" + name + '\'' + ", size=" + size + '}';
}
}
@Test
public void test2() {
// 1、默认从src下加载.xml,根据配置文件创建 容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2、从容器对象中取得 需要的 bean对象
People people = (People) context.getBean("people");
System.out.println(people);
}
结果:
People{age=16, name='ohou', ball=Ball{name='好球', size=0}}
3、注解实现AOP
1、首先是开启AOP扫描:
aop:aspectj-autoproxy</aop:aspectj-autoproxy>
2、目标类添加注解(创建bean)
@Component
public class AopDemo {
public void add() {
System.out.println("执行增加!");
}
public void update() {
System.out.println("执行修改!");
}
public void delete() {
System.out.println("删除!");
}
}
3、增强添加注解(创建bean、配置切面(配置切入点、通知类型))
@Component
@Aspect
public class AdviceDemo {
@Before(value = "execution(public void add())")
public void before(JoinPoint joinPoint) {
System.out.println(joinPoint.getTarget());
System.out.println(joinPoint.getSignature());
System.out.println(joinPoint.getSignature().getName());
System.out.println("开启事务!");
}
@After(value = "execution(public void add())")
public void after() {
System.out.println("提交事务!");
}
public void afterExp() {
System.out.println("出现异常会执行我!");
}
}
@Test
public void testAop() {
// 1、默认从src下加载.xml,根据配置文件创建 容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2、从容器对象中取得 需要的 bean对象
AopDemo aopDemo = (AopDemo) context.getBean("aopDemo");
aopDemo.add();
}
com.wo.domain.AopDemo@44c79f32
void com.wo.domain.AopDemo.add()
add
开启事务!
执行增加!
提交事务!
十、Spring中的事务管理
1、事务的特性
先回顾一下事务的特性(ACID):
- 原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。
- 一致性(Consistency):事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
拿转账来说,
假设用户A和用户B两者的钱加起来一共是20000,
那么不管A和B之间如何转账,转几次账,
事务结束后两个用户的钱相加起来应该还得是20000,
这就是事务的一致性。
- 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。 - 持久性(Durability):当事务正确完成后,它对于数据的改变是永久性的。
例如我们在使用JDBC操作数据库时,在提交事务方法后,
提示用户事务操作完成,当我们程序执行完成直到看到提示后,
就可以认定事务已经正确提交,即使这时候数据库出现了问题,
也必须要将我们的事务完全执行完成,
否则就会造成我们看到提示事务处理完毕,
但是数据库因为故障而没有执行事务的重大错误。
如果违背以上原则就很可能引起下面的问题:
- 脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
小明的银行卡余额里有100元。现在他打算用手机点一个外卖饮料,
需要付款10元。但是这个时候,他的女朋友看中了一件衣服95元,
她正在使用小明的银行卡付款。于是小明在付款的时候,
程序后台读取到他的余额只有5块钱了,根本不够10元,
所以系统拒绝了他的交易,告诉余额不足。
但是小明的女朋友最后因为密码错误,无法进行交易。
小明非常郁闷,明明银行卡里还有100元,怎么会余额不足呢?
- 幻读也叫虚读:一个事务执行两次查询,第二次结果集包含第一次中没有或某些行已经被删除的数据,造成两次结果不一致,只是另一个事务在这两次查询中间插入或删除了数据造成的。幻读是事务非独立执行时发生的一种现象。
例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,
这时事务T2又对这个表中插入了一行数据项,
而这个数据项的数值还是为“1”并且提交给数据库。
而操作事务T1的用户如果再查看刚刚修改的数据,
会发现还有一行没有修改,其实这行是从事务T2中添加的,
就好像产生幻觉一样,这就是发生了幻读。
- 不可重复读:一个事务两次读取同一行的数据,结果得到不同状态的结果,中间正好另一个事务更新了该数据,两次结果相异,不可被信任。
例如事务T1在读取某一数据,
而事务T2立马修改了这个数据并且提交事务给数据库,
事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
注意:
1.不可重复读和脏读的区别:脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
2.幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
2、事务的操作步骤
1.开启事务
2.提交事务
3.回滚事务(发生异常时)