以下笔记转载自github

IOC 和 DI 的概述

IOC(Inversion of Controll)

思想是反转资源获取的方向,传统的资源查找方式要求组件向容器发起请求查找资源。作为回应,容器适时的返回资源。而应用了IOC之后,则是容器主动的将资源推送给它所管理的组件,组件所要做的仅是选择一种合适的方式来接收资源

DI(Dependency Injection)

是IOC的另一种表述方式,即组件以一些预先定义好的方式(如:getter方法)来接收来自容器的资源注入

Spring配置

在SpringIOC容器读取bean配置创建bean实例之前,必须对它进行实例化。只有在容器实例化后,才可以从IOC容器里获取bean实例并使用

Spring提供了两种类型的IOC容器实现

  • BeanFactory:IOC容器的基本实现,在调用getBean()方法时才会实例化对象
  • ApplicationContext:提供了更多的高级特性,在加载配置文件后就会实例化对象。是BeanFactory的子接口

BeanFactory是Spring框架的基础设施,面向Spring本身

ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory

无论使用何种方式,配置文件时都是相同的

1
2
3
4
5
<!-- 配置bean -->
<!-- class: bean的全类名,通过反射的方式在IOC容器中创建bean,所以要求bean中必须有无参构造器 -->
<bean id="people" class="learningspring.ioc.examples.People">
<property name="name" value="Chen"/>
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class People {
private String name;

public People() {
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
@Test
public void test(){
// 创建IOC容器
ApplicationContext ctx = new FileSystemXmlApplicationContext("F:\\Projects\\IdeaProjects\\LearningSpring\\src\\main\\java\\learningspring\\ioc\\examples\\applicationContext.xml");

// 从IOC容器中获取bean实例
People people = (People) ctx.getBean("people");

System.out.println(people);
}

ApplicationContext

ApplicationContext有两个实现类:

  • ClassPathXmlApplicationContext:加载类路径里的配置文件
  • FileSystemXmlApplicationContext:加载文件系统里的配置文件

Bean的相关配置

bean标签的id和name的配置

  • id:使用了约束中的唯一约束。不能有特殊字符
  • name:没有使用约束中的唯一约束(理论上可以重复,但是实际开发中不能出现)。可以有特殊字符

bean的生命周期的配置

  • init-method:bean被初始化的时候执行的方法
  • destroy-method:bean被销毁的时候执行的方法,前提是bean是单例的,工厂关闭

bean的作用范围的配置

  • scope:bean的作用范围
    • singleton:单例模式,默认的作用域。
    • prototype:多例模式。
    • request:应用在Web项目中,Spring创建这个类后,将这个类存入到request范围中。
    • session:应用在Web项目中,Spring创建这个类后,将这个类存入到session范围中。
    • globalsession:应用在Web项目中,必须在porlet环境下使用。但是如果没有这种环境,相当于session。

Spring的Bean管理配置

Spring的Bean的实例化方式

无参构造方式(默认)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* //TODO
*
* @author Chen Rui
* @version 1.0
**/
public class Dog {

private String name;
private Integer length;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getLength() {
return length;
}

public void setLength(Integer length) {
this.length = length;
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", length=" + length +
'}';
}
}

1
2
3
4
5
6
7
8
9
10
11
<?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">

<!-- Spring Bean的实例化方式-->

<!-- 无参构造的方式 -->
<bean id="dog" class="learningspring.ioc.examples.demo3.Dog"/>

</beans>

静态工厂实例化方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
*
* @author Chen Rui
* @version 1.0
**/
public class Car {
private String name;
private Double price;

public Car() {
}

public Car(String name, Double price) {
this.name = name;
this.price = price;
}

@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package learningspring.ioc.examples.demo3;

/**
*
* @author Chen Rui
* @version 1.0
**/
public class CarFactory {

public static Car createCar(){
return new Car();
}
}
1
2
3
4
5
6
7
8
9
10
<?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">

<!-- Spring Bean的实例化方式-->
<!-- 静态工厂的方式 -->
<bean id="car" class="learningspring.ioc.examples.demo3.CarFactory" factory-method="createCar"/>

</beans>

实例工厂实例化方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* //TODO
*
* @author Chen Rui
* @version 1.0
**/
public class Dog {

private String name;
private Integer length;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getLength() {
return length;
}

public void setLength(Integer length) {
this.length = length;
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", length=" + length +
'}';
}
}

1
2
3
4
5
6
7
8
9
10
11
12
/**
* //TODO
*
* @author Chen Rui
* @version 1.0
**/
public class DogFactory {

public Dog createDog(){
return new Dog();
}
}
1
2
3
4
5
6
7
8
9
10
11
<?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">

<!-- Spring Bean的实例化方式-->

<!-- 实例工厂的方式 -->
<bean id="dogFactory" class="learningspring.ioc.examples.demo3.DogFactory"/>
<bean id="dog2" factory-bean="dogFactory" factory-method="createDog"/>
</beans>

Spring的属性注入方式

构造方法方式的属性注入

1
2
3
4
5
6
7
8
9
10
<?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">
<!--构造方法方式的属性注入-->
<bean id="car" class="learningspring.ioc.examples.demo3.Car">
<constructor-arg name="name" value="BWM"/>
<constructor-arg name="price" value="800000"/>
</bean>
</beans>

Set方法方式的属性注入

1
2
3
4
5
6
7
8
9
10
11
<?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">

<!--Set方法方式的属性注入-->
<bean id="dog" class="learningspring.ioc.examples.demo3.Dog">
<property name="name" value="Golden"/>
<property name="length" value="100"/>
</bean>
</beans>

为Bean注入引用类型的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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">
<!--构造方法方式的属性注入-->
<bean id="car" class="learningspring.ioc.examples.demo3.Car">
<constructor-arg name="name" value="BWM"/>
<constructor-arg name="price" value="800000"/>
</bean>

<!--Set方法方式的属性注入-->
<bean id="dog" class="learningspring.ioc.examples.demo3.Dog">
<property name="name" value="Golden"/>
<property name="length" value="100"/>
</bean>

<!--为Bean注入对象属性-->
<!--构造方法方式一样可行-->
<bean id="employee" class="learningspring.ioc.examples.demo3.Employee">
<property name="name" value="Chen"/>
<property name="car" ref="car"/>
<property name="dog" ref="dog"/>
</bean>
</beans>

P名称空间的属性注入(Spring2.5)

  • 通过引入p名称空间完成属性注入
    • 普通属性:p:属性名=“值”
    • 对象属性:p:属性名-ref=“值”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<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">

<!--P名称空间的属性注入-->
<bean id="cat" class="learningspring.ioc.examples.demo3.Cat" p:name="Orange" p:length="100"/>

<!--为Bean注入对象属性-->
<bean id="employee" class="learningspring.ioc.examples.demo3.Employee" p:cat-ref="cat">
<property name="name" value="Chen"/>
<property name="car" ref="car"/>
<property name="dog" ref="dog"/>
</bean>
</beans>

SpEL方式的属性注入(Spring3)

SpEL:Spring Expresssion Language 的表达式语言

语法:#{表达式}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<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">

<!--SpEL表达式注入-->
<bean id="cat2" class="learningspring.ioc.examples.demo4.Cat">
<!--字符串要加单引号-->
<!--也可以通过#{beanName.属性名或方法名}来通过其他bean的属性或者方法来注入-->
<property name="name" value="#{'Orange'}"/>
<property name="length" value="#{101}"/>
</bean>
</beans>

注入集合类型的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 注入集合类型的数据测试
*
* @author Chen Rui
* @version 1.0
**/
public class CollectionBean {

private String[] strs;
private List<String> list;
private Set<String> set;
private Map<String, String> map;

public void setStrs(String[] strs) {
this.strs = strs;
}

public void setList(List<String> list) {
this.list = list;
}

public void setSet(Set<String> set) {
this.set = set;
}

public void setMap(Map<String, String> map) {
this.map = map;
}

@Override
public String toString() {
return "CollectionBean{" +
"strs=" + Arrays.toString(strs) +
", list=" + list +
", set=" + set +
", map=" + map +
'}';
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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">

<!--Spring的集合属性的注入-->
<!--注入数组类型-->
<bean id="collectionBean" class="learningspring.ioc.examples.demo4.CollectionBean">
<!-- 注入数组类型 -->
<property name="strs">
<list>
<value>Tom</value>
<value>Jack</value>
</list>
</property>

<!-- 注入List集合 -->
<property name="list">
<list>
<value>Lucy</value>
<value>Lily</value>
</list>
</property>

<!-- 注入Set集合 -->
<property name="set">
<set>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</set>
</property>

<!-- 注入Map集合 -->
<property name="map">
<map>
<entry key="a" value="1"/>
<entry key="b" value="2"/>
</map>
</property>
</bean>
</beans>

Spring分模块开发的配置

  • 加载配置文件时,直接加载多个配置文件
1
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml", "applicationContext2.xml");
  • 在一个配置文件中引入多个配置文件
1
2
3
4
5
6
7
8
9
<?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">

<!--引入配置文件-->
<import resource="applicationContext2.xml"/>

</beans>

Spring开发中的常用注解

@Component

该注解在类上使用,使用该注解就相当于在配置文件中配置了一个Bean,例如:

1
2
3
4
5
6
7
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDaoImpl.save");
}
}

相当于以下内容:

1
<bean id="userDao" class="learningspring.ioc.examplesannotation.demo1.UserDaoImpl"></bean>

该注解有3个衍生注解:

  • @Controller:修饰Web 层类
  • @Service:修饰Service层类
  • @Repository:修饰Dao层类

@Value

该注解用于给属性注入值,有2种用法

  • 如果有set方法,则需要将该注解添加到set方法上
  • 如果没有set方法,则需要将该注解添加到属性上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* Value 注解用于属性注入
* 当类有提供set方法时添加在set方法上
* 如果没有,则添加到属性上
*
* @author Chen Rui
* @version 1.0
**/

@Component("dog")
public class Dog {
private String name;

@Value("100") // 注入属性值
private Double length;

public Dog() {
}

public Dog(String name, Double length) {
this.name = name;
this.length = length;
}

public String getName() {
return name;
}

@Value("Golden") // 注入属性值
public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", length=" + length +
'}';
}
}

@Autowired

@Value 通常用于普通属性的注入。

@Autowired 通常用于为对象类型的属性注入值,但是按照类型完成属性注入

习惯是按照名称完成属性注入,所以必须让@Autowired注解和@Qualifier注解一同使用

(如果没有@Qualifier注解,修改以下例子中@Repository注解的值,也能编译成功)

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service("userService")
public class UserServiceImpl implements UserService {

@Autowired
@Qualifier("userDao")
private UserDao userDao;

@Override
public void save() {
System.out.println("UserServiceImpl.save");
userDao.save();
}
}
1
2
3
4
5
6
7
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDaoImpl.save");
}
}

@Resource

该注解也可以用于属性注入,通常情况下使用**@Resource注解,默认按照名称**完成属性注入。

该注解由J2EE提供,需要导入包javax.annotation.Resource

@Resource有两个重要的属性:nametype,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* UserController
*
* @author Chen Rui
* @version 1.0
**/
@Controller("userController")
public class UserController {

@Resource(name = "userService")
private UserService userService;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* UserService实现类
*
* @author Chen Rui
* @version 1.0
**/

@Service("userService")
public class UserServiceImpl implements UserService {

@Resource(name = "userDao")
private UserDao userDao;

@Override
public void save() {
System.out.println("UserServiceImpl.save");
userDao.save();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* UserDao实现类
* @author Chen Rui
* @version 1.0
**/

@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDaoImpl.save");
}
}

@PostConstruct 和 @PreDestroy

@PostConstruct@PreDestroy注解,前者为Bean生命周期相关的注解,在配置文件中,使用到了init-method参数来配置Bean初始化之前要执行的方法,该注解也是同样的作用。将该注解添加到想在初始化之前执行的目标方法上,就可以实现该功能,而后者则是Bean生命周期中destroy-method目标方法,使用该注解在指定方法上,即可实现在Bean销毁之前执行该方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* UserDao实现类
* @author Chen Rui
* @version 1.0
**/

@Component("userDao")
public class UserDaoImpl implements UserDao {

@PostConstruct
public void init(){
System.out.println("UserDaoImpl.init");
}

@Override
public void save() {
System.out.println("UserDaoImpl.save");
}

@PreDestroy
public void destroy(){
System.out.println("UserDaoImpl.destroy");
}
}

@Scope

Bean的作用范围的注解,默认为singleton(单例)

可选值:

  • singleton
  • prototype
  • request
  • session
  • globalsession
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* UserDao实现类
* @author Chen Rui
* @version 1.0
**/

@Component("userDao")
@Scope // 默认为singleton
public class UserDaoImpl implements UserDao {

@PostConstruct
public void init(){
System.out.println("UserDaoImpl.init");
}

@Override
public void save() {
System.out.println("UserDaoImpl.save");
}

@PreDestroy
public void destroy(){
System.out.println("UserDaoImpl.destroy");
}
}

基于XML配置和基于注解配置的对比

基于XML的配置 基于注解的配置
Bean的定义 <bean id=”Bean的id” class=”类的全路径”/> @Component或衍生注解@Controller,@Service和@Repository
Bean的名称 通过id或name指定 @Component(“Bean的id”)
Bean的属性注入 <property>或者通过p命名空间 通过注解@Autowired 按类型注入
通过@Qualifier按名称注入
Bean的生命周期 init-method指定Bean初始化前执行的方法,destroy-method指定Bean销毁前执行的方法 @PostConstruct 对应于int-method
@PreDestroy 对应于destroy-method
Bean的作用域 在bean标签中配置scope属性 @Scope, 默认是singleton
配置多例可以在目标类上使用@Scope(“prototype”)
使用场景 Bean来自第三方,可以使用在任何场景 Bean的实现类由自己维护

XML可以适用于任何场景,就算Bean来自第三方也可以适用XML方式来管理。而注解方式就无法在此场景下使用,注解方式可以让开发的过程更加方便,但前提是Bean由自己维护,这样才能在源码中添加注解。

所以可以使用两者混合的方式来开发项目,使用XML配置文件来管理Bean,使用注解来进行属性注入

Spring AOP

AOP的概述

面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP切入点表达式语法

AOP切入点表达式是基于execution的函数完成的

语法:**[访问修饰符] 方法返回值 包名.类名.方法名(参数)**

“*” 表示任意返回值类型
“..” 表示任意参数

  • public void learningspring.aop.aspectj.xml.demo2.ProductDaoImpl.save(..) :具体到某个增强的方法
  • * *.*.*.*Dao.save(..) :所有包下的所有以Dao结尾的类中的save方法都会被增强
  • * learningspring.aop.aspectj.xml.demo2.ProductDaoImpl+.save(..) :ProductDaoImpl及其子类的save方法都会被增强
  • * learningspring.aop.aspectj.xml..*.*(..):xml包及其子包的所有类的方法都会被增强

AspectJ的注解配置案例

首先也是创建一个接口ProductDao

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* ProductDao接口
*
* @author Chen Rui
* @version 1.0
**/
public interface ProductDao {

/**
* 添加商品
*/
void save();

/**
* 查询商品
*/
void query();

/**
* 修改商品
*/
void modify();

/**
* 删除商品
*/
String delete();
}

然后创建一个Dao实现类ProductDaoImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* ProductDao的实现类
*
* @author Chen Rui
* @version 1.0
**/
public class ProductDaoImpl implements ProductDao {

@Override
public void save() {
System.out.println("添加商品");
}

@Override
public String delete() {
System.out.println("删除商品");
return new Date().toString();
}

@Override
public void modify() {
System.out.println("修改商品");
}

@Override
public void query() {
System.out.println("查询商品");
int a = 1/0;
}
}

接着创建增强类ProductEnhancer,在该类里面使用注解

使用@Pointcut注解可以配置切入点信息,在较多的方法都要使用同一个增强时,就可以配置一个切入点让目标方法都去引用

@Before:前置增强

@AfterReturning:后置增强,其中的returning的值必须和方法传入的参数名相同

@Around:环绕增强

@AfterThrowing:异常抛出增强,其中的throwing的值必须和方法传入的参数名相同

@After:最终增强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* ProductDao的增强类(切面类)
*
* @author Chen Rui
* @version 1.0
**/
@Aspect
public class ProductEnhancer {

/**
* 切入点配置
* 对ProductDaoImpl里的方法都增强
*/
@Pointcut(value = "execution(* learningspring.aop.aspectj.annotation.demo2.ProductDaoImpl.*(..))")
private void pointcut1(){}

/**
* 前置增强案例
* 在调用save方法之前进行权限校验
* @param joinPoint 切入点对象
*/
@Before(value = "execution(* learningspring.aop.aspectj.annotation.demo2.ProductDaoImpl.save())")
public void checkPri(JoinPoint joinPoint){
System.out.println("【前置增强】权限校验" + joinPoint);
}

/**
* 后置增强案例
* 在调用delete方法之后,写入日志记录操作时间
* @param result 目标方法返回的对象
*/
@AfterReturning(returning = "result", value = "execution(* learningspring.aop.aspectj.annotation.demo2.ProductDaoImpl.delete())")
public void writeLog(Object result){
System.out.println("【后置增强】写入日志 操作时间:" + result.toString());
}

/**
* 环绕增强
* 在调用modify方法前后,显示性能参数
* @param joinPoint 切入点对象
* @throws Throwable 可抛出的异常
*/
@Around(value = "execution(* learningspring.aop.aspectj.annotation.demo2.ProductDaoImpl.modify())")
public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("【环绕增强】当前空闲内存" + Runtime.getRuntime().freeMemory()/(1024 * 1024) + "MB");
Object obj = joinPoint.proceed();
System.out.println("【环绕增强】当前空闲内存" + Runtime.getRuntime().freeMemory()/(1024 * 1024) + "MB");
return obj;
}

/**
* 异常抛出增强
* 在调用query时若抛出异常则打印异常信息
* @param ex 异常对象
*/
@AfterThrowing(throwing = "ex", value = "execution(* learningspring.aop.aspectj.annotation.demo2.ProductDaoImpl.query())")
public void exception(Throwable ex){
System.out.println("【异常抛出增强】" + "异常信息:" +ex.getMessage());
}

/**
* 最终增强
* 无论ProductDaoImpl里的每个方法是否抛出异常都打印当前时间
*/
@After(value = "pointcut1()")
public void finallyAdvice(){
System.out.println("【最终增强】" + new Date().toString());
}
}

编写测试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* AspectJ的注解方式配置测试类
*
* @author Chen Rui
* @version 1.0
**/

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:aspectj-annotation.xml")
public class AppTest {

@Resource(name = "productDao")
private ProductDao productDao;

@Test
public void test(){

productDao.save();

productDao.delete();

productDao.modify();

productDao.query();
}
}

运行,控制台输出

1
2
3
4
5
6
7
8
9
10
11
12
13
【前置增强】权限校验execution(void learningspring.aop.aspectj.annotation.demo2.ProductDao.save())
添加商品
【最终增强】Tue Mar 19 16:01:06 CST 2019
删除商品
【最终增强】Tue Mar 19 16:01:06 CST 2019
【后置增强】写入日志 操作时间:Tue Mar 19 16:01:06 CST 2019
【环绕增强】当前空闲内存186MB
修改商品
【环绕增强】当前空闲内存186MB
【最终增强】Tue Mar 19 16:01:06 CST 2019
查询商品
【最终增强】Tue Mar 19 16:01:06 CST 2019
【异常抛出增强】异常信息:/ by zero

Spring事务管理

什么是事务

事务:逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。

事务的特性

  • 原子性:事务不可分割
  • 一致性:事务执行前后数据完整性保持一致
  • 隔离性:一个事务的执行不应该受到其他事务的干扰
  • 持久性:一旦事务结束,数据就持久化到数据库

不考虑隔离性引发的安全性问题

  • 读问题
    • 脏读:A事务读到B事务未提交的数据
    • 不可重复读:B事务在A事务两次读取数据之间,修改了数据,导致A事务两次读取结果不一致
    • 幻读/虚读:B事务在A事务批量修改数据时,插入了一条新的数据,导致数据库中仍有一条数据未被修改。
  • 写问题
    • 丢失更新:

解决读问题

  • 设置事务的隔离级别
    • Read uncommitted:未提交读,任何读问题都解决不了
    • Read committed:已提交读,解决脏读,但是不可重复读和幻读有可能发生
    • Repeatable read:重复读,解决脏读和不可重复读,但是幻读有可能发生
    • Serializable:解决所有读问题,因为禁止并行执行

Spring事务管理API

  • PlatformTransactionManager:平台事务管理器

    • DataSourceTransactionManager:底层使用JDBC管理事务
  • TransactionDefinition:事务定义信息

    ​ 用于定义事务相关的信息,隔离级别,超时信息,传播行为,是否只读……

  • TransactionStatus:事务的状态

    ​ 用于记录在事务管理过程中,事务的状态

API的关系:

Spring在进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务的管理,在事务管理过程中,产生各种状态,将这些状态信息记录到事务状态对象

Spring事务的传播行为

首先假设一个背景,Service1里的x()方法已经定义了一个事务,Service2里的y()方法也有一个事务,但现在新增一行代码在Service2的y()方法中要先调用Service1里的x()方法然后再执行本身的方法。这时就涉及到事务的传播行为

Spring中提供了7种传播行为

假设x()方法称为A,y()方法称为B

  • 保证多个操作在同一个事务中
    • PROPAGATION_REQUIRED(*):Spring事务隔离级别的默认值。如果A中有事务,则使用A中的事务。如果没有,则创建一个新的事务,将操作包含进来。
    • PROPAGATION_SUPPORTS:支持事务。如果A中有事务,使用A中的事务。如果A没有事务,则不使用事务。
    • PROPAGATION_MANDATORY:如果A中有事务,使用A中的事务。如果没有事务,则抛出异常。
  • 保证多个事务不在同一个事务中
    • PROPAGATION_REQUIRES_NEW(*):如果A中有事务,将A的事务挂起,创建新事务,只包含自身操作。如果A中没有事务,创建一个新事物,包含自身操作。
    • PROPAGATION_NOT_SUPPORTED:如果A中有事务,将A的事务挂起,不使用事务。
    • PROPAGATION_NEVER:如果A中有事务,则抛出异常。
  • 嵌套式事务
    • PROPAGATION_NESTED(*):嵌套事务,如果A中有事务,则按照A的事务执行,执行完成后,设置一个保存点,再执行B中的操作,如果无异常,则执行通过,如果有异常,则可以选择回滚到初始位置或者保存点。

Spring事务管理案例——转账情景

转账情景实现

首先创建接口AccountDao,定义两个方法分别是outin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* AccountDao
*
* @author Chen Rui
* @version 1.0
**/

public interface AccountDao {

/**
* 转出
*
* @param from 转出账户
* @param money 转出金额
*/
void out(String from, double money);

/**
* 转入
*
* @param to 转入账户
* @param money 转入金额
*/
void in(String to, double money);
}

接着创建实现类AccountDaoImpl实现outin方法并且继承JdbcSupport类。这样就可以直接使用父类的JDBCTemplate对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* AccountDao实现类
*
* @author Chen Rui
* @version 1.0
**/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

@Override
public void out(String from, double money) {
this.getJdbcTemplate().update("UPDATE account SET money = money - ? WHERE name = ?", money, from);
}

@Override
public void in(String to, double money) {
this.getJdbcTemplate().update("UPDATE account SET money = money + ? WHERE name = ?", money, to);
}
}

然后创建接口AccountrService,定义transfer方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* AccountService
*
* @author Chen Rui
* @version 1.0
**/
public interface AccountService {

/**
* 转账
* @param from 转出账户
* @param to 转入账户
* @param money 交易金额
*/
void transfer(String from, String to, Double money);
}

再创建类AccountServiceImpl实现该接口,并声明AccountDao引用并创建set方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* AccountService实现类
*
* @author Chen Rui
* @version 1.0
**/
public class AccountServiceImpl implements AccountService {

private AccountDao accountDao;

public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}

@Override
public void transfer(String from, String to, Double money) {
accountDao.out(from, money);
accountDao.in(to, money);
}
}

最后创建配置文件spring-tx-programmatic.xml,用来管理Bean。

引入数据库连接文件,配置数据源,创建Bean对象accountDao将数据源dataSource注入到accountDao中,再创建Bean对象accountService,将accountDao注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 编程式事务管理配置文件 -->

<!-- 配置Service -->
<bean id="accountService" class="learningspring.transaction.programmatic.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>

<!-- 配置Dao -->
<bean id="accountDao" class="learningspring.transaction.programmatic.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 引入数据库配置文件 -->
<context:property-placeholder location="db.properties"/>

<!-- 配置C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>

到此一个转账模拟业务就实现了,数据库表依然使用前面创建的account表,先查询当前数据库的数据。

编写测试方法,实现让姓名为Bob的账户向Jack转账1000元。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 编程式事务测试类
*
* @author Chen Rui
* @version 1.0
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = "classpath:spring-tx-programmatic.xml")
public class AppTest {

@Resource(name = "accountService")
private AccountService accountService;

@Test
public void test(){
accountService.transfer("Bob","Jack",1000d);
}
}

运行结果

现在对类AccountServiceImpl里的transfer方法进行修改,让其发生异常,再观察结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* AccountService实现类
*
* @author Chen Rui
* @version 1.0
**/
public class AccountServiceImpl implements AccountService {

private AccountDao accountDao;

public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}

@Override
public void transfer(String from, String to, Double money) {
accountDao.out(from, money);
// 抛出异常
int i = 1/0;
accountDao.in(to, money);
}

}

查询数据库数据

这时Bob账户的钱就少了1000元,而Jack账户也没有增加1000元。

所以就需要事务来进行管理。

编程式事务

所谓编程式事务,就是要在源码中编写事务相关的代码。实现编程式事务,首先要在AccountServiceImpl中声明TransactionTemplate对象,并创建set方法。然后修改transfer参数列表所有参数都用final(因为使用了匿名内部类)修饰,并修改方法体内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* AccountService实现类
*
* @author Chen Rui
* @version 1.0
**/
public class AccountServiceImpl implements AccountService {

private AccountDao accountDao;

private TransactionTemplate transactionTemplate;

public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}

@Override
public void transfer(final String from, final String to, final Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
accountDao.out(from, money);
// 抛出异常
int i = 1/0;
accountDao.in(to,money);
}
});
}
}

然后修改spring-tx-programmatic.xml文件,创建Bean对象transactionManagertransactionTemplate,并将transactionTemplate注入到accountService中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 编程式事务管理配置文件 -->

<!-- 配置Service -->
<bean id="accountService" class="learningspring.transaction.programmatic.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="transactionTemplate" ref="transactionTemplate"/>
</bean>

<!-- 配置Dao -->
<bean id="accountDao" class="learningspring.transaction.programmatic.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 引入数据库配置文件 -->
<context:property-placeholder location="db.properties"/>

<!-- 配置C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
</beans>

此时异常依然存在,数据库数据仍然是上次执行的结果状态

再次运行测试方法,并查询结果,观察是否发生变化

现在就实现了编程式事务,当出现异常时,数据库的数据就不会被修改。

声明式事务

XML配置方式

声明式事务即通过配置文件实现,利用的就是Spring的AOP

修改类AccountServiceImpl,删除TransactionTemplate对象,并修改transfer方法,保留异常代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* AccountService实现类
*
* @author Chen Rui
* @version 1.0
**/
public class AccountServiceImpl implements AccountService{

private AccountDao accountDao;

public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}

@Override
public void transfer(String from, String to, Double money) {
accountDao.out(from, money);
int i = 1/0;
accountDao.in(to,money);

}
}

然后创建配置文件spring-tx-declarative.xml,配置数据源即Bean对象,然后配置事务管理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 声明式事务管理配置文件 -->

<!-- 配置Service -->
<bean id="accountService" class="learningspring.transaction.declarative.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>

<!-- 配置Dao -->
<bean id="accountDao" class="learningspring.transaction.declarative.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 引入数据库配置文件 -->
<context:property-placeholder location="db.properties"/>

<!-- 配置C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>

接着就配置事务的增强,配置文件中加入以下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 配置事务的增强 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 配置事务的规则 根据实际业务修改-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>

<!-- AOP的配置 -->
<aop:config>
<aop:pointcut id="pointcut1" expression="execution(* learningspring.transaction.declarative.AccountServiceImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>

先查看当前数据库数据

编写测试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 声明式事务配置测试类
*
* @author Chen Rui
* @version 1.0
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = "classpath:spring-tx-declarative.xml")
public class AppTest {

@Resource(name = "accountService")
private AccountService accountService;

@Test
public void test(){
accountService.transfer("Bob","Jack",1000d);
}
}

运行查看结果,是否变化

至此就实现了声明式事务XML方式的配置。

注解配置方式

Spring的事务配置仍然支持注解配置

继续沿用spring-tx-declarative.xml文件,把事务增强和AOP相关的配置注释,并开启注解事务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">


<!-- 声明式事务管理配置文件 -->

<!-- 配置Service -->
<bean id="accountService" class="learningspring.transaction.declarative.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>

<!-- 配置Dao -->
<bean id="accountDao" class="learningspring.transaction.declarative.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 引入数据库配置文件 -->
<context:property-placeholder location="db.properties"/>

<!-- 配置C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置事务的增强 -->
<!--<tx:advice id="txAdvice" transaction-manager="transactionManager">-->
<!--<tx:attributes>-->
<!-- 配置事务的规则 -->
<!--<tx:method name="*" propagation="REQUIRED"/>-->
<!--</tx:attributes>-->
<!--</tx:advice>-->

<!-- AOP的配置 -->
<!--<aop:config>-->
<!--<aop:pointcut id="pointcut1" expression="execution(* learningspring.transaction.declarative.AccountServiceImpl.*(..))"/>-->
<!--<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>-->
<!--</aop:config>-->

<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

接下来就可以在业务层类上使用事务管理的注解了。修改AccountServiceImpl类,添加@Transactional注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* AccountService实现类
*
* @author Chen Rui
* @version 1.0
**/
@Transactional(rollbackFor = Exception.class)
public class AccountServiceImpl implements AccountService{

private AccountDao accountDao;

public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}


@Override
public void transfer(String from, String to, Double money) {
accountDao.out(from, money);
int i = 1/0;
accountDao.in(to,money);

}
}

再次运行测试方法,数据库也不会发生改变。