Spring AOP 应用:三种配置及实现方式

Posted 丿涛哥哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring AOP 应用:三种配置及实现方式相关的知识,希望对你有一定的参考价值。

Spring AOP 应用:三种配置及实现方式

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

1、 AOP 相关术语

1.1、 业务主线

在讲解AOP术语之前,我们先来看一下下面这两张图,在此基础上去进一步回顾AOP,不进行实现

Spring_49

上图描述的就是未采用AOP思想设计的程序,当我们红色框中圈定的方法时,会带来⼤量的重复劳动。 程序中充斥着⼤量的重复代码,使我们程序的独立性很差。而下图中是采用了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运用动态代理技术,在运行期对需要使用的业务逻辑方法进行增强。
Spring_50

1.2、 AOP 术语

名词解释
Joinpoint(连接点)它指的是那些可以用于把增强代码加入到业务主线中的点,那么由上图中我们可以看出,这些点指的就是方法。在方法执行的前后通过动态代理技术加入增强的代码。在Spring框架AOP思想的技术实现中,也只支持方法类型的连接点。
Pointcut(切入点)它指的是那些已经把增强代码加入到业务主线进来之后的连接点。由上图中,我们看出表现层 transfer 方法就只是连接点,因为判断访问权限的功能并没有对其增强。
Advice(通知/增强)它指的是切面类中用于提供增强功能的方法。并且不同的方法增强的时机是不一 样的。比如,开启事务肯定要在业务方法执行之前执行;提交事务要在业务方法正常执行之后执行,而回滚事务要在业务方法执行产生异常之后执行等等。那么这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知。
Target(目标对象)它指的是代理的目标对象。即被代理对象。
Proxy(代理)它指的是一个类被AOP织入增强后,产生的代理类。即代理对象。
Weaving(织入)它指的是把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Aspect(切面)它指定是增强的代码所关注的方面,把这些相关的增强代码定义到一个类中,这个类就是切面类。例如,事务切面,它里面定义的方法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务无关的方法。我们前面的案例中 TrasnactionManager 就是一个切面。

连接点:方法开始时、结束时、正常运行完毕时、方法异常时等这些特殊的时机点,我们称之为连接点,项目中每个方法都有连接点,连接点是一种候选点

切入点:指定AOP思想想要影响的具体方法是哪些,描述感兴趣的方法

Advice增强:

第一个层次:指的是横切逻辑

第⼆个层次:方位点(在某一些连接点上加入横切逻辑,那么这些连接点就叫做方位点,描述的是具体 的特殊时机)

Aspect切面:切面概念是对上述概念的一个综合

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实现

需求:横切逻辑代码是打印日志,希望把打印日志的逻辑织入到目标方法的特定位置(service层transfer 方法)

4.1、 XML 模式

Spring是模块化开发的框架,使用aop就引入aop的jar

1、坐标

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aop</artifactId>
	<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.9.4</version>
</dependency>

2、AOP 核心配置

<!--
    Spring基于XML的AOP配置前期准备:
    在spring的配置⽂件中加⼊aop的约束
    xmlns:aop="http://www.springframework.org/schema/aop"
    http://www.springframework.org/schema/aop
    https://www.springframework.org/schema/aop/spring-aop.xsd

    Spring基于XML的AOP配置步骤:
        第⼀步:把通知Bean交给Spring管理
        第⼆步:使⽤aop:config开始aop的配置
        第三步:使⽤aop:aspect配置切⾯
        第四步:使⽤对应的标签配置通知的类型
        ⼊⻔案例采⽤前置通知,标签为aop:before
-->
<!--把通知bean交给spring来管理-->
<bean id="logUtil" class="com.tao.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config>
    <!--配置切⾯-->
    <aop:aspect id="logAdvice" ref="logUtil">
        <!--配置前置通知-->
        <aop:before method="printLog" pointcut="execution(public *
            com.tao.service.impl.TransferServiceImpl.updateAccountByCardNo(com.tao.pojo.Account))"></aop:before>
    </aop:aspect>
</aop:config>

细节:

1、关于切入点表达式

上述配置实现了对 TransferServiceImpl 的 updateAccountByCardNo 方法进行增强,在其执行之前,输出了记录日志的语句。这里面,我们接触了一个比较陌生的名称:切入点表达式,它是做什么的呢?我们往下看。

  • 概念及作用

切入点表达式,也称之为AspectJ切入点表达式,指的是遵循特定语法结构的字符串,其 作用是用于对符合语法格式的连接点进行增强。它是AspectJ表达式的一部分。

  • 关于AspectJ

AspectJ是一个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切入点表达式的部分,开始支持AspectJ切入点表达式。

  • 切入点表达式使用示例
全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
	全匹配⽅式:
		public void com.tao.service.impl.TransferServiceImpl.updateAccountByCardNo(com.tao.pojo.Account)
	访问修饰符可以省略
		void com.tao.service.impl.TransferServiceImpl.updateAccountByCardNo(com.tao.pojo.Account)
	返回值可以使⽤*,表示任意返回值
		* com.tao.service.impl.TransferServiceImpl.updateAccountByCardNo(com.tao.pojo.Account)
	包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
 		* ....TransferServiceImpl.updateAccountByCardNo(com.tao.pojo.Account)
 	包名可以使⽤..表示当前包及其⼦包
 		* ..TransferServiceImpl.updateAccountByCardNo(com.tao.pojo.Account)
 	类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
 	* ...(com.tao.pojo.Account)
 	参数列表,可以使⽤具体类型
 	基本类型直接写类型名称 : int
 	引⽤类型必须写全限定类名:java.lang.String
 	参数列表可以使⽤*,表示任意参数类型,但是必须有参数
 		* *..*.*(*)
 	参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
 		* *..*.*(..)
 	全通配⽅式:
 		* *..*.*(..)

2、改变代理方式的配置:

在前面我们已经说了,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择 的。被代理对象实现了接口,则采用基于接口的动态代理。当被代理对象没有实现任何接口的时候,Spring会⾃动切换到基于子类的动态代理方式。

但是我们都知道,无论被代理对象是否实现接口,只要不是final修饰的类都可以采用cglib提 供的方式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的方式实现强制使用 基于子类的动态代理(即cglib的方式),配置的方式有两种

  • 使用aop:config标签配置
<aop:config proxy-target-class="true">
  • 使用aop:aspectj-autoproxy标签配置
<!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP的⽀持-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectjautoproxy>

3、五种通知类型

1、前置通知

配置方式:aop:before标签

<!--
	作⽤:
        ⽤于配置前置通知。
    出现位置:
        它只能出现在aop:aspect标签内部
    属性:
        method:⽤于指定前置通知的⽅法名称
        pointcut:⽤于指定切⼊点表达式
        pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:before method="printLog" pointcut-ref="pointcut1">
</aop:before>

执行时机

前置通知永远都会在切入点方法(业务核心方法)执行之前执行。

细节

前置通知可以获取切入点方法的参数,并对其进行增强。

2、正常执行时通知

配置方式

<!--
    作⽤:
    	⽤于配置正常执⾏时通知
    出现位置:
    	它只能出现在aop:aspect标签内部
    属性:
        method:⽤于指定后置通知的⽅法名称
        pointcut:⽤于指定切⼊点表达式
        pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after-returning method="afterReturningPrintLog" pointcutref="pt1"></aop:after-returning>

3、异常通知

配置方式

<!--
    作⽤:
    	⽤于配置异常通知。
    出现位置:
    	它只能出现在aop:aspect标签内部
    属性:
        method:⽤于指定异常通知的⽅法名称
        pointcut:⽤于指定切⼊点表达式
        pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>

执行时机

异常通知的执行时机是在切入点方法(业务核心方法)执行产生异常之后,异常通知执行。如果切入点方法执行没有产生异常,则异常通知不会执行。

细节

异常通知不仅可以获取切入点方法执行的参数,也可以获取切入点方法执行产生的异常信息。

4、最终通知

配置方式

<!--
    作⽤:
    	⽤于指定最终通知。
    出现位置:
    	它只能出现在aop:aspect标签内部
    属性:
        method:⽤于指定最终通知的⽅法名称
        pointcut:⽤于指定切⼊点表达式
        pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>

执行时机

最终通知的执行时机是在切入点方法(业务核心方法)执行完成之后,切入点方法返回之前执行。 换句话说,无论切入点方法执行是否产生异常,它都会在返回之前执行。

细节

最终通知执行时,可以获取到通知方法的参数。同时它可以做一些清理操作。

5、环绕通知

配置方式

<!--
    作⽤:
    	⽤于配置环绕通知。
    出现位置:
    	它只能出现在aop:aspect标签的内部
    属性:
        method:⽤于指定环绕通知的⽅法名称
        pointcut:⽤于指定切⼊点表达式
        pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
**特别说明**

环绕通知,它是有别于前⾯四种通知类型外的特殊通知。前⾯四种通知(前置,后置,异常和最终)
它们都是指定何时增强的通知类型。⽽环绕通知,它是Spring框架为我们提供的⼀种可以通过编码的
⽅式,控制增强代码何时执⾏的通知类型。它⾥⾯借助的ProceedingJoinPoint接⼝及其实现类,
实现⼿动触发切⼊点⽅法的调⽤。

4.2、 XML+注解模式

XML 中开启 Spring 对注解 AOP 的支持

<!--开启spring对注解aop的支持-->
<aop:aspectj-autoproxy/>

示例

@Component
@Aspect
public class LogUtil {
    /**
    * 我们在xml中已经使用了通用切入点表达式,供多个切面使用,那么在注解中如何使用呢?
    * 第一步:编写一个方法
    * 第⼆步:在方法使用@Pointcut注解
    * 第三步:给注解的value属性提供切入点表达式
    * 细节:
    * 1.在引用切入点表达式时,必须是方法名+(),例如"pointcut()"。
    * 2.在当前切面中使用,可以直接写方法名。在其他切面中使用必须是全限定方法名。
    */
	@Pointcut("execution(* com.lagou.service.impl.*.*(..))")
	public void pointcut(){}
    
	@Before("pointcut()")
	public void beforePrintLog(JoinPoint jp){
		Object[] args = jp.getArgs();
		System.out.println("前置通知:beforePrintLog,参数是:"+
		Arrays.toString(args));
	}
    
	@AfterReturning(value = "pointcut()",returning = "rtValue")
	public void afterReturningPrintLog(Object rtValue){
		System.out.println("后置通知:afterReturningPrintLog,返回值是:"+rtValue);
	}
    
	@AfterThrowing(value = "pointcut()",throwing = "e")
	public void afterThrowingPrintLog(Throwable e){
		System.out.println("异常通知:afterThrowingPrintLog,异常是:"+e);
	}
    
	@After("pointcut()")
	public void afterPrintLog(){
		System.out.println("最终通知:afterPrintLog");
	}
    
    /**
    * 环绕通知
    * @param pjp
    * @return
    */
	@Around("pointcut()")
	public Object aroundPrintLog(ProceedingJoinPoint pjp){
		//定义返回值
		Object rtValue = null;
		try{
		//前置通知
        System.out.println("前置通知");
        //1.获取参数
        Object[] args = pjp.getArgs();
        //2.执行切入点方法
        rtValue = pjp.proceed(args);
        //后置通知
        System.out.println("后置通知");
		}catch (Throwable t){
            //异常通知
            System.out.println("异常通知");
            t.printStackTrace();
		}finally {
            //最终通知
            System.out.println("最终通知");
		}
		return rtValue;
	}
}

4.3、 注解模式

在使用注解驱动开发aop时,我们要明确的就是,是注解替换掉配置⽂件中的下面这行配置:

<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>

在配置类中使用如下注解进行替换上述配置

@Configuration
@ComponentScan("com.tao")
@EnableAspectJAutoProxy //开启spring对注解AOP的支持
public class SpringConfiguration {
}

以上是关于Spring AOP 应用:三种配置及实现方式的主要内容,如果未能解决你的问题,请参考以下文章

AOP的三种实现方式之一通过xml配置文件实现

spring aop配置及实现

Spring——AOP实现的三种方式

Spring实现AOP的三种方式

在Spring中使用AOP切面编程

使用Spring配置文件实现AOP