AOP概述
1. 介绍
AOP(Aspect Oriented Programing)面向切面编程,一种编程范式,指导开发者如何组织程序结构;
OOP(Object Oriented Programing)面向对象编程;
说白了AOP就是将程序中的共性代码抽取出来,那么需要就动态织入生成代理对象;
我们可以认为AOP是对OOP编程的一种补充;
2. 作用
在程序运行期间,不修改源码的基础上对已有方法进行增强
(无侵入性: 耦合度低)
3. 优势
1). 减少重复代码
2). 提高开发效率
3). 维护方便
4. Spring的AOP的实现方式
1). 当Bean实现接口时,Spring就会用JDK的动态代理(默认)
2). 当Bean没有实现接口时,Spring使用CGLib来实现
JDK8之后, JDK动态代理效率高于CGlib
3). 备注: 开发者可以在spring中强制使用CGLib (了解)
AOP入门
AOP相关概念
1. Target(目标对象)
要被增强的对象(被代理类对象)
2. Proxy(代理对象)
对目标对象的增强对象 (生成的代理类对象)
3. Joinpoint(连接点)
目标对象中的可被增强的所有方法(被代理类中的所有方法)
1). JDKProxy中被代理类不可被增强方法 --》父接口没有的方法
2). CGlib中被代理类不可被增强方法–》用final修饰的方法
4. Pointcut(切入点)
要被增强的方法(被代理类中要增强的方法)
1). 切入点一定是连接点
2). 但连接点不一定是切入点
5. Advice(通知/增强)
通知就是增强的那段代码形成的方法
增强的代码到底放到方法的什么位置
1). 前置通知 在方法之前进行增强
2). 后置通知 在方法之后进行增强
3). 异常通知 在方法异常进行增强,如果没有异常,则不会增强
4). 最终通知 最终执行的方法进行增强(无论是否出现异常,都会运行)
5). 环绕通知 单独使用(以上所有通知)
6. Aspect(切面)
切面= 切入点+通知
三点成一面:说白了就是在哪些方法上(切入点)的什么位置(通知类型)插入什么样的代码(增强的代码)
目标方法和增强方法合到在一起 叫做切面
7. Weaving(织入)
在运行过程中,spring底层将通知和切入点进行整合的过程(代理对象),称为织入
说白了,织入就是生成代理对象的过程;
AOP开发过程
- 开发阶段(开发者完成)
- 正常的制作程序
- 将非共性功能开发到对应的目标对象类中,并制作成切入点方法
- 将共性的功能代码抽取出来,制作成通知,而封装增强代码的类称为通知类|切面类
- 在配置类(切面类)中,声明切入点
- 在配置类(切面类)中,声明切入点与通知间的关系(含通知类型),即切面
-运行阶段(AOP完成)
- Spring容器加载配置类时, 使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置将通知对应的功能织入,形成完整的代码逻辑
- 当切入点方法被运行,将会调用代理对象的方法,达到增强目标对象的效果
- 此时IOC容器中维护的bean是代理对象,还是目标对象?
- 是代理对象;
AOP配置
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- spring核心jar包,已经依赖的AOP的jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- TODO: 切入点表达式 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
案例测试
SpringConfig类
package com.spring.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
//配置类
@Configuration
//扫描包
@ComponentScan("com.spring")
//开启AOP注解支持
@EnableAspectJAutoProxy
public class SpringConfig {
}
MyAdvie类
package com.spring.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//前提,切面类要在IOC容器中
@Component
//定义为切面类
@Aspect
public class MyAdvie {
/**
* 定义一个方法,用来装载切面表达式,减少代码的冗余
* 这个方法必须是无返回值,无参数,无实现体的
*/
@Pointcut("execution(public * com.spring.service.impl.*.find(..))")
public void MyPc(){}
/**
* 前置增强方法:defore
* 表达式扫中的方法都是切入点
* 当前方法定义了切入点+通知=切面
* 场景:
* 满足目标方法执行前,要做增强的功能
* 类似于过滤机制
*/
// @Before("MyPc()")
// public void before(){
// System.out.println("前置增强正在运行....");
// }
/**
* 后置增强方法:AfterReturning
* 特点:方法正常运行之后,才被执行,
* 如果目标方法执行出现异常,不能被运行
* 场景:
* 满足类方法顺利执行成功后,要做什么事
* 复检,
*
*/
// @AfterReturning("MyPc()")
// public void afterReturning(){
// System.out.println("后置增强正在运行....");
// }
/**
* 异常增强方法:AfterThrowing
* 只有出现异常,才做增强的通知
* 场景:
* 出现异常,通知
* 类似:程序出现异常,通知运维人员(发送邮箱 短信等)
* 警告
*
*/
// @AfterThrowing("MyPc()")
// public void afterThrowing(){
// System.out.println("异常增强正在运行.....");
// }
/**
* 最终执行方法:After
* 特点:无论是否发生异常,都会被执行
* 场景:
* 日志(无论成功或者失败,都记录日志) 释放资源
* 做收尾工作
*/
// @After("MyPc()")
// public void after(){
// System.out.println("最终通知方法正在执行....");
// }
/**
* 环绕通知 重点
* @param joinPoint 表示切入点 (底层是对当前代理对象调用的方法的封装)
* spring底层会将当前增强的方法封装到ProceedingJoinPoint类型对象下,并注入环绕通知方法
* 参数下
* @return 方法的返回值
* 环绕通知,一般独立使用,不会配置前置后置等通知使用
* 当然也可配合使用!
* 注意点:
* 要想实现环绕通知,必须先获取目标方法
*/
@Around("MyPc()")
public Object around(ProceedingJoinPoint joinPoint){
System.out.println("环绕方法前置....");
Object proceed = null;
try {
proceed = joinPoint.proceed();
System.out.println("环绕方法后置.....");
} catch (Throwable e) {
System.out.println("环绕异常通知....");
e.printStackTrace();
}finally {
System.out.println("环绕最终通知....");
}
return proceed;
}
}
Service接口
package com.spring.service;
public interface CarService {
void update(String str);
String find();
}
Service接口实现类
package com.spring.service.impl;
import com.spring.service.CarService;
import org.springframework.stereotype.Service;
@Service
public class CarServiceImpl implements CarService {
@Override
public void update(String str) {
System.out.println("修改信息:"+str);
}
@Override
public String find() {
System.out.println("正在查询...");
return "查询结果....";
}
}
测试类
package com.spring;
import com.spring.config.SpringConfig;
import com.spring.service.CarService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class Main {
@Autowired
private CarService carService;
@Test
public void test01(){
carService.find();
}
}
运行结果
环绕方法前置....
正在查询...
环绕方法后置.....
环绕最终通知....
切入点表达式
# 1. 切入点表达式
1). 通过切入点表达式可以让spring找到所要监控的切入点
2). 依赖
<!-- 切入点表达式 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
# 2. 语法
1. 完整写法:execution(方法的修饰符 方法的返回值 类的全限定名.方法名(参数))
2. 支持通配符的写法:
1) * 表示匹配任意字符串
2) .. 任意重复次数
3. 规则
1. 方法的修饰符可以省略:
2. 返回值可以使用*号代替:表示匹配任意返回值类型
3. 包名可以使用*号代替,代表任意包(一层包使用一个*)
4. 使用..配置包名,标识此包以及此包下的所有子包
5. 类名可以使用*号代替,标识任意类
6. 方法名可以使用*号代替,表示任意方法
7. 可以使用..配置参数,任意参数
# 3. 示例
1. 完整写法:execution(方法的修饰符 方法的返回值 类的全限定名.方法名(参数))
public void com.itheima.service.impl.AccountServiceImpl.insert(String)
2. 支持通配符的写法:
1) * 标识任意字符串
2) .. 任意重复次数
3. 规则
1. 方法的修饰符可以省略:
execution(void com.spring.service.impl.CarserviceImpl.find(String))
2. 返回值可以使用*号代替:标识任意返回值类型
execution(* com.spring.service.impl.CarserviceImpl.insert(String))
3. 包名可以使用*号代替,代表任意包(一层包使用一个*)
execution(* com.*.*.*.AccountServiceImpl.insert(String))
4. 使用..配置包名,标识此包以及此包下的所有子包
execution(* com..AccountServiceImpl.insert(String))
5. 类名可以使用*号代替,标识任意类
execution(* com..*.insert(String))
6. 方法名可以使用*号代替,表示任意方法名称
execution(* com..*.*(String))
7. 可以使用..配置参数,任意参数
execution(* com..*.*(..))
# 推荐: (在需求范围,越具体越好,效率高)
//service包下的所有方法,均为切入点
execution(void com.spring.service..*.*(..))
//service包下的任意Service(比如UserService,AccountService...)下的所有方法
execution(void com.spring.service.*Service.*(..))
事务
事务介绍
transaction(事务)
一组操作,要么同时成功,要么同时失败
是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作,这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;
总之,事务是一组不可再分割的操作集合 ;
开启事务
业务操作…
成功提交 / 失败回滚
事务的特点
CRUD: 增删改查(create retrieve/read update delete)
- 事务特征(ACID)
- 原子性(Atomicity)指事务是一个不可分割的整体,其中的操作要么全执行或全不执行
- 一致性(Consistency)事务前后数据的完整性必须保持一致(不能无中生有)
- 隔离性(Isolation)事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离(隔离级别有关)
- 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
事务的隔离级别
隔离级别 | 说明 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
ISOLATION_READ_UNCOMMITTED | 读未提交 | √ | √ | √ |
ISOLATION_READ_COMMITTED | 读已提交 | × | √ | √ |
ISOLATION_REPEATABLE_READ | 可重复读 | × | × | √ |
ISOLATION_SERIALIZABLE | 串行化操作 | × | × | × |
隔离级别
- 隔离级别由低到高
【READ_UNCOMMITTED】=>【READ_COMMITTED】=>【REPEATABLE_READ】=>【SERIALIZABLE】 - 数据库的默认级别
1). 对大多数数据库来说就是:READ_COMMITTED(读已提交)
2). MySQL默认采用:REPEATABLE_READ(可重复读),
3). Oracle采用:READ__COMMITTED(读已提交)
事务并发产生的三个问题
- 脏读:允许读取未提交的信息
脏数据(dirty data) : 正在编辑中的数据
- 原因:Read uncommitted
- 解决方案: Read committed
- 不可重复读:读取过程中单个数据发生了变化
- 解决方案: Repeatable read
- 幻读:读取过程中数据条目发生了变化
- 解决方案: Serializable
声明式事务
【1】从AOP到Spring声明式事务?
问题 : TxAdvice是事务切面类的特点
1. 代码很多
2. 事务操作不具备特例性
解决: Spring直接封装了TxAdvice类,让开发者不需要再写这个类了
但是一些设置,可能要根据开发者的需求进行更改
1. 切入点
2. 通知
1). 事务管理器
如果开发者dao层使用MyBatis框架, 就让其使用 DataSourceTransactionManager
如果开发者dao层使用Hibernate框架, 就让其使用 HibernateTransactionManager
…
2). 事务属性
事务隔离级别 / 只读 / 超时 / 传播行为
【2】声明式事务开发步骤?
1. 注解
//启动AOP功能 (注释掉)
//@EnableAspectJAutoProxy
//开启事务管理器(声明式事务)
@EnableTransactionManagement
2. @Bean 设置 事务管理器
3. @Transactional (指定切入点和对应事务属性)
1). 如果放在类的方法上,说明当前方法是切入点
2). 如果放在类上,说明当前类的所有方法是切入点
3). 如果放在接口的方法上,说明此方法的所有重写方法是切入点 (常用)
4). 如果放在接口上,说明此接口的所有实现类的所有方法都是切入点 (常用)
【3】Spring声明式事务与编程式事务区别?
1. 区别是编程式事务 : 开发者自己编写代码实现事务功能
2. 声明式事务: spring底层封装了事务切面类, 让开发者声明配置即可用
IV. 放在实现类的方法上,表示该方法是切入点
public interface AccountService {
//TODO: 配置需要事务支持的切入点 (声明式事务的第三步)
@Transactional(
isolation = Isolation.DEFAULT,
readOnly = false,
timeout = 10,
propagation = Propagation.REQUIRED
)
void transfer(int outId,int inId,double money);
}
Spring事务管理
核心对象
-
JAVAEE开发使用分层设计的思想进行
一般dao层只做数据库增删改查实现, 当业务中包含多个dao层的调用时,需要在service层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理
-
Spring为业务层提供了整套的事务解决方案
- PlatformTransactionManager
- TransactionDefinition
- TransactionStatus
相关API
- PlatformTransactionManager
1. 这是一个接口,以下是实现类
1). - DataSourceTransactionManager (重点!!!)
适用于Spring JDBC或MyBatis
2). - HibernateTransactionManager
适用于Hibernate3.0及以上版本
3). - JpaTransactionManager
适用于JPA (Java EE 标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行)
2. 此接口定义了事务的基本操作
1). 获取事务状态 :
TransactionStatus getTransaction(TransactionDefinition definition)
2). 提交事务 :
void commit(TransactionStatus status)
3). 回滚事务 :
void rollback(TransactionStatus status)
3. 示例代码
//1. 创建事务管理器
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//2. 为事务管理器设置与数据层相同的数据源
dstm.setDataSource(dataSource);
- TransactionDefinition接口
# TransactionDefinition(事务定义)
1. 实现类
DefaultTransactionDefinition
2. 此接口定义了事务的基本信息
//2. 创建事务定义对象
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
/*
设置事务隔离级别
0). spring默认隔离级别是跟数据库软件一致
1). mysql默认是REPEATABLE_READ
2). oracle默认是READ_COMMITTED
*/
td.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
/*
* 设置是否只读
* 1). false,表示读写均可(默认设置,适合增删改操作)
2). true,表示只读(适合查,效率高)
* */
td.setReadOnly(false);
/*
* 设置超时时间
* 1). 默认值是-1, 表示永不超时
* 2). 单位是秒
* */
td.setTimeout(10);
/*
设置事务传播行为
1. 一般增删改:REQUIRED (默认值)
2. 一般查询 SUPPORTS
这个比较复杂,待会详解
* */
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
- TransactionStatus接口**(了解)
此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作
事务传播行为
事务传播行为:指的就是当一个事务方法A被另一个事务方法B调用时,这个事务方法A应该对待B的事务态度。
以下将涉及两个概念:
1. 事务管理员: 比如service层的transfer方法
2. 事务协调员: 比如dao层的两个方法
事务协调员对待管理员所携带事务的处理态度
# 运用场景举例
1. 还是以转账操作为例 : S(事务管理员)
- 子业务S1:X用户执行转出操作,修改表 , Y用户执行转入操作,修改表 (事务协调员S1)
- 子业务S2:银行记录转账日志到数据库日志表中 (事务协调员S2)
# 使用规范
1. 一般增删改:加事务 REQUIRED (默认取值)
2. 一般查询:不加事务 SUPPORTS
3. 非主要业务不对主业务造成影响:REQUIRES_NEW
在转账时,做日志记录:
转账是主业务,日志记录是非主业务,那么也就是说非主业务的操作状态不能影响主业务;
或者无论转账是否成功或者失败,都要记录日志
4. 必须在有事务的环境运行:MANDATORY
# 代码编写注意
1. 事务传播行为是发生在两个bean之间的
2. S操作 (事务管理员) 单独一个Bean
3. S1和S2操作 (事务协调员)单独封装到一个Bean中