AOP相关概念
基于XML配置的AOP
快速入门
xml方式配置AOP的步骤:
- 导入AOP相关坐标,Spring-context坐标下包含spring-aop的包
- 准备目标类、准备增强类,并配置给Spring管理;
- 配置切点表达式(哪些方法被增强);
- 配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)。
准备目标类、准备增强类,并配置给Spring管理
目标类
public interface UserService {
void show1();
void show2();
}
public class UserServiceImpl implements UserService {
public void show1() {
System.out.println("show1...");
}
public void show2() {
System.out.println("show2...");
}
}
增强类
public class MyAdvice {
public void beforeAdvice(){
System.out.println("beforeAdvice");
}
public void afterAdvice(){
System.out.println("afterAdvice");
}
}
xml配置文件
<!--配置目标类,内部的方法是连接点-->
<bean id="userService" class="com.rqz.service.impl.UserServiceImpl"/>
<!--配置通知类,内部的方法是增强方法-->
<bean id=“myAdvice" class="com.rqz.advice.MyAdvice"/>
配置切点表达式(哪些方法被增强)和配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)
<aop:config>
<!--配置切点表达式,对哪些方法进行增强-->
<aop:pointcut id="myPointcut" expression="execution(void com.rqz.service.impl.UserServiceImpl.show1())"/>
<!--切面=切点+通知-->
<aop:aspect ref="myAdvice">
<!--指定前置通知方法是beforeAdvice-->
<aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
<!--指定后置通知方法是afterAdvice-->
<aop:after-returning method="afterAdvice" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
切点表达式的配置方式有两种,直接将切点表达式配置在通知上,也可以将切点表达式抽取到外面,在通知上进行引用
<aop:config>
<!--配置切点表达式,对哪些方法进行增强-->
<aop:pointcut id="myPointcut" expression="execution(void com.rqz.service.impl.UserServiceImpl.show1())"/>
<!--切面=切点+通知-->
<aop:aspect ref="myAdvice">
<!--指定前置通知方法是beforeAdvice-->
<aop:before method="beforeAdvice" pointcut-ref="myPointcut"/>
<!--指定后置通知方法是afterAdvice-->
<aop:after-returning method="afterAdvice" pointcut="execution(void com.rqz.service.impl.UserServiceImpl.show1())"/>
</aop:aspect>
</aop:config>
配置详解
切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下:
execution([访问修饰符]返回值类型 包名.类名.方法名(参数))
其中
- 访问修饰符可以省略不写;
- 返回值类型、某一级包名、类名、方法名 可以使用 * 表示任意;
- 包名与类名之间使用单点 . 表示该包下的类,使用双点 … 表示该包及其子包下的类;
- 参数列表可以使用两个点 … 表示任意参数。
切点表达式举例
//表示访问修饰符为public、无返回值、在com.rqz.aop包下的TargetImpl类的无参方法show
execution(public void com.rqz.aop.TargetImpl.show())
//表述com.rqz.aop包下的TargetImpl类的任意方法
execution(* com.rqz.aop.TargetImpl.*(..))
//表示com.rqz.aop包下的任意类的任意方法
execution(* com.rqz.aop.*.*(..))
//表示com.rqz.aop包及其子包下的任意类的任意方法
execution(* com.rqz.aop..*.*(..))
//表示任意包中的任意类的任意方法
execution(* *..*.*(..))
AspectJ的通知由以下五种类型
通知名称 | 配置方式 | 执行时机 |
---|---|---|
前置通知 | < aop:before > | 目标方法执行之前执行 |
后置通知 | < aop:after-returning > | 目标方法执行之后执行,目标方法异常时,不在执行 |
环绕通知 | < aop:around > | 目标方法执行前后执行,目标方法异常时,环绕后方法不在执行 |
异常通知 | < aop:after-throwing > | 目标方法抛出异常时执行 |
最终通知 | < aop:after > | 不管目标方法是否有异常,最终都会执行 |
环绕通知
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
//环绕前
System.out.println("环绕前通知");
//目标方法
joinPoint.proceed();
///环绕后
System.out.println("环绕后通知");
}
<aop:around method="around" pointcut-ref="myPointcut"/>
异常通知
当目标方法抛出异常时,异常通知方法执行,且后置通知和环绕后通知不在执行
public void afterThrowing(){
System.out.println("目标方法抛出异常了,后置通知和环绕后通知不在执行");
}
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
最终通知
类似异常捕获中的finally,不管目标方法有没有异常,最终都会执行的通知
public void after(){
System.out.println("不管目标方法有无异常,我都会执行");
}
<aop:after method="after" pointcut-ref="myPointcut"/>
通知方法在被调用时,Spring可以为其传递一些必要的参数
参数类型 | 作用 |
---|---|
JoinPoint | 连接点对象,任何通知都可使用,可以获得当前目标对象、目标方法参数等信息 |
ProceedingJoinPoint | JoinPoint子类对象,主要是在环绕通知中执行proceed(),进而执行目标方法 |
Throwable | 异常对象,使用在异常通知中,需要在配置文件中指出异常对象名称 |
JoinPoint 对象
public void 通知方法名称(JoinPoint joinPoint){
//获得目标方法的参数
System.out.println(joinPoint.getArgs());
//获得目标对象
System.out.println(joinPoint.getTarget());
//获得精确的切点表达式信息
System.out.println(joinPoint.getStaticPart());
}
ProceedingJoinPoint对象
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint.getArgs());//获得目标方法的参数
System.out.println(joinPoint.getTarget());//获得目标对象
System.out.println(joinPoint.getStaticPart());//获得精确的切点表达式信息
Object result = joinPoint.proceed();//执行目标方法
return result;//返回目标方法返回值
}
Throwable对象
public void afterThrowing(JoinPoint joinPoint,Throwable th){
//获得异常信息
System.out.println("异常对象是:"+th+"异常信息是:"+th.getMessage());
}
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" throwing="th"/>
基于注解配置的AOP
基本使用
Spring的AOP也提供了注解方式配置,使用相应的注解替代之前的xml配置,xml配置AOP时,我们主要配置了三部分:目标类被Spring容器管理、通知类被Spring管理、通知与切点的织入(切面),如下:
<!--配置目标-->
<bean id="target" class="com.rqz.aop.TargetImpl"></bean>
<!--配置通知-->
<bean id="advices" class="com.rqz.aop.Advices"></bean>
<!--配置aop-->
<aop:config proxy-target-class="true">
<aop:aspect ref="advices">
<aop:around method="around" pointcut="execution(* com.rqz.aop.*.*(..))"/>
</aop:aspect>
</aop:config>
目标类被Spring容器管理、通知类被Spring管理
@Component("target")
public class TargetImpl implements Target{
public void show() {
System.out.println("show Target running...");
}
}
@Component
public class AnnoAdvice {
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知...");
joinPoint.proceed();
System.out.println("环绕后通知...");
}
}
配置aop,其实配置aop主要就是配置通知类中的哪个方法(通知类型)对应的切点表达式是什么
@Component
@Aspect //第一步
public class AnnoAdvice {
@Around("execution(* com.rqz.aop.*.*(..))") //第二步
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前通知...");
joinPoint.proceed();
System.out.println("环绕后通知...");
}
}
注解@Aspect、@Around需要被Spring解析,所以在Spring核心配置文件中需要配置aspectj的自动代理
<aop:aspectj-autoproxy/>
如果核心配置使用的是配置类的话,需要配置注解方式的aop自动代理
@Configuration
@ComponentScan("com.rqz.aop")
@EnableAspectJAutoProxy //第三步
public class ApplicationContextConfig {
}
配置详解
各种注解方式通知类型
//前置通知
@Before("execution(* com.rqz.aop.*.*(..))")
public void before(JoinPoint joinPoint){}
//后置通知
@AfterReturning("execution(* com.rqz.aop.*.*(..))")
public void AfterReturning(JoinPoint joinPoint){}
//环绕通知
@Around("execution(* com.rqz.aop.*.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {}
//异常通知
@AfterThrowing("execution(* com.rqz.aop.*.*(..))")
public void AfterThrowing(JoinPoint joinPoint){}
//最终通知
@After("execution(* com.rqz.aop.*.*(..))")
public void After(JoinPoint joinPoint){}
切点表达式的抽取,使用一个空方法,将切点表达式标注在空方法上,其他通知方法引用即可
@Component
@Aspect
public class AnnoAdvice {
//切点表达式抽取
@Pointcut("execution(* com.rqz.aop.*.*(..))")
public void pointcut(){}
//前置通知
@Before("pointcut()")
public void before(JoinPoint joinPoint){}
//后置通知
@AfterReturning("AnnoAdvice.pointcut()")
public void AfterReturning(JoinPoint joinPoint){}
// ... 省略其他代码 ...
}
基于AOP的声明式事务控制
基于xml声明式事务控制
导入Spring事务的相关的坐标,spring-jdbc坐标已经引入的spring-tx坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
配置目标类AccountServiceImpl
<bean id="accountService" class="com.rqz.service.impl.AccoutServiceImpl">
<property name="accountMapper" ref="accountMapper"></property>
</bean>
Spring提供的事务通知
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
<!--Spring提供的事务通知-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="指定要进行事务的方法名称"/>
</tx:attributes>
</tx:advice>
<!--平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<aop:config>
<aop:advisor advice-ref="myAdvice" pointcut="execution(* com.rqz.service.impl.*.*(..))"/>
</aop:config>
对上述配置进行详解
平台事务管理器PlatformTransactionManager是Spring提供的封装事务具体操作的规范接口,封装了事务的提交和回滚方法
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}
不同的持久层框架事务操作的方式有可能不同,所以不同的持久层框架有可能会有不同的平台事务管理器实现,例如,MyBatis作为持久层框架时,使用的平台事务管理器实现是DataSourceTransactionManager。Hibernate作为持久层框架时,使用的平台事务管理器是HibernateTransactionManager。
事务定义信息配置,每个事务有很多特性,例如:隔离级别、只读状态、超时时间等,这些信息在开发时可以通过connection进行指定,而此处要通过配置文件进行配置
<!-- name属性名称指定哪个方法要进行哪些事务的属性配置 -->
<tx:attributes>
<tx:method
name="方法名称"
isolation="隔离级别"
propagation="传播行为"
read-only="只读状态"
timeout="超时时间"/>
</tx:attributes>
方法名在配置时,也可以使用 * 进行模糊匹配,例如:
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--精确匹配transferMoney方法-->
<tx:method name="transferMoney"/>
<!--模糊匹配以Service结尾的方法-->
<tx:method name="*Service"/>
<!--模糊匹配以insert开头的方法-->
<tx:method name="insert*"/>
<!--模糊匹配以update开头的方法-->
<tx:method name="update*"/>
<!--模糊匹配任意方法,一般放到最后作为保底匹配-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
isolation属性:指定事务的隔离级别
事务并发存在三大问题:脏读、不可重复读、幻读/虚读。可以通过设置事务的隔离级别来保证并发问题的出现,常用的是READ_COMMITTED 和 REPEATABLE_READ
isolation属性 | 解释 |
---|---|
DEFAULT | 默认隔离级别,取决于当前数据库隔离级别,例如MySQL默认隔离级别是REPEATABLE_READ |
READ_UNCOMMITTED | A事务可以读取到B事务尚未提交的事务记录,不能解决任何并发问题,安全性最低,性能最高 |
READ_COMMITTED | A事务只能读取到其他事务已经提交的记录,不能读取到未提交的记录。可以解决脏读问题,但是不能解决不可重复读和幻读 |
REPEATABLE_READ | A事务多次从数据库读取某条记录结果一致,可以解决不可重复读,不可以解决幻读 |
SERIALIZABLE | 串行化,可以解决任何并发问题,安全性最高,但是性能最低 |
read-only属性:设置当前的只读状态
如果是查询则设置为true,可以提高查询性能,如果是更新(增删改)操作则设置为false
<!-- 一般查询相关的业务操作都会设置为只读模式 -->
<tx:method name="select*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
timeout属性:设置事务执行的超时时间
单位是秒,如果超过该时间限制但事务还没有完成,则自动回滚事务,不在继续执行。默认值是-1,即没有超时时间限制
<!-- 设置查询操作的超时时间是3秒 -->
<tx:method name="select*" read-only="true" timeout="3"/>
propagation属性:设置事务的传播行为
主要解决是A方法调用B方法时,事务的传播方式问题的,例如:使用单方的事务,还是A和B都使用自己的事务等。事务的传播行为有如下七种属性值可配置
事务传播行为 | 解释 |
---|---|
REQUIRED(默认值) | A调用B,B需要事务,如果A有事务B就加入A的事务中,如果A没有事务,B就自己创建一个事务 |
REQUIRED_NEW | A调用B,B需要新事务,如果A有事务就挂起,B自己创建一个新的事务 |
SUPPORTS | A调用B,B有无事务无所谓,A有事务就加入到A事务中,A无事务B就以非事务方式执行 |
NOT_SUPPORTS | A调用B,B以无事务方式执行,A如有事务则挂起 |
NEVER | A调用B,B以无事务方式执行,A如有事务则抛出异常 |
MANDATORY | A调用B,B要加入A的事务中,如果A无事务就抛出异常 |
NESTED | A调用B,B创建一个新事务,A有事务就作为嵌套事务存在,A没事务就以创建的新事务执行 |
基于注解声明式事务控制
@Service("accountService")
public class AccoutServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
//<tx:method name="*" isolation="REPEATABLE_READ" propagation="REQUIRED“/>
@Transactional(isolation = Isolation.REPEATABLE_READ , propagation = Propagation.REQUIRED , readOnly = false,timeout = 5)
public void transferMoney(String decrAccountName, String incrAccountName, int money) {
accountMapper.decrMoney(decrAccountName,money); //转出钱
int i = 1/0; //模拟某些逻辑产生的异常
accountMapper.incrMoney(incrAccountName,money); //转入钱
}
}
@Configuration
@ComponentScan("com.rqz.service")
@PropertySource("classpath:jdbc.properties")
@MapperScan("com.rqz.mapper")
@EnableTransactionManagement
public class ApplicationContextConfig {
@Bean
public PlatformTransactionManager tansactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
// ... 省略其他配置 ...
}