Spring aop 及 Spring 声明式事务管理
1 什么是AOP
所谓面向切面编程,就是我希望在目标对象(有无接口都可以)上扩展原有代码的功能,但可以在不修改源码的基础上来完成!!!
-
AOP编程是什么?AOP编程有哪些应用场景?
- AOP 面向切面编程
- 日志的处理、权限处理、事务管理
-
什么是连接点、切入点、通知、切面,代理对象、目标对象?
- 连接点: 被代理对象的所有方法都被称作为连接点
- 切入点: 被代理的方法就称作为切入点
- 通知: 增强的代码我们就称作为通知
- 切面: 切面= 切入点+ 通知
1. AOP编程有哪些步骤?
-
导入依赖: aop\ aspectjweaver
-
编写目标对象
-
.创建自定义的切面类(增强的代码)(切面类就是一些增强的方法)
-
bean.xml配置,配置出一个切面
<!--1. 创建目标对象与切面对象--> <!-- 目标对象 --> <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/> <!--切面对象--> <bean id="logAspect" class="com.itheima.service.aspect.LogAspect"/> <!--2. 使用aop.config标签把目标对象与切面对象组合到一块形成一个切面。--> <aop:config> <aop:aspect ref="切面对象"> <!-- 2.1 切入点表达式 : 代表了我需要增强UserServiceImpl的所有方法--> <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/> <!--2.2 切面 = 通知+切入点 --> <aop:before method="切面对象的方法" pointcut-ref="pt"/> </aop:aspect> </aop:config>
<!--1. 创建目标对象与切面对象--> <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/> <bean id="logAspect" class="com.itheima.service.aspect.LogAspect"/> <!--2. 使用aop.config标签把目标对象与切面对象组合到一块形成一个切面。--> <aop:config> <aop:aspect ref="logAspect"> <!-- 2.1 切入点表达式 : 代表了我需要增强UserServiceImpl的所有方法--> <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/> <!--2.2 切面 = 通知+切入点 --> <!--前置通知--> <aop:before method="before" pointcut-ref="pt"/> <!--后置通知--> <aop:after-returning method="afterReturn" pointcut-ref="pt"/> <!--异常通知--> <aop:after-throwing method="agfterException" pointcut-ref="pt"/> <!--最终的通知--> <aop:after method="afterFinal" pointcut-ref="pt"/> <!--配置环绕通知--> <aop:around method="around" pointcut-ref="pt"/> </aop:aspect> </aop:config>
2 Aop编程切入点表达式
1 切入点表达式
用于声明要在目标对象的哪些方法进行切入通知!!
语法:
execution(修饰符 返回值类型 包名.类名.方法名(形参))
语法: execution( 修饰符 返回值类型 包路径.类名称.方法名称( 参数列表类型 ) )
- execution(* com.itheima.service.impl.UserServiceImpl.*(…))
- 修饰符的写法
- 明确具体的修饰符
- 省略不写
- 返回值类型写法
- 明确类型
- 使用通配符
- 包名
- 明确具体的包结构
- 匹配一级目录 *
- 匹配多级目录 *…*
- 类名
- 明确类名
- 使用通配符
- 方法名
- 明确方法名
- 使用通配符
- 形参类型
- 明确形参的类型
- 使用… 通配符
2.1 切入点表达式 :
语法:
execution(修饰符 返回值类型 包名.类名.方法名(形参))
1. 修饰符:
a.明确修饰符, <aop:pointcut id="pt" expression="execution(public * com.itheima.service.impl.UserServiceImpl.*(..))"/>
b.省略修饰符的。 <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>
2. 返回值类型:(返回值类型是不能省略)
a.明确具体类型 : <aop:pointcut id="pt" expression="execution(void com.itheima.service.impl.UserServiceImpl.*(..))"/>
b.通配符: <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>
3. 包名:
.明确具体的包: <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>
b.使用通配符匹配一级目录 *: <aop:pointcut id="pt" expression="execution(* com.*.service.impl.UserServiceImpl.*(..))"/>
c.使用通配符匹配多级目录 *..* :<aop:pointcut id="pt" expression="execution(* com.*..*.impl.UserServiceImpl.*(..))"/>
4. 类名:
a.明确具体的类名 : <aop:pointcut id="pt" expression="execution(* com.*..*.impl.UserServiceImpl.*(..))"/>
b.使用通配符 : <aop:pointcut id="pt" expression="execution(* com.*..*.impl.*Impl.*(..))"/>
5. 方法名:
a.明确具体的方法名 : <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.delete(..))"/>
b.使用通配符 : <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>
6. 形参:
a. 明确具体的类型: <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.update(int,String))"/>
b. 使用通配符: <aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.update(..))"/>
3. Aop编程通知类型
01 通知的类型
前置通知:在执行目标对象方法之前执行 aop:before
后置通知:在执行目标对象方法之后执行 (异常不执行) aop:after-returning
异常通知:在执行目标对象方法里面发生异常时候执行 aop:after-throwing
最终通知:在执行完目标对象方法后始终执行的方法就是最终通知(异常也执行) aop:after
- 前置通知 aop:before
- 后置通知 aop:afterTeturning
- 异常通知 aop:throwing
- 最终通知 aop:after
try{
前置通知
执行目标方法
后置通知
}catch(Exception e){
异常通知
}finally{
最终通知
}
切面类
bean.xml配置文件
02 aop:around 环绕通知
环绕通知的方法必须有一个参数, pj代表了当前你执行的方法对象。 比如: 你执行了update方法,
那么ProceedingJoinPoint则带update方法.
ProceedingJoinPoint代表了什么?如何让目标方法执行
- 代表了当前的方法对象
- ProceedingJoinPoint.proceed()
在LogAspect切面类添加环绕通知方法
public class LogAspect {
/*
环绕通知的方法必须有一个参数, pj代表了当前你执行的方法对象。 比如: 你执行了update方法,
那么ProceedingJoinPoint则带update方法。
*/
// 环绕通知
public void around(ProceedingJoinPoint pj){
pj.proceed();
}
}
<!--1. 创建目标对象与切面对象-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
<bean id="logAspect" class="com.itheima.service.aspect.LogAspect"/>
<!--2. 使用aop.config标签把目标对象与切面对象组合到一块形成一个切面。-->
<aop:config>
<aop:aspect ref="logAspect">
<!-- 2.1 切入点表达式 : 代表了我需要增强UserServiceImpl的所有方法-->
<aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>
<!--配置环绕通知-->
<aop:around method="around" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
4 Aop(XML+注解)
注解方式的aop编程需要使用哪些注解, 每个注解的作用是什么?
- @Aspect 代表是一个切面 切面= 通知+切入点
- @PointCut() @Pointcut(“execution(* com.itheima.service.impl.UserServiceImpl.*(…))”)
- @Before(“切入点方法名”) ,@AfterReturning (“切入点方法名”),@AfterThrowing(“切入点方法名”) ,@After(“切入点方法名”),@Around(“切入点方法名”)。
01 bean.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--1. 创建目标对象与切面对象-->
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
<!--component-scan这个标签只能扫描: @Respository、 @Service\@Controller、Component这些注解而已,对于aop的注解没法扫描-->
<context:component-scan base-package="com.itheima"/>
<!--开启aop的注解扫描-->
<aop:aspectj-autoproxy/>
</beans>
02 切面类LogAspect上添加注解
/*
@Aspect: 代表是一个切面 切面= 通知+切入点
*/
@Component
@Aspect
public class LogAspect {
//切入点表达式
/*
<aop:pointcut id="pt" expression="execution(* com.itheima.service.impl.UserServiceImpl.*(..))"/>
id:则为该方法的方法名,当前切入点的id为pt
*/
@Pointcut("execution(* com.itheima.service.impl.UserServiceImpl.*(..))")
public void pt(){}
/*
环绕通知的方法必须有一个参数, pj代表了当前你执行的方法对象。 比如: 你执行了update方法,
那么ProceedingJoinPoint则带update方法。
*/
// 前置通知
@Before("pt()")
public void before(){
System.out.println("前置通知..");
}
// 后置通知
@AfterReturning("pt()")
public void afterReturn(){
System.out.println("后置通知");
}
// 异常通知
@AfterThrowing("pt()")
public void agfterException(){
System.out.println("异常通知..");
}
// 最终通知
@After("pt()")
public void afterFinal(){
System.out.println("最终通知..");
}
/*
// 环绕通知
@Around("pt()")
public void around(ProceedingJoinPoint pj){
try {
System.out.println("====前置通知===");
//让目标方法执行执行方法
pj.proceed();
System.out.println("获取方法执行的实参:"+ Arrays.toString(pj.getArgs()));
System.out.println("======后置通知=======");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("=======异常通知========");
} finally {
System.out.println("=====最终通知=========");
}
}
*/
}
5 Aop纯注解开发
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mNBObAjH-1609830946803)(assets\1587291022015.png)]
@ComponentScan(“com.itheima”) 扫描 ioc 相关的注解的 @Respository @Service @Controller @component这些注解的
@EnableAspectJAutoProxy 扫描aop相关的注解的。
@Configuration 该注解的作用代表了该类是一个配置类,用于取代bean.xml文件的。
@PropertySource(“classpath:db2.propertes”) 用于指定加载配置文件的。
@Bean(“dataSource”) 方法上一旦添加 @Bean(“dataSource”)注解,那么该方法会自动执行,并且把该方法 的返回值存储到容器中。
/*
@ComponentScan("com.itheima") 扫描@Respository @Service @Controller @component这些注解的。
@EnableAspectJAutoProxy 扫描aop相关的注解的。
*/
@Configuration //代表该类是个配置类。用于取代Bean.xml文件
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
6. 事务的概念
什么是事务,事务的四大特性是什么?
- A(原子性)C(一致性) I(隔离性) D( 持久性)
1. 什么是事务?
事务,一组最小单位的执行单元(一组SQL语句),这个最小单位要么一起成功,要么一起失败!!
2.事务的四个特性(ACID)?
原子性(A):如果使用事务控制对操作进行控制,这些操作要么一起成功,要么一起失败!!
一致性A):事务可以让数据库从一个一致性状态切换到另一个一致性的状态(转账总额不变)
隔离性A):多个并发事务之间应该相互隔离。
如果多个并发事务隔离出问题,可能导致以下现象:
1)脏读:一个事务读到另一个未提交的数据(必须防止的)
2)不可重复读:一个事务读到另一个已经提交的更新(update)数据
3)幻读(虚读) : 一个事务读到另一个已经提交的插入(insert)数据
可以通过设置数据库的隔离级别,防止以上的现象的发现:
脏读 不可重复读 幻读(虚读)
read uncommited N N N
read committed Y N N
repeatable read Y Y N
serilizable Y Y Y
持久性(D):事务一旦提交(commit),数据应该永久保存下来。
注意:
1)mysql的默认隔离级别:repeatable read
oracle的默认隔离级别: read committed
2)隔离级别越高,事务并发性能越差
7 Spring事务管理
Spring事务管理:
第一: JavaEE 体系进行分层开发, 事务控制位于业务层, Spring 提供了分层设计业务层
的事务处理解决方案。
第二: spring 框架为我们提供了一组事务控制的接口。
第三:Spring的事务管理,分为 声明式事务管理 与 编程式事务管理器。
Spring事务管理分为:
声明式事务管理,底层是AOP思想(重点掌握)
编程式事务管理,底层不是AOP思想(了解)
- spring要求大家进行事务控制在service层
- spring事务的类别
- 声明式事务管理
- 编程式事务管理
01 回顾JDBC事务管理:
开启事务: connection.setAutoCommit(false)
提交事务:connection.commit()
回滚事务: connection.rollback();
JDBC业务操作
public void UserServiceImpl{
public void save(){
//获取连接
Connection conn = JDBCUtils.getConnection();
//====开启事务(取消自动提交)======
conn.setAutoCommit(false);
try{
//预编译sql语句
String sql = "insert into user(username,age) vales(?,?)";
PreparedStatement stmt = conn.preparedStatement(sql);
//设置参数
stmt.setParameter(1,'张三');
stmt.setParameter(2,20);
//执行SQL
stmt.executeUpdate();
//====提交事务====
conn.commit()
}catch(Exception e){
e.printStackOver();
=== 回滚事务======
conn.rollback();
}finally{
//连接放回连接池
JDBCUtils.releaseConn(conn);
}
}
}
8 Spring 声明式事务 XML方式(重点)
propagation:事务传播行为,REQUIRED代表了一定要有事务, SUPPORTS代表事务可有可无
01 创建事务管理器的对象
(切面对象,增强的代码):
<!--创建切面对象,spring已经为我们提供好了事务管理器,我们目标是要把事务管理器的代码添加到service方法上面-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/>
02 配置事务管理的 通知规则
(指定哪些方法需要事务,哪些方法不需要事务)
<!--2. 是事务管理器配置通知的规则:为哪些方法配置事务-->
<tx:advice id="adive 引用通知规则" transaction-manager="transactionManager" >
<tx:attributes>
<!-- <!–spring在匹配一个方法事务要事务的时候是从上往下匹配的,只有匹配到其中一个,那么就不会再往下去执行了。–>
<tx:method name="find*" propagation="SUPPORTS"/>
<tx:method name="get*" propagation="SUPPORTS"/>
<tx:method name="query*" propagation="SUPPORTS"/>
<tx:method name="select*" propagation="SUPPORTS"/>
<!–所有的方法必须要有事务–>
<tx:method name="*" propagation="REQUIRED"/>
-->
<!--事务的规则:哪些方法是需要事务管理的。-->
<tx:method name="save" isolation="DEFAULT" propagation="REQUIRED" />
<!-- propagation:事务传播行为,REQUIRED代表了一定要有事务, SUPPORTS代表事务可有可无-->
<tx:method name="findAll" isolation="DEFAULT" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
03 配置一个切面
<!-- 3.事务切面 = 通知+切入点 -->
<aop:config>
<!--切入点表达式-->
<aop:pointcut id="pt" expresssion="execution(切入点表达式)"/>
<!-- 通知+切入点 -->
<aop:advisor adivise-ref="adive 引用通知规则"
pointcut-ref="切入点表达式引用 pt"/>
</aop:config>
04 完整 applicationContext.xml
<!-- Spring声明式事务 -->
<!-- 1.事务管理器 封装事务管理代码 commit,rollbac-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 2.事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 事务特性 -->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<!-- 3.事务切面 = 通知+切入点 -->
<aop:config>
<!-- 3.1 切入点 -->
<aop:pointcut id="pt" expression="execution(* com.itheima.service.*ServiceImpl.*(..))"/>
<!-- 3.2 通知+切入点 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
9 Spring声明式事务(XML+注解)
Spring声明式事务改造为注解方式
注意:事务管理器依然还是bean来配置!!!
在业务类(service层)上添加@Transactional 注解
修改bean.xml开启事务注解扫描.
01 实现步骤
1.事务管理器依然还是bean来配置!!!
2.在业务类上添加 @Transactional 注解 英[trænˈzækʃn]
service层
3.修改applicationContext.xml开启事务注解扫描.
<!--扫描事务管理器 专门用于扫描transactional-->
<tx:annotation-driven transaction-manager="配置文件上 事务管理的id(事务切面对象)"/>
02 @Transactional注解的细节:
/**
* 账户Service实现
* @Transactional 注解的使用
* 1.位置问题
* 1.1 业务类上: 对类的所有方法添加事务控制(推荐使用)
* 1.2 业务类的方法上: 对某个类的方法添加事务控制
* 1.3 业务接口上: 对接口声明的所有方法添加事务控制
* 1.3 业务接口的方法上: 对接口声明的指定方法添加事务控制
*
* 注意:类和方法同时存在,优先使用方法上的
* 2.属性问题
* transactionManager: 引用的事务管理器的id
* 默认@Transactional根据类型进行注入
* 如果有多个类型才会按照名称进行注入
* value: 和transactionManager的值一样的
*
* isolation: 设置事务的隔离级别
* propagation: 设置事务的传播行为
* rollbackFor: (理论)针对某些异常进行事务回滚 (实际测试,所有异常都可以进行事务控制 )
* noRollbackFor: 针对某些异常不进行事务回滚 (实际测试是OK的)
*/
@Transactional(isolation = Isolation.DEFAULT,
propagation = Propagation.REQUIRED,
noRollbackFor = NullPointerException.class ) // 事务注解
public class AccountServiceImpl implements AccountService{
}
10. Spring声明式事务管理 零配置(纯注解)
@EnableTransactionManagement //开启事务管理的扫描 扫描@Transactional
@Import(配置类名.class) // 导入其他的配置类的。
01 编写配置类,添加相应注解
@Configuration
@ComponentScan("com.itheima")//
@EnableTransactionManagement //开启事务管理的扫描 扫描@Transactional
@Import(JdbConfig.class) // 导入其他的配置类的。
public class SpringConfigration {
}
02 编写JdbcConfig配置类
@PropertySource("classpath:db2.properties")
public class JdbConfig {
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource createDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUsername(username);
dataSource.setUrl(url);
dataSource.setPassword(password);
return dataSource;
}
@Bean("jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器
@Bean
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
11 Spring编程式事务【了解】
使用Spring编程式事务,意思就是用写代码的方式直接在业务类加入Spring事务控制!
bean.xml
<!--1. 创建目标对象+切面对象-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</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>
在业务方法上编码实现事务控制
@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
//创建事务管理模板类
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public void save(Account account) {
//事务控制器业务逻辑 , 在doInTransaction方法内部的代码会收到事务管理器的管理
TransactionCallback callback = new TransactionCallback(){
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
accountDao.save(account);
int result = 1/0;
accountDao.save(account);
return null;
}
};
transactionTemplate.execute(callback);
}
@Override
public void findAll() {
System.out.println("查找所有..");
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
测试
@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
//创建事务管理模板类
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public void save(Account account) {
//事务控制器业务逻辑 , 在doInTransaction方法内部的代码会收到事务管理器的管理
TransactionCallback callback = new TransactionCallback(){
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
accountDao.save(account);
int result = 1/0;
accountDao.save(account);
return null;
}
};
transactionTemplate.execute(callback);
}
@Override
public void findAll() {
System.out.println("查找所有..");
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}