Spring AOP 应用

AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往应用在权限校验、日志、事务控制、性能监控中。

Spring AOP 应用

  • 1 AOP术语
  • 2 Spring中AOP代理选择
  • 3 Spring中AOP的配置方式
  • 4 Spring中AOP实现
    • 4.1 XML模式
    • 4.2 XML+注解模式
    • 4.3 纯注解模式
  • 5 Spring 声明式事务的支持
    • 5.1 事务回顾
      • 5.1.1 事务的概念
      • 5.1.2 事务的四大特性
      • 5.1.3 事务的隔离级别
      • 5.1.4 事务的传播行为
    • 5.2 Spring中事务的API
    • 5.3 Spring基于注解的声明式事务配置

1 AOP术语

名词 解释
Joinpoint(连接点) 它指的是那些可以用于把增强代码加入到业务主线中的点,这些点指的就是方法。在方法执行的前后通过动态代理技术假如增强的代码。在Spring框架AOP思想的技术实现中,也只支持方法类型的连接点。
Pointcut(切入点) 它指的是那些已经把增强代码加入到业务主线进来之后的连接点。在项目中判断是否有访问权限就是一个连接点
Advice(通知/增强) 它指的是切面类中用于提供增强功能的方法,并且不同的方法增强的时机是不一样的。比如,开启事务肯定要在业务方法执行之前执行;提交事务要在业务方法正常执行之后执行,而回滚事务要在业务方法执行产生异常之后执行等等。那么这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知
Target(目标对象) 它指的是代理的目标对象。即被代理对象。
Proxy(代理) 它指的是一个类被AOP织如增强后,产生的代理类。即代理对象。
Weaving(织入) 它指的是把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Aspect(切面) 它指定是增强的代码所关注的方面,把这些相关的增强代码定义到一个类中,这个类就是切面类。例如:事务切面,它里面定义的方法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务无关的方法。
连接点:方法开始时、结束时、正常运行完毕时、方法异常时等这些特殊的时机点,我们称之为连接点,项目中每个方法都有连接点,连接点也是一种候选点
切入点: 指定AOP思想想要影响的具体方法时哪些
Advice增强:
     第一个层次:指的是横切逻辑
     第二个层次:方位点(在某一些连接点上加入横切逻辑,那么这些连接点就叫做方位点)
Aspect切面:切面概念是对上述概念的一个综合
     切面 = 切入点 + 增强
          = 切入点(锁定方法)+ 方位点(锁定方法中的特殊时机)+横切逻辑

众多的概念,目的就是为了锁定要在哪个地方插入什么横切逻辑代码

2 Spring中AOP代理选择

Spring实现AOP思想使用的是动态代理技术
默认情况下,Spring会根据被代理对象是否实现接口来选择使用JDK还是CGLIB。当被代理对象没有实现任何接口时,Spring会选择CGLIB。当被代理对象实现了接口,Spring会选择JDK官方的代理技术,不过我们可以通过配置的方式,让Spring强制使用CGLIB。

3 Spring中AOP的配置方式

在Spring的AOP配置中,也和IoC配置一样,支持3类配置方法

  • 使用XML配置
  • 使用XML+注解组合配置
  • 使用纯注解配置

4 Spring中AOP实现

4.1 XML模式

  • 坐标
<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-aopartifactId>
    <version>5.3.15version>
dependency>
<dependency>
    <groupId>org.aspectjgroupId>
    <artifactId>aspectjweaverartifactId>
    <version>1.9.7version>
dependency>
  • AOP 核心配置


<bean id="logUitls" class="com.lxsh.utils.LogUtils">bean>

<aop:config>
	
	<aop:aspect id="idAdvice" ref="logUtils">
		
		<aop:before method="printLog" pointcut="execution(public * com.lxsh.service.*.*(..))">aop:before>
	aop:aspect>
aop:config>
  • 细节
    关于切入点表达式
    上述配置实现了对service包下任意方法进行增强,在其执行之前,输出了记录日志的语句。在这里,我们接触了一个比较陌生的名称:切入点表达式,它是做什么的呢?我们继续往下看。
    概念及作用
    切入点表达式,也称之为AspectJ切入点表达式,**指的是遵循特定语法结构的字符串,其作用是用于对符合语法格式的连接点进行增强。**他是AspectJ表达式的一部分。
    关于AspectJ
    AspectJ是一个基于Java语言的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切入点表达式的部分,开始支持AspectJ切入点表达式。
    切入点表达式的使用示例

    全限定方法名     访问修饰符     返回值     包名.包名.包名.类名.方法名(参数列表)
    全匹配方式
    public void com.lxsh.service.UserService.saveUser(com.lxsh.po.User)
    访问修饰符可以省略
    void com.lxsh.service.UserService.saveUser(com.lxsh.po.User)
    返回值可以用  *   表示任意返回值
    * com.lxsh.service.UserService.saveUser(com.lxsh.po.User)
    包名可以使用  .   表示任意包,但是有几级包,必须写几个
    * ...UserService.saveUser(com.lxsh.po.User)
    包名可以使用  ..   表示当前包及其子包
    * ..UserService.saveUser(com.lxsh.po.User)
    类名和方法名,都可以用 . 表示任意类、任意方法
    * ...(com.lxsh.po.User)
    参数列表,可以使用具体类型
    基本类型直接写类型名称:int
    引入类型必须写全限定类型:java.lang.String
    参数列表可以使用*,表示任务参数类型,但是必须有参数
    * *..*.*(*)
    参数列表也可以使用 ..  表示有无参数均可,有参数可以是任意类型
    * *..*.*(..)
    全通配方式
    * *..*.*(..)
    

    改变代理方式的配置
    在前面我们已经说了,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择的。被代理对象实现了接口,则采用基于接口的动态代理。当被代理对象没有实现任何接口的时候,Spring会自动切换到基于子类的动态代理方式。
    但是我们都知道,无论被代理对象是否实现接口,只要不是final修饰的类都可以采用CGLIB提供的方式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的方式实现强制使用基于子类的动态代理(即CGLIB的方式),配置方式有两种


<aop:config proxy-target-class="true">aop:config>

<aop:aspectj-autoproxy proxy-target-class="true">aop:aspectj-autoproxy>

五种通知类型

  1. 前置通知
    配置方式:aop:before标签

<aop:before method="beforeMethod" pointcut-ref="pt">aop:before>

执行时机
前置通知永远都会在切入点方法(业务核心方法)执行之前执行。
细节
前置通知可以获取切入点方法的参数,并对其进行增强。

  1. 后置通知
    配置方式:aop:after-returning标签

<aop:after-returning method="returningMethod" pointcut-ref="pt">aop:after-returning>

执行时机
后置通知的执行时机时在切入点方法正常执行之后执行(业务核心方法),当切入点方法执行产生异常之后,后置通知就不再执行了,而是执行异常通知。
细节
后置通知既可以获取切入点方法的参数,也可以获取到切入点方法的返回值。

  1. 异常通知
    配置方式

<aop:after-throwing method="throwingMethod" pointcut-ref="pt">aop:after-throwing>

执行时机
异常通知的执行时机时在切入点方法(业务核心方法)执行产生异常之后,异常通知执行,如果切入点方法执行没有产生异常,则异常通知不会执行。
细节
异常通知不仅可以获取切入点方法执行的参数,也可以获取切入点方法执行产生的异常信息。

  1. 最终通知
    配置方式

<aop:after method="afterMethod" pointcut-ref="pt">aop:after>

执行时机
最终通知的执行时机时在切入点方法正常执行之后执行(业务核心方法),切入点方法返回之前执行,换句话说,无论切入点方法执行是否产生异常,他都会在返回之前执行。
细节
最终通知执行时,可以获取到通知方法的参数,同时它可以做一些清理操作。

  1. 环绕通知
    配置方式

<aop:around method="aroundMethod" pointcut-ref="pt">aop:around>

特别说明
环绕通知,它是有别于前面四种通知类型外的特殊通知,前面四种通知(前置、后置、异常和最终)它们都是指定何时增强的通知类型。而环绕通知,它是Spring框架为我们提供的一种可以通过编码的方式,控制增强代码何时执行的通知类型,它里面借助的ProceedingJoinPoint接口及其实现类,实现手动触发切入点方法的调用。
执行时机
当配置环绕通知之后,在环绕通知里面必须要明确调用业务层的方法,如果不调用,就会出现只出现通知,而不执行方法。其中原理和动态代理是一样的
细节
环绕通知可以代替其他的四个通知,所以环绕通知不可以与其他四种通知共用。

4.2 XML+注解模式

基于xml模式的演进

/**
 * @Aspect注解 开启切面
 */
@Component
@Aspect
public class LogUtils{

	/**
	 * 配置切入点
	 */
	@Pointcut("execution(public * com.lxsh.service.*.*(..))")
	public void pointCut(){
	}

	/**
	 * 业务逻辑开始之前
	 */
	@Before("pointCut()")
	public void beforeMethod(JoinPoint joinPoint){
		//方法参数
		Object[] args = joinPoint.getArgs();
		System.out.println("业务逻辑开始之前执行...")
	}

	/**
	 * 业务逻辑结束时执行(无论异常与否)
	 */
	@After("pointCut()")
	public void afterMethod(JoinPoint joinPoint){
		System.out.println("业务逻辑结束时执行(无论异常与否)...")
	}

	/**
	 * 业务异常时执行
	 */
	@AfterThrowing("pointCut()")
	public void throwingMethod(JoinPoint joinPoint){
		System.out.println("异常时执行...")
	}

	/**
	 * 业务逻辑正常时执行
	 */
	@AfterReturning(value = "pointCut()" returning ="retVal")
	public void sucessMethod(Object retVal){
		System.out.println("业务逻辑正常时执行...")
	}

	/**
	 * 环绕通知
	 */
	@Around("pointCut()")
	public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
		System.out.println("环绕通知中的before method...")
		Object result = null
		try{
			result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
			System.out.println("环绕通知中的beforemethod...")
		}catch(Exception e){
			System.out.println("环绕通知中的throwing method...")
		}finally{
			System.out.println("环绕通知中的after method...")
		}
		System.out.println("环绕通知中的afterReturning method...")
		return result;
	}
}

<aop:aspectj-autoproxy>aop:aspectj-autoproxy>

4.3 纯注解模式

基于xml+注解模式的演进

在启动类/配置类上加入以下注释即可
@EnableAspectJAutoProxy

5 Spring 声明式事务的支持

**编程式事务:**在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
**声明式事务:**通过XML或者注解配置的方式达到事务控制的目的,叫做声明式事务

5.1 事务回顾

5.1.1 事务的概念

事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功,从而确保了数据的准确与安全。
例如:A–>B转账,对应如下两条sql语句:

/*转出账户减钱*/
update account set money = money-100 where name = 'A';
/*转入账户加钱*/
update account set money = money+100 where name = 'B';

这两条语句的执行,要么全部成功,要么全部不成功。

5.1.2 事务的四大特性

原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么多发生,要么都不发生。
从操作的角度来描述,事务中的各个操作要么都成功要么都失败
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态
例如转账前A有1000,B有1000,转账后A+B也的是2000.
一致性时从数据的角度来说的。(1000,1000) (900,1100) 不应该出现(900,1000)
隔离性(isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务。
每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
比如:事务1给员工涨工资2000,但是事务1尚未被提交,员工发起事务2查询工资,发现工资涨了2000块钱,读到了事务1尚未提交的数据(脏读)
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性,接下来即使数据库发生故障也不应该对其有任何影响。

5.1.3 事务的隔离级别

不考虑隔离级别,会出现以下情况:(以下情况全是错误的),也即为隔离级别在解决事务并发问题

  • 脏读:一个线程中的事务读到了另外一个线程中未提交的数据。
  • 不可重复读:一个线程中的事务读到了另外一个线程中已经提交的update的数据(前后内容不一样)
    场景:
    员工A发起事务1,查询工资,工资为1W,此时事务1尚未关闭
    财务人员发起事务2,给员工A涨了2000块钱,并且提交了事务
    员工A通过事务1再次发起查询请求,发现工资为1.2W,原来读出来1W读取不到了,叫做不可重复读
  • 幻读(虚读):一个线程中的事务读到了另外一个线程中已经提交的insert或者delete数据(前后条数不一样)
    场景:
    事务1查询所有工资为1w的员工的总数,查询出来10个人,此时事务尚未关闭
    事务2财务人员发起,新来员工,工资1w,向表中插入了2条数据,并且提交了事务
    事务1再次查询工资为1w的员工个数,发现有12个人,这就是幻读

数据库共定义了四种隔离级别:
Serializable(串行化):可避免脏读,不可重复读,虚读情况的发生。(串行化) 最高
Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。(幻读有可能发生) 第二
该机制下会对update的行进行加锁
Read committed(读已提交):可避免脏读情况发生,不可重复读和幻读一定会发生。 第三
Read uncommitted(读未提交):最低级别,以上情况均无法保证(读未提交) 最低
注意:级别依次升高,效率依次降低
MySQL的默认隔离级别是:REPEATABLE READ
查询当前使用的隔离级别:select @@tx_isolation;
设置MySQL事务的隔离级别:set session transaction isloation level xxx;(设置的是当前mysql连接会话的,并不是永久改变的)

5.1.4 事务的传播行为

事务往往在service层进行控制,如果出现service层方法A调用了另外一个service层方法B,A和B方法本身都已经被添加了事务控制,那么A调用B的时候,就需要进行事务的一些协商,这就叫做事务的传播行为。
A调用B,我们站在B的角度来观察来定义事务的传播行为(前两种情况最常用)

行为 描述
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作

5.2 Spring中事务的API

PlatformTransactionManager

public interface PlatformTransactionManager{
	/**
	 * 获取事务状态信息
	 */
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
}
	/**
	 * 提交事务
	 */
	void commit(TransactionStatus status) throws TransactionException;

	/**
	 * 回滚事务
	 */
	void rollback(TransactionStatus status) throws TransactionException;

作用
此接口是Spring的事务管理器核心接口。Spring本身并不支持事务实现,只是负责提供标准,应用底层支持什么样的事务,需要提供具体实现类。此处也是策略模式的具体应用给。在Spring框架中,也为我们内置了一些具体策略,例如:DataSourceTransactionManager,HibernateTransactionManager等等(HibernateTransactionManager事务管理器在spring-orm-5.1.12.RELEASE.jar中)
SpringJdbcTemplate(数据库操作工具)
DataSourceTransactionManager(Mybatis) 归根结底是横切逻辑代码,声明式事务要做的就是使用AOP(动态代理)将事务控制逻辑织入到业务代码

5.3 Spring基于注解的声明式事务配置

基于XML+注解
- 事务的必要坐标

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-contextartifactId>
    <version>5.3.15version>
dependency>
<dependency>
    <groupId>org.aspectjgroupId>
    <artifactId>aspectjweaverartifactId>
    <version>1.9.7version>
dependency>
<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-jdbcartifactId>
    <version>5.3.15version>
dependency>
<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-txartifactId>
    <version>5.3.15version>
dependency>
  • 配置事务

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource">property>
bean>

<tx:annotation-driven transaction-manager="transactionManager"/>
  • 在接口、类或者方法上添加@Transaction注解
@Transaction(readOnly=true,propagation=Propagation.SUPPORTS)

基于纯注解
Spring基于注解驱动开发的事务控制配置,只需要把xml配置部分改为注解实现,只是需要一个注解替换掉xml配置文件中的配置。
在Spring配置类上添加@EnableTransactionManagement注解即可

//开启Spring注解事务的支持
@EnableTransactionManagement
public class SpringConfiguration{

}

你可能感兴趣的:(Java,spring,java,后端)