Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mozhu811/learning-spring
Spring学习笔记
https://github.com/mozhu811/learning-spring
spring spring-framework
Last synced: about 1 month ago
JSON representation
Spring学习笔记
- Host: GitHub
- URL: https://github.com/mozhu811/learning-spring
- Owner: mozhu811
- Created: 2019-03-21T01:20:12.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2022-12-16T03:22:51.000Z (almost 2 years ago)
- Last Synced: 2024-09-29T00:04:58.080Z (about 2 months ago)
- Topics: spring, spring-framework
- Language: Java
- Homepage: https://cruii.io/archives/2019111907160329543
- Size: 88.9 KB
- Stars: 150
- Watchers: 2
- Forks: 17
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Spring 学习笔记
* [IOC 和 DI 的概述](#ioc-%E5%92%8C-di-%E7%9A%84%E6%A6%82%E8%BF%B0)
* [IOC(Inversion of Controll)](#iocinversion-of-controll)
* [DI(Dependency Injection)](#didependency-injection)
* [Spring配置](#spring%E9%85%8D%E7%BD%AE)
* [ApplicationContext](#applicationcontext)
* [Bean的相关配置](#bean%E7%9A%84%E7%9B%B8%E5%85%B3%E9%85%8D%E7%BD%AE)
* [bean标签的id和name的配置](#bean%E6%A0%87%E7%AD%BE%E7%9A%84id%E5%92%8Cname%E7%9A%84%E9%85%8D%E7%BD%AE)
* [bean的生命周期的配置](#bean%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E7%9A%84%E9%85%8D%E7%BD%AE)
* [bean的作用范围的配置](#bean%E7%9A%84%E4%BD%9C%E7%94%A8%E8%8C%83%E5%9B%B4%E7%9A%84%E9%85%8D%E7%BD%AE)
* [Spring的Bean管理配置](#spring%E7%9A%84bean%E7%AE%A1%E7%90%86%E9%85%8D%E7%BD%AE)
* [Spring的Bean的实例化方式](#spring%E7%9A%84bean%E7%9A%84%E5%AE%9E%E4%BE%8B%E5%8C%96%E6%96%B9%E5%BC%8F)
* [无参构造方式(默认)](#%E6%97%A0%E5%8F%82%E6%9E%84%E9%80%A0%E6%96%B9%E5%BC%8F%E9%BB%98%E8%AE%A4)
* [静态工厂实例化方式](#%E9%9D%99%E6%80%81%E5%B7%A5%E5%8E%82%E5%AE%9E%E4%BE%8B%E5%8C%96%E6%96%B9%E5%BC%8F)
* [实例工厂实例化方式](#%E5%AE%9E%E4%BE%8B%E5%B7%A5%E5%8E%82%E5%AE%9E%E4%BE%8B%E5%8C%96%E6%96%B9%E5%BC%8F)
* [Spring的属性注入方式](#spring%E7%9A%84%E5%B1%9E%E6%80%A7%E6%B3%A8%E5%85%A5%E6%96%B9%E5%BC%8F)
* [构造方法方式的属性注入](#%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95%E6%96%B9%E5%BC%8F%E7%9A%84%E5%B1%9E%E6%80%A7%E6%B3%A8%E5%85%A5)
* [Set方法方式的属性注入](#set%E6%96%B9%E6%B3%95%E6%96%B9%E5%BC%8F%E7%9A%84%E5%B1%9E%E6%80%A7%E6%B3%A8%E5%85%A5)
* [为Bean注入引用类型的数据](#%E4%B8%BAbean%E6%B3%A8%E5%85%A5%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B%E7%9A%84%E6%95%B0%E6%8D%AE)
* [P名称空间的属性注入(Spring2\.5)](#p%E5%90%8D%E7%A7%B0%E7%A9%BA%E9%97%B4%E7%9A%84%E5%B1%9E%E6%80%A7%E6%B3%A8%E5%85%A5spring25)
* [SpEL方式的属性注入(Spring3)](#spel%E6%96%B9%E5%BC%8F%E7%9A%84%E5%B1%9E%E6%80%A7%E6%B3%A8%E5%85%A5spring3)
* [注入集合类型的数据](#%E6%B3%A8%E5%85%A5%E9%9B%86%E5%90%88%E7%B1%BB%E5%9E%8B%E7%9A%84%E6%95%B0%E6%8D%AE)
* [Spring分模块开发的配置](#spring%E5%88%86%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91%E7%9A%84%E9%85%8D%E7%BD%AE)
* [Spring开发中的常用注解](#spring%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3)
* [@Component](#component)
* [@Value](#value)
* [@Autowired](#autowired)
* [@Resource](#resource)
* [@PostConstruct 和 @PreDestroy](#postconstruct-%E5%92%8C-predestroy)
* [@Scope](#scope)
* [基于XML配置和基于注解配置的对比](#%E5%9F%BA%E4%BA%8Exml%E9%85%8D%E7%BD%AE%E5%92%8C%E5%9F%BA%E4%BA%8E%E6%B3%A8%E8%A7%A3%E9%85%8D%E7%BD%AE%E7%9A%84%E5%AF%B9%E6%AF%94)
* [Spring AOP](#spring-aop)
* [AOP的概述](#aop%E7%9A%84%E6%A6%82%E8%BF%B0)
* [AOP的案例(应用场景)](#aop%E7%9A%84%E6%A1%88%E4%BE%8B%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF)
* [AOP底层实现原理](#aop%E5%BA%95%E5%B1%82%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86)
* [JDK动态代理](#jdk%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86)
* [JDK动态代理案例](#jdk%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E6%A1%88%E4%BE%8B)
* [Cglib动态代理](#cglib%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86)
* [Cglib动态代理案例](#cglib%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E6%A1%88%E4%BE%8B)
* [Spring中的AOP实现——AspectJ](#spring%E4%B8%AD%E7%9A%84aop%E5%AE%9E%E7%8E%B0aspectj)
* [AOP开发中的相关术语](#aop%E5%BC%80%E5%8F%91%E4%B8%AD%E7%9A%84%E7%9B%B8%E5%85%B3%E6%9C%AF%E8%AF%AD)
* [AspectJ的XML配置案例](#aspectj%E7%9A%84xml%E9%85%8D%E7%BD%AE%E6%A1%88%E4%BE%8B)
* [Spring中常用的增强类型](#spring%E4%B8%AD%E5%B8%B8%E7%94%A8%E7%9A%84%E5%A2%9E%E5%BC%BA%E7%B1%BB%E5%9E%8B)
* [前置增强](#%E5%89%8D%E7%BD%AE%E5%A2%9E%E5%BC%BA)
* [后置增强](#%E5%90%8E%E7%BD%AE%E5%A2%9E%E5%BC%BA)
* [环绕增强](#%E7%8E%AF%E7%BB%95%E5%A2%9E%E5%BC%BA)
* [异常抛出增强](#%E5%BC%82%E5%B8%B8%E6%8A%9B%E5%87%BA%E5%A2%9E%E5%BC%BA)
* [最终增强](#%E6%9C%80%E7%BB%88%E5%A2%9E%E5%BC%BA)
* [AOP切入点表达式语法](#aop%E5%88%87%E5%85%A5%E7%82%B9%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%AF%AD%E6%B3%95)
* [AspectJ的注解配置案例](#aspectj%E7%9A%84%E6%B3%A8%E8%A7%A3%E9%85%8D%E7%BD%AE%E6%A1%88%E4%BE%8B)
* [Spring JDBC Template](#spring-jdbc-template)
* [JDBC Template的入门](#jdbc-template%E7%9A%84%E5%85%A5%E9%97%A8)
* [基本使用](#%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8)
* [数据库连接池](#%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0)
* [DBCP连接池的配置](#dbcp%E8%BF%9E%E6%8E%A5%E6%B1%A0%E7%9A%84%E9%85%8D%E7%BD%AE)
* [C3P0连接池配置](#c3p0%E8%BF%9E%E6%8E%A5%E6%B1%A0%E9%85%8D%E7%BD%AE)
* [完成基本的CRUD操作](#%E5%AE%8C%E6%88%90%E5%9F%BA%E6%9C%AC%E7%9A%84crud%E6%93%8D%E4%BD%9C)
* [插入操作](#%E6%8F%92%E5%85%A5%E6%93%8D%E4%BD%9C)
* [修改操作](#%E4%BF%AE%E6%94%B9%E6%93%8D%E4%BD%9C)
* [删除操作](#%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C)
* [查询操作](#%E6%9F%A5%E8%AF%A2%E6%93%8D%E4%BD%9C)
* [查询某个属性](#%E6%9F%A5%E8%AF%A2%E6%9F%90%E4%B8%AA%E5%B1%9E%E6%80%A7)
* [查询返回单个对象](#%E6%9F%A5%E8%AF%A2%E8%BF%94%E5%9B%9E%E5%8D%95%E4%B8%AA%E5%AF%B9%E8%B1%A1)
* [查询返回对象集合](#%E6%9F%A5%E8%AF%A2%E8%BF%94%E5%9B%9E%E5%AF%B9%E8%B1%A1%E9%9B%86%E5%90%88)
* [Spring事务管理](#spring%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86)
* [什么是事务](#%E4%BB%80%E4%B9%88%E6%98%AF%E4%BA%8B%E5%8A%A1)
* [事务的特性](#%E4%BA%8B%E5%8A%A1%E7%9A%84%E7%89%B9%E6%80%A7)
* [不考虑隔离性引发的安全性问题](#%E4%B8%8D%E8%80%83%E8%99%91%E9%9A%94%E7%A6%BB%E6%80%A7%E5%BC%95%E5%8F%91%E7%9A%84%E5%AE%89%E5%85%A8%E6%80%A7%E9%97%AE%E9%A2%98)
* [解决读问题](#%E8%A7%A3%E5%86%B3%E8%AF%BB%E9%97%AE%E9%A2%98)
* [Spring事务管理API](#spring%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86api)
* [Spring事务的传播行为](#spring%E4%BA%8B%E5%8A%A1%E7%9A%84%E4%BC%A0%E6%92%AD%E8%A1%8C%E4%B8%BA)
* [Spring事务管理案例——转账情景](#spring%E4%BA%8B%E5%8A%A1%E7%AE%A1%E7%90%86%E6%A1%88%E4%BE%8B%E8%BD%AC%E8%B4%A6%E6%83%85%E6%99%AF)
* [转账情景实现](#%E8%BD%AC%E8%B4%A6%E6%83%85%E6%99%AF%E5%AE%9E%E7%8E%B0)
* [编程式事务](#%E7%BC%96%E7%A8%8B%E5%BC%8F%E4%BA%8B%E5%8A%A1)
* [声明式事务](#%E5%A3%B0%E6%98%8E%E5%BC%8F%E4%BA%8B%E5%8A%A1)
* [XML配置方式](#xml%E9%85%8D%E7%BD%AE%E6%96%B9%E5%BC%8F)
* [注解配置方式](#%E6%B3%A8%E8%A7%A3%E9%85%8D%E7%BD%AE%E6%96%B9%E5%BC%8F)# 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`
**无论使用何种方式,配置文件时都是相同的**
```xml
```
```java
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 + '\'' +
'}';
}
}
``````java
@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的实例化方式
#### 无参构造方式(默认)
```java
/**
* //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 +
'}';
}
}```
```xml
```
#### 静态工厂实例化方式
```java
/**
*
* @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 +
'}';
}
}
``````java
package learningspring.ioc.examples.demo3;/**
*
* @author Chen Rui
* @version 1.0
**/
public class CarFactory {public static Car createCar(){
return new Car();
}
}
``````xml
```
#### 实例工厂实例化方式
```java
/**
* //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 +
'}';
}
}```
```java
/**
* //TODO
*
* @author Chen Rui
* @version 1.0
**/
public class DogFactory {public Dog createDog(){
return new Dog();
}
}
``````xml
```
### Spring的属性注入方式
#### 构造方法方式的属性注入
```xml
```
#### Set方法方式的属性注入
```xml
```
#### 为Bean注入引用类型的数据
```xml
```
#### P名称空间的属性注入(Spring2.5)
+ 通过引入p名称空间完成属性注入
+ 普通属性:p:属性名=“值”
+ 对象属性:p:属性名-ref=“值”```xml
```
#### SpEL方式的属性注入(Spring3)
SpEL:Spring Expresssion Language 的表达式语言
语法:#{表达式}
```xml
```
### 注入集合类型的数据
```java
/**
* 注入集合类型的数据测试
*
* @author Chen Rui
* @version 1.0
**/
public class CollectionBean {private String[] strs;
private List list;
private Set set;
private Map map;public void setStrs(String[] strs) {
this.strs = strs;
}public void setList(List list) {
this.list = list;
}public void setSet(Set set) {
this.set = set;
}public void setMap(Map map) {
this.map = map;
}@Override
public String toString() {
return "CollectionBean{" +
"strs=" + Arrays.toString(strs) +
", list=" + list +
", set=" + set +
", map=" + map +
'}';
}
}```
```xml
Tom
Jack
Lucy
Lily
aaa
bbb
ccc
```
### Spring分模块开发的配置
+ 加载配置文件时,直接加载多个配置文件
```java
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml", "applicationContext2.xml");
```+ 在一个配置文件中引入多个配置文件
```xml
```
# Spring开发中的常用注解
## @Component
该注解在类上使用,使用该注解就相当于在配置文件中配置了一个Bean,例如:
```java
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDaoImpl.save");
}
}
```相当于以下内容:
```xml
```
该注解有3个衍生注解:
+ **@Controller:修饰Web 层类**
+ **@Service:修饰Service层类**
+ **@Repository:修饰Dao层类**## @Value
该注解用于给属性注入值,有2种用法
+ 如果有set方法,则需要将该注解添加到set方法上
+ 如果没有set方法,则需要将该注解添加到属性上```java
/**
* 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`注解的值,也能编译成功)
```java
@Service("userService")
public class UserServiceImpl implements UserService {@Autowired
@Qualifier("userDao")
private UserDao userDao;@Override
public void save() {
System.out.println("UserServiceImpl.save");
userDao.save();
}
}
``````java
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDaoImpl.save");
}
}
```## @Resource
该注解也可以用于属性注入,通常情况下使用**@Resource注解**,默认按照**名称**完成属性注入。
该注解由J2EE提供,需要导入包`javax.annotation.Resource`。
`@Resource`有两个重要的属性:`name`和`type`,而Spring将`@Resource`注解的`name`属性解析为bean的名字,而`type`属性则解析为bean的类型。所以,如果使用`name`属性,则使用byName的自动注入策略,而使用`type`属性时则使用byType自动注入策略。如果既不制定`name`也不制定`type`属性,这时将通过反射机制使用byName自动注入策略。
```java
/**
* UserController
*
* @author Chen Rui
* @version 1.0
**/
@Controller("userController")
public class UserController {
@Resource(name = "userService")
private UserService userService;
}
``````java
/**
* 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();
}
}```
```java
/**
* 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生命周期相关的注解,在配置文件中,使用到了i`nit-method`参数来配置Bean初始化之前要执行的方法,该注解也是同样的作用。将该注解添加到想在初始化之前执行的目标方法上,就可以实现该功能,而后者则是Bean生命周期中`destroy-method`目标方法,使用该注解在指定方法上,即可实现在Bean销毁之前执行该方法。
```java
/**
* 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```java
/**
* 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的定义 | \ | @Component或衍生注解@Controller,@Service和@Repository |
| Bean的名称 | 通过id或name指定 | @Component(“Bean的id”) |
| Bean的属性注入 | \或者通过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的案例(应用场景)
背景:某项目已经写好了保存到数据库的方法。假设现在需要添加一个新的功能,例如权限校验,在保存到数据库之前要对用户权限进行校验。
```java
public class UserDaoImpl implements UserDao {
@Override
public void save(){
...
}
}
```现在需要多加一个需求,在用户将数据保存到数据库之前,进行权限校验。
此时通常就会在该方法中添加一个方法来进行权限校验然后在save方法中调用。
```java
public class UserDaoImpl implements UserDao {
@Override
public void save(){
checkPri();
// 保存到数据库
}
private void checkPri(){
// 权限校验
}
}
```用这样的方法来实现,弊端就是只能在这一个类中使用,通常一个项目中有许多的方法都可能需要执行权限校验,此时就要在每个类中编写同样的代码,所以该方法并不科学。
此时就有了一个更好的方法,即**纵向继承**。
定义一个通用的Dao,在通用的Dao中编写权限校验的方法。
```java
public class BaseDao{
public void checkPri(){
// 权限校验
}
}
```然后每一个不同的类都去继承这个类,再调用该方法
```java
public class UserDaoImpl extends BaseDao implements UserDao{
@Override
public void save(){
checkPri();
// 保存到数据库
}
}
```此时就只需要维护`BaseDao`中的一份代码就可以,大大减轻了工作量,提高了效率。
但AOP的思想更高一步,不采用纵向继承,而采用**横向抽取**来取代
```java
public class UserDaoImpl implements UserDao{
@Override
public void save(){
// 保存到数据库
}
}
```横向抽取机制实质上就是**代理机制**,通过创建`UserDaoImpl`类的代理类,通过代理类来调用权限校验的方法。
## AOP底层实现原理
AOP的实现使用了动态代理技术,动态代理分为两种
+ JDK动态代理:只能对实现了接口的类产生代理
+ Cglib动态代理(类似于javassist的第三方代理技术):对没有实现接口的类产生代理对象,即生成子类对象。### JDK动态代理
#### JDK动态代理案例
该案例实现一个计算器的日志功能
首先创建一个接口`Calculator`
```java
/**
* 计算器接口
*
* @author Chen Rui
* @version 1.0
**/
public interface Calculator {/**
* 加法
* @param a 实数
* @param b 实数
* @return 相加结果
*/
int add(int a, int b);/**
* 减法
* @param a 实数,被减数
* @param b 实数,减数
* @return 相减结果
*/
int sub(int a, int b);/**
* 乘法
* @param a 实数
* @param b 实数
* @return 相乘结果
*/
int mul(int a, int b);/**
* 除法
* @param a 实数,被除数
* @param b 实数,除数
* @return 相除结果
*/
int div(int a, int b);
}
```接着创建一个类`CalculatorImpl`来实现该接口并重写方法
```java
/**
* 计算器实现类
*
* @author Chen Rui
* @version 1.0
**/
public class CalculatorImpl implements Calculator {@Override
public int add(int a, int b) {
return a + b;
}@Override
public int sub(int a, int b) {
return a - b;
}@Override
public int mul(int a, int b) {
return a * b;
}@Override
public int div(int a, int b) {
if (b == 0){
System.out.println("除数不能为0");
return 0;
}
return a / b;
}
}```
在测试类中测试该计算器代码
```java
/**
* @author Chen Rui
* @version 1.0
**/
public class AppTest {
@Test
public void test() {
Calculator target = new CalculatorImpl();
int a = 10;
int b = 10;
System.out.println("res --> " + target.add(a, b));System.out.println("res --> " + target.mul(a, b));
System.out.println("res --> " + target.sub(a, b));
System.out.println("res --> " + target.div(a, b));
}
}
```此时控制台的输出结果为:
```
res --> 20
res --> 100
res --> 0
res --> 1
```现在为该计算器增加**打印日志**的功能
创建一个计算器的代理类`CalculatorLoggingProxy`,在类中首先定义被代理的目标对象target,并通过构造函数进行赋值。
```java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Date;/**
* 计算器代理类
* 实现扩展打印日志功能
*
* @author Chen Rui
* @version 1.0
**/
public class CalculatorProxy {
/**
* 被代理的对象
*/
private Calculator target;public CalculatorProxy(Calculator target) {
this.target = target;
}public Calculator createProxy(){
Calculator proxy;ClassLoader classLoader = target.getClass().getClassLoader();
Class[] interfaces = new Class[]{Calculator.class};
InvocationHandler handler = new InvocationHandler() {
/**
* @param proxy 正在返回的代理对象,一般在invoke方法中都不使用该对象
* 如果使用该对象,则会引发栈内存溢出。因为会循环调用invoke方法。
* @param method 正在被调用的方法
* @param args 调用方式时传入的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法名
String methodName = method.getName();
// 输出日志逻辑
System.out.println(new Date() + ": The method " + methodName + " begins with " + Arrays.asList(args));
// 执行方法
Object result = method.invoke(target, args);
// 输出日志逻辑
System.out.println(new Date() + ": The method " + methodName + " ends with " + result);
return result;
}
};proxy = (Calculator) Proxy.newProxyInstance(classLoader,interfaces,handler);
return proxy;
}
}```
此时重新编写测试方法
```java
/**
* @author Chen Rui
* @version 1.0
**/
public class AppTest {
@Test
public void test() {
Calculator target = new CalculatorImpl();
// 创建代理对象
Calculator proxy = new CalculatorProxy(target).createProxy();
int a = 10;
int b = 10;
System.out.println("res --> " + proxy.add(a, b));System.out.println("res --> " + proxy.mul(a, b));
System.out.println("res --> " + proxy.sub(a, b));
System.out.println("res --> " + proxy.div(a, b));
}
}
```到此就完成了在不改变`CalculatorImpl`类的源代码的情况下,实现对计算器的功能增加,实现了日志打印的功能。此时控制台的打印内容为
```
Sun Mar 17 20:36:26 CST 2019: The method add begins with [10, 10]
Sun Mar 17 20:36:26 CST 2019: The method add ends with 20
res --> 20
Sun Mar 17 20:36:26 CST 2019: The method mul begins with [10, 10]
Sun Mar 17 20:36:26 CST 2019: The method mul ends with 100
res --> 100
Sun Mar 17 20:36:26 CST 2019: The method sub begins with [10, 10]
Sun Mar 17 20:36:26 CST 2019: The method sub ends with 0
res --> 0
Sun Mar 17 20:36:26 CST 2019: The method div begins with [10, 10]
Sun Mar 17 20:36:26 CST 2019: The method div ends with 1
res --> 1
```### Cglib动态代理
#### Cglib动态代理案例
同样来实现一个对计算器来增加打印日志功能
首先创建计算器类`Calculator`
```java
/**
* 计算器类
*
* @author Chen Rui
* @version 1.0
**/
public class Calculator {public int add(int a, int b) {
return a + b;
}public int sub(int a, int b) {
return a - b;
}public int mul(int a, int b) {
return a * b;
}public int div(int a, int b) {
if (b == 0){
System.out.println("除数不能为0");
return 0;
}
return a / b;
}
}```
此时需要导入cglib的jar包,在maven中添加依赖
```xml
cglib
cglib
2.2.2```
接着创建计算器的代理类`CalculatorProxy`并且实现`MethodInterceptor`接口并重写`intercept`方法。
在类中首先定义被代理的目标对象,并通过构造函数进行赋值。然后创建`createProxy()`方法返回被增强的计算器对象。
```java
/**
* 计算器代理类
*
* @author Chen Rui
* @version 1.0
**/
public class CalculatorProxy implements MethodInterceptor {/**
* 被代理的对象
*/
private Calculator target;public CalculatorProxy(Calculator target) {
this.target = target;
}public Calculator createProxy(){
// 1.创建cglib的核心类对象
Enhancer enhancer = new Enhancer();// 2.设置父类
enhancer.setSuperclass(target.getClass());// 3.设置回调(类似于jdk动态代理中的InvocationHandler对象)
enhancer.setCallback(this);// 4.创建代理对象
Calculator proxy = (Calculator) enhancer.create();return proxy;
}@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 获取方法名
String methodName = method.getName();
// 输出日志逻辑
System.out.println(new Date() + ": The method " + methodName + " begins with " + Arrays.asList(args));
// 执行方法
Object result = methodProxy.invokeSuper(proxy, args);
// 输出日志逻辑
System.out.println(new Date() + ": The method " + methodName + " ends with " + result);
return result;
}
}```
## Spring中的AOP实现——AspectJ
### AOP开发中的相关术语
```java
public class UserDao{
public void save(){}
public void query(){}
public void update(){}
public void delete(){}
}
```- joinpoint(连接点) : 可以被拦截到的点。save(), query(),update(),delete()方法都可以增强,这些方法就可以称为连接点。
- pointcut(切入点):真正被拦截到的点。在实际开发中,可以只对save()方法进行增强,那么save()方法就是切入点。
- advice(增强):方法层面的增强,现在可以对save()方法进行权限校验,权限校验(checkPri())的方法称为增强。
- introduction(引介):类层面的增强。
- target(目标):被增强的对象。
- weaving(织入):将增强(advice)应用到目标(target)的过程
- proxy(代理):代理对象,被增强以后的代理对象
- aspect(切面):多个增强(advice)和多个切入点(pointcut)的组合### AspectJ的XML配置案例
首先创建一个接口`ProductDao`,在里面定义添加商品,查询商品,修改商品,删除商品方法。
```java
/**
* ProductDao
*
* @author Chen Rui
* @version 1.0
**/
public interface ProductDao {/**
* 添加商品
*/
void save();/**
* 删除商品
*/
void delete();/**
* 修改商品
*/
void modify();/**
* 查询商品
*/
void query();
}```
接着创建一个类`ProductDaoImpl`来实现该接口
```java
/**
* ProductDao的实现类
*
* @author Chen Rui
* @version 1.0
**/
public class ProductDaoImpl implements ProductDao {@Override
public void save() {
System.out.println("添加商品");
}@Override
public void delete() {
System.out.println("删除商品");
}
@Override
public void modify() {
System.out.println("修改商品");
}
@Override
public void query() {
System.out.println("查询商品");
}
}```
现在目的就是给`save()`方法进行增强,使得在调用`save()`方法前进行权限校验。
要实现此功能,先创建一个**增强类**,或者叫**切面类**。里面编写要增强的功能,例如权限校验。
创建增强类`ProductEnhancer`
```java
/**
* ProductDao的增强类(切面类)
*
* @author Chen Rui
* @version 1.0
**/
public class ProductEnhancer {public void checkPri(){
System.out.println("【前置增强】权限校验");
}}
```
然后创建配置文件`aspectj-xml.xml`来配置,该文件名此案例仅用于演示,实际开发中不要采取此名,依据实际需求编写。
```xml
```
至此切入点及切面都已配置完成,编写测试类和方法
```java
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import javax.annotation.Resource;
/**
* AspectJ的XML方式配置测试类
*
* @author Chen Rui
* @version 1.0
**/@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:aspectj-xml.xml")
public class AppTest {@Resource(name = "productDao")
private ProductDao productDao;@Test
public void test(){
// 对save方法进行增强
productDao.save();productDao.delete();
productDao.modify();
productDao.query();
}
}```
运行`test()`方法,控制台打印结果如下:
```
【前置增强】权限校验
添加商品
删除商品
修改商品
查询商品
```至此就实现了在不修改`ProductDaoImpl`类的情况下,对其中的`save()`方法进行增强。
### Spring中常用的增强类型
#### 前置增强
在目标方法执行之前执行,可以获得切入点的信息
修改之前的`ProductEnhancer`类的`checkPri()`方法的参数。
```java
import org.aspectj.lang.JoinPoint;/**
* ProductDao的增强类(切面类)
*
* @author Chen Rui
* @version 1.0
**/
public class ProductEnhancer {public void checkPri(JoinPoint joinPoint){
System.out.println("【前置增强】权限校验" + joinPoint);
}}
```执行测试方法,控制台输出
```
【前置增强】权限校验execution(void learningspring.aop.aspectj.xml.demo2.ProductDao.save())
添加商品
删除商品
修改商品
查询商品
```#### 后置增强
在目标方法执行之后执行,可以获得方法的返回值
首先修改`ProductDao`中的`delete()`方法的返回值类型,改成String
```java
/**
* ProductDao
*
* @author Chen Rui
* @version 1.0
**/
public interface ProductDao {/**
* 添加商品
*/
void save();/**
* 删除商品
*/
String delete();/**
* 修改商品
*/
void modify();/**
* 查询商品
*/
void query();
}```
再修改`ProductDaoImpl`中的`delete()`方法
```java
/**
* 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("查询商品");
}
}```
修改`ProductEnhancer`类,添加`writeLog()`方法,实现写日志功能
```java
/**
* ProductDao的增强类(切面类)
*
* @author Chen Rui
* @version 1.0
**/
public class ProductEnhancer {/**
* 前置增强案例
* 在调用save方法之前进行权限校验
* @param joinPoint 切入点对象
*/
public void checkPri(JoinPoint joinPoint){
System.out.println("【前置增强】权限校验" + joinPoint);
}/**
* 后置增强案例
* 在调用delete方法之后,写入日志记录操作时间
* @param result 目标方法返回的对象
*/
public void writeLog(Object result){
System.out.println("【后置增强】写入日志 操作时间:" + result.toString());
}
}
```然后修改`aspectj.xml`配置文件,配置新的**切入点**和**切面**
```xml
```
执行测试方法,控制台打印结果
```
【前置增强】权限校验execution(void learningspring.aop.aspectj.xml.demo2.ProductDao.save())
添加商品
删除商品
【后置增强】写入日志 操作时间:Tue Mar 19 15:59:48 CST 2019
修改商品
查询商品
```#### 环绕增强
在目标方法执行之前和之后都执行
利用环绕增强来实现在调用`modify()`方法前后进行性能监控
首先修改`ProductEnhancer`类,添加`monitor()`方法
```java
/**
* ProductDao的增强类(切面类)
*
* @author Chen Rui
* @version 1.0
**/
public class ProductEnhancer {/**
* 前置增强案例
* 在调用save方法之前进行权限校验
* @param joinPoint 切入点对象
*/
public void checkPri(JoinPoint joinPoint){
System.out.println("【前置增强】权限校验" + joinPoint);
}/**
* 后置增强案例
* 在调用delete方法之后,写入日志记录操作时间
* @param result 目标方法返回的对象
*/
public void writeLog(Object result){
System.out.println("【后置增强】写入日志 操作时间:" + result.toString());
}/**
* 环绕增强
* 在调用modify方法前后,显示性能参数
* @param joinPoint 切入点对象
* @throws Throwable 可抛出的异常
*/
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;
}
}```
然后再修改`aspectj.xml`配置文件,添加新的**切入点**和**切面**
```xml
```
运行测试方法,控制台打印结果:
```
【前置增强】权限校验execution(void learningspring.aop.aspectj.xml.demo2.ProductDao.save())
添加商品
删除商品
【后置增强】写入日志 操作时间:Tue Mar 19 15:58:49 CST 2019
【环绕增强】当前空闲内存185MB
修改商品
【环绕增强】当前空闲内存185MB
查询商品
```#### 异常抛出增强
在程序出现异常时执行
利用异常抛出增强来实现获取异常信息的功能
首先修改`ProductDaoImpl`中的`query()`方法,使该方法抛出异常
```java
/**
* ProductDao的实现类
*
* @author Chen Rui
* @version 1.0
**/
public class ProductDaoImpl implements ProductDao {@Override
public void save() {
System.out.println("添加商品");
}@Override
public void query() {
System.out.println("查询商品");
int a = 1/0;
}@Override
public void modify() {
System.out.println("修改商品");
}@Override
public String delete() {
System.out.println("删除商品");
return new Date().toString();
}
}
```接着修改`ProductEnhancer`类,添加`exception()`方法
```java
/**
* ProductDao的增强类(切面类)
*
* @author Chen Rui
* @version 1.0
**/
public class ProductEnhancer {/**
* 前置增强案例
* 在调用save方法之前进行权限校验
* @param joinPoint 切入点对象
*/
public void checkPri(JoinPoint joinPoint){
System.out.println("【前置增强】权限校验" + joinPoint);
}/**
* 后置增强案例
* 在调用delete方法之后,写入日志记录操作时间
* @param result 目标方法返回的对象
*/
public void writeLog(Object result){
System.out.println("【后置增强】写入日志 操作时间:" + result.toString());
}/**
* 环绕增强
* 在调用modify方法前后,显示性能参数
* @param joinPoint 切入点对象
* @throws Throwable 可抛出的异常
*/
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 异常对象
*/
public void exception(Throwable ex){
System.out.println("【异常抛出增强】" + "异常信息:" +ex.getMessage());
}
}```
然后再修改`aspectj-xml.xml`配置文件,添加新的**切入点**和**切面**
```xml
```
最后执行测试方法,控制台输出结果:
```
【前置增强】权限校验execution(void learningspring.aop.aspectj.xml.demo2.ProductDao.save())
添加商品
删除商品
【后置增强】写入日志 操作时间:Tue Mar 19 15:58:16 CST 2019
【环绕增强】当前空闲内存183MB
修改商品
【环绕增强】当前空闲内存183MB
查询商品
【异常抛出增强】异常信息:/ by zero
```#### 最终增强
无论代码是否有异常最终都会执行
继续在异常抛出增强的代码修改,实现无论是否抛出异常都会打印当前时间信息
首先修改`ProductEnhancer`类,添加`finallyAdvice()`方法
```java
/**
* ProductDao的增强类(切面类)
*
* @author Chen Rui
* @version 1.0
**/
public class ProductEnhancer {/**
* 前置增强案例
* 在调用save方法之前进行权限校验
* @param joinPoint 切入点对象
*/
public void checkPri(JoinPoint joinPoint){
System.out.println("【前置增强】权限校验" + joinPoint);
}/**
* 后置增强案例
* 在调用delete方法之后,写入日志记录操作时间
* @param result 目标方法返回的对象
*/
public void writeLog(Object result){
System.out.println("【后置增强】写入日志 操作时间:" + result.toString());
}/**
* 环绕增强
* 在调用modify方法前后,显示性能参数
* @param joinPoint 切入点对象
* @throws Throwable 可抛出的异常
*/
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 异常对象
*/
public void exception(Throwable ex){
System.out.println("【异常抛出增强】" + "异常信息:" +ex.getMessage());
}/**
* 最终增强
* 无论query方法是否抛出异常都打印当前时间
*/
public void finallyAdvice(){
System.out.println("【最终增强】" + new Date().toString());
}
}```
修改`aspectj.xml`配置文件,添加新的**切面**
```xml
```
最后运行测试代码,控制台输出结果:
```
【前置增强】权限校验execution(void learningspring.aop.aspectj.xml.demo2.ProductDao.save())
添加商品
删除商品
【后置增强】写入日志 操作时间:Tue Mar 19 15:57:01 CST 2019
【环绕增强】当前空闲内存183MB
修改商品
【环绕增强】当前空闲内存183MB
查询商品
【最终增强】Tue Mar 19 15:57:01 CST 2019
【异常抛出增强】异常信息:/ by zero
```### 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`
```java
/**
* ProductDao接口
*
* @author Chen Rui
* @version 1.0
**/
public interface ProductDao {/**
* 添加商品
*/
void save();/**
* 查询商品
*/
void query();/**
* 修改商品
*/
void modify();/**
* 删除商品
*/
String delete();
}
```然后创建一个Dao实现类`ProductDaoImpl`
```java
/**
* 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`:最终增强
```java
/**
* 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());
}
}```
编写测试方法
```java
/**
* 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();
}
}
```运行,控制台输出
```
【前置增强】权限校验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 JDBC Template
Spring提供了提供了多种持久层技术的模板类
| ORM持久化技术 | 模板类 |
| --------------- | ---------------------------------------------------- |
| JDBC | org.springframework.jdbc.core.JdbcTemplate |
| Hibernate3.0 | org.springframework.orm.hibernate3.HibernateTemplate |
| IBatis(Mybatis) | org.springframework.orm.ibatis.SqlMapClientTemplate |
| JPA | org.springframework.orm.jpa.JpaTemplate |## JDBC Template的入门
首先引入jar包,在`pom.xml`文件中加入`spring-jdbc`,`spring-tx`,`mysql-connector-java`(本案例使用的是MySQL8)三个依赖。
```xml
org.springframework
spring-jdbc
4.3.14.RELEASEorg.springframework
spring-tx
4.3.14.RELEASEmysql
mysql-connector-java
8.0.15```
然后创建数据库表,本例使用的MySQL8
```mysql
create table account
(
id int auto_increment
primary key,
name varchar(8) not null,
money double default 0
)
comment '账户表';
```### 基本使用
最基本的使用,不依赖于Spring 的管理,手动创建对象,采用硬编码的方式进行属性注入。不推荐使用该方法。
```java
public class AppTest {
/**
* 硬编码
*/
@Test
public void test1(){
// 创建连接池
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/springjdbc?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false ");
dataSource.setUsername("root");
dataSource.setPassword("123456");
// 创建JDBC Template
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
int i = jdbcTemplate.update("INSERT INTO account VALUES (null ,?,?)", "Tom", 20000d);
if (i > 0){
System.out.println("Update Successful");
}
}
}
```接下来使用第二种方法,把连接池对象和模板(Template)都交给Spring来管理
创建`spring-jdbc.xml`该文件用来管理Bean
```xml
```
在测试类中加入相应的注解,以及配置文件信息,编写新的测试方法
```java
/**
* Spring JDBC Template的使用
*
* @author Chen Rui
* @version 1.0
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-jdbc.xml")
public class AppTest {
/**
* Spring 配置文件方式
* 把连接池和模板(Template)都交给spring管理
* 日志信息:Loaded JDBC driver: com.mysql.cj.jdbc.Driver
* 是使用的默认的连接池
*/
@Resource(name = "jdbcTemplate")
private JdbcTemplate jdbcTemplate;@Test
public void test2(){
int i = jdbcTemplate.update("INSERT INTO account VALUES (null ,?,?)", "Jack", 30000d);
if (i > 0){
System.out.println("Update Successful");
}
}
```通过`@Resource`注解从IOC容器中获取到模板对象,然后通过该模板对象来操作数据库。
这样就完成了Spring JDBC Template的最基本使用
### 数据库连接池
在实际开发中,可能并不会使用默认的连接池,而是去使用一些开源的数据库连接池,在该例中介绍两种数据库连接池DBCP和C3P0
#### DBCP连接池的配置
首先创建连接数据库的配置文件`db.properties`,注意,不同的MySQL版本可能url信息会不同,比如MySQL8就需要添加`serverTimezone`参数。
```properties
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springjdbc?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
jdbc.username=root
jdbc.password=123456
```接着创建一个新的配置文件`spring-dbcp.xml`和前面的配置文件做区分。
利用`context:property-placeholder`标签引入`db.properties`配置文件,通过`${key}`的方式来获取对应的value。
```xml
```
编写测试方法
```java
/**
* Spring JDBC Template的使用
*
* @author Chen Rui
* @version 1.0
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-dbcp.xml")
public class AppTest {
//使用开源的数据库连接池进行配置/**
* 使用DBCP连接池
*/
@Resource(name = "jdbcTemplateDBCP")
private JdbcTemplate jdbcTemplateDBCP;@Test
public void test3(){
int i = jdbcTemplateDBCP.update("INSERT INTO account VALUES (null ,?,?)", "Lucy", 40000d);
if (i > 0){
System.out.println("Update Successful");
}
}
}
```#### C3P0连接池配置
同样是创建一个新的配置文件`spring-c3p0.xml`,以作区分,同时也要引入数据库配置文件`db.properties`
要注意`property`标签的`name`属性和前面的配置文件**稍有不同**
```xml
```
编写测试方法
```java
/**
* Spring JDBC Template的使用
*
* @author Chen Rui
* @version 1.0
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-*.xml")
public class AppTest {
//使用开源的数据库连接池进行配置/**
* 使用C3P0连接池
*/
@Resource(name = "jdbcTemplateC3P0")
private JdbcTemplate jdbcTemplateC3P0;@Test
public void test4(){
int i = jdbcTemplateC3P0.update("INSERT INTO account VALUES (null ,?,?)", "Lily", 50000d);
if (i > 0){
System.out.println("Update Successful");
}
}
}```
### 完成基本的CRUD操作
以下内容都是使用的**C3P0连接池**,并且通过`@Resource`注解从IOC容器中获取了`jdbcTemplateC3P0`对象
#### 插入操作
```java
/**
* 插入操作
*/
@Test
public void test(){
int i = jdbcTemplateC3P0.update("INSERT INTO account VALUES (null ,?,?)", "Lily", 50000d);
if (i > 0){
System.out.println("Update Successful");
}
}
```#### 修改操作
```java
/**
* 修改操作
*/
@Test
public void test(){
int i = jdbcTemplateC3P0.update("UPDATE account SET name = ? WHERE id = ?", "Bob", 1);
if (i > 0){
System.out.println("Update Successful");
}
}
```#### 删除操作
```java
/**
* 删除操作
*/
@Test
public void test(){
int i = jdbcTemplateC3P0.update("DELETE FROM account WHERE id = ?", 2);
if (i > 0){
System.out.println("Delete Successful");
}
}
```#### 查询操作
##### 查询某个属性
```java
/**
* 查询操作
*
* 查询单个字符串结果
*/
@Test
public void test(){
String result = jdbcTemplateC3P0.queryForObject("SELECT name FROM account WHERE id = ?", String.class, 1);
if (result != null){
System.out.println(result);
} else{
System.out.println("NULL");
}
}/**
* 统计查询
* 返回数据表中的记录数
*/
@Test
public void test(){
Long result = jdbcTemplateC3P0.queryForObject("SELECT COUNT(*) FROM account", Long.class);
System.out.println(result);
}
```##### 查询返回单个对象
要实现查询的数据封装成一个对象的话,查询`queryForObject`的参数列表可知需要一个`rowMapper`的参数。所以需要创建一个执行数据封装的类来实现`RowMapper`接口里的`mapRow`方法,在这个方法里进行数据对象的封装。
```java
/**
* 数据封装类
*
* @author Chen Rui
* @version 1.0
**/
public class MyRowMapper implements RowMapper {
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getDouble("money"));
return account;
}
}```
编写测试方法
```java
/**
* 将查询的结果封装成对象
* 要创建一个自定义rowMapper来实现RowMapper接口
*/
@Test
public void test(){
Account account = jdbcTemplateC3P0.queryForObject("SELECT * FROM account WHERE id = ?", new MyRowMapper(), 1);
if (account != null){
System.out.println(account);
} else{
System.out.println("NULL");
}
}
```##### 查询返回对象集合
要实现查询返回对象集合依然需要自定义类实现`RowMapper`接口,调用的是`query`方法
```java
/**
* 查询多条记录
*/
@Test
public void test10(){
List accounts = jdbcTemplateC3P0.query("SELECT * FROM account", new MyRowMapper());
accounts.forEach(System.out::println);
}
```# 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()方法然后再执行本身的方法。这时就涉及到**事务的传播行为**。
![](https://blogpictrue-1251547651.cos.ap-chengdu.myqcloud.com/blog/20190321110709.png)
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`,定义两个方法分别是`out`和`in`
```java
/**
* 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`实现`out`和`in`方法并且继承`JdbcSupport`类。这样就可以直接使用父类的`JDBCTemplate`对象。
```java
/**
* 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`方法
```java
/**
* 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`方法
```java
/**
* 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`注入。
```xml
```
到此一个转账模拟业务就实现了,数据库表依然使用前面创建的`account`表,先查询当前数据库的数据。
![](https://blogpictrue-1251547651.cos.ap-chengdu.myqcloud.com/blog/20190321124514.png)
编写测试方法,实现让姓名为Bob的账户向Jack转账1000元。
```java
/**
* 编程式事务测试类
*
* @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);
}
}
```运行结果
![](https://blogpictrue-1251547651.cos.ap-chengdu.myqcloud.com/blog/20190321124630.png)
现在对类`AccountServiceImpl`里的`transfer`方法进行修改,让其发生异常,再观察结果
```java
/**
* 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);
}}
```
查询数据库数据
![](https://blogpictrue-1251547651.cos.ap-chengdu.myqcloud.com/blog/20190321125027.png)
这时Bob账户的钱就少了1000元,而Jack账户也没有增加1000元。
所以就需要事务来进行管理。
### 编程式事务
所谓编程式事务,就是要在源码中编写事务相关的代码。实现编程式事务,首先要在`AccountServiceImpl`中声明`TransactionTemplate`对象,并创建set方法。然后修改`transfer`参数列表所有参数都用`final`(因为使用了匿名内部类)修饰,并修改方法体内容。
```java
/**
* 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对象`transactionManager`和`transactionTemplate`,并将`transactionTemplate`注入到`accountService`中。
```xml
```
此时异常依然存在,数据库数据仍然是上次执行的结果状态
![](https://blogpictrue-1251547651.cos.ap-chengdu.myqcloud.com/blog/20190321125027.png)
再次运行测试方法,并查询结果,观察是否发生变化
![](https://blogpictrue-1251547651.cos.ap-chengdu.myqcloud.com/blog/20190321130039.png)
现在就实现了编程式事务,当出现异常时,数据库的数据就不会被修改。
### 声明式事务
#### XML配置方式
声明式事务即通过配置文件实现,利用的就是Spring的AOP
修改类`AccountServiceImpl`,删除`TransactionTemplate`对象,并修改`transfer`方法,保留异常代码
```java
/**
* 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对象,然后配置事务管理器。
```xml
```
接着就配置事务的增强,配置文件中加入以下配置
```xml
```
先查看当前数据库数据
![](https://blogpictrue-1251547651.cos.ap-chengdu.myqcloud.com/blog/20190321130039.png)
编写测试方法
```java
/**
* 声明式事务配置测试类
*
* @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);
}
}
```运行查看结果,是否变化
![](https://blogpictrue-1251547651.cos.ap-chengdu.myqcloud.com/blog/20190321132512.png)
至此就实现了声明式事务XML方式的配置。
#### 注解配置方式
Spring的事务配置仍然支持注解配置
继续沿用`spring-tx-declarative.xml`文件,把事务增强和AOP相关的配置注释,并开启注解事务。
```xml
```
接下来就可以在业务层类上使用事务管理的注解了。修改`AccountServiceImpl`类,添加`@Transactional`注解
```java
/**
* 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);}
}
```再次运行测试方法,数据库也不会发生改变。