Spring AOP的使用总结

AOP相关概念

AOP相关概念
AOP相关概念

基于XML配置的AOP

快速入门

xml方式配置AOP的步骤:

  1. 导入AOP相关坐标,Spring-context坐标下包含spring-aop的包
  2. 准备目标类、准备增强类,并配置给Spring管理;
  3. 配置切点表达式(哪些方法被增强);
  4. 配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)。

准备目标类、准备增强类,并配置给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连接点对象,任何通知都可使用,可以获得当前目标对象、目标方法参数等信息
ProceedingJoinPointJoinPoint子类对象,主要是在环绕通知中执行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_UNCOMMITTEDA事务可以读取到B事务尚未提交的事务记录,不能解决任何并发问题,安全性最低,性能最高
READ_COMMITTEDA事务只能读取到其他事务已经提交的记录,不能读取到未提交的记录。可以解决脏读问题,但是不能解决不可重复读和幻读
REPEATABLE_READA事务多次从数据库读取某条记录结果一致,可以解决不可重复读,不可以解决幻读
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_NEWA调用B,B需要新事务,如果A有事务就挂起,B自己创建一个新的事务
SUPPORTSA调用B,B有无事务无所谓,A有事务就加入到A事务中,A无事务B就以非事务方式执行
NOT_SUPPORTSA调用B,B以无事务方式执行,A如有事务则挂起
NEVERA调用B,B以无事务方式执行,A如有事务则抛出异常
MANDATORYA调用B,B要加入A的事务中,如果A无事务就抛出异常
NESTEDA调用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;
    }
// ... 省略其他配置 ...
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值