Spring AOP

引言(just I think): 因为java是面向对象编程,而面向对象是纵向继承机制,无法集中注意力到核心代码中,所以我们需要调用其他的机制来解决非核心代码的包装,将其从核心代码块中解耦出来

首先我们从spring官网了解一下他们对AOP思想的介绍

官网地址:docs.spring.io/spring-fram…

面向方面编程 (AOP) 通过提供另一种思考程序结构的方式来补充面向对象编程 (OOP)。OOP 中模块化的关键单元是类,而在 AOP 中,模块化的单元是方面 。方面使关注点模块化,例如跨越多种类型和对象的事务管理。(此类关注点在 AOP 文献中通常称为横切关注点。)

Spring 的关键组件之一是AOP 框架。虽然 Spring IoC 容器不依赖于 AOP,这意味着您不需要使用 AOP,但 AOP 补充了 Spring IoC 以提供非常强大的中间件解决方案。

具体概念

1
***AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面 向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况 下给程序动态统一添加额外功能的一种技术  。***

AOP的相关相关术语:

    1. 横切关注点

就是从每个核心方法中抽取出来的非核心代码(既非核心业务逻辑),最后放入一个类中(这个类叫切面),不同的业务逻辑实现不同的方法

1
这个概念不是语法层面天然存在的,而是根据附加功能(核心方法中的非核心代码)的逻辑上的需要:有十个附加功能(核心方法中的非核心代码),就有十个横切关注点。  
    1. 通知

img

简单地说就是在切面中,非核心业务也就是我们需要做的事,被封装成为一个方法(该方法叫通知)—-通知就是横切关注点的实现。

  1. 切面

封装横切关注点或者说是通知的类

  1. 目标

被代理的目标对象

  1. 代理

向目标对象应用通知之后创建的代理对象(不需要我们自己创建,封装的就是)AOP帮助我们创建

  1. 连接点

这也是一个纯逻辑概念,不是语法定义的。 把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉 点就是连接点

img

从哪里套出来的就放回哪里,不改变元代码的逻辑

  1. 切入点

定位连接点的方式(位置)。

img

作用

  • 简化代码: 把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能, 提高内聚性。
  • 代码增强: 把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就 被切面给增强了。

基于注解的AOP

技术说明:

img

几种动态代理

  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因 为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
  • AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最 终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。

通过阅读官方文档我们可以知道 :

Spring AOP 默认使用标准的 J2SE* 动态代理* 作为 AOP 代理。这使得任何接口(或接口集)都可以被代理。

启用AOP顺序

  1. 将目标对象和切面交给IOC容器管理(注解+扫描)
  2. 将切面类和目标类作为容器中的组件,所以加@Component
  3. 将当前组件用@Aspect注解标注为切面
  4. 开启基于注解的aop ( 开启AspectJ的自动代理,为目标对象自动生成代理 )
1
2
<!--    作用: 开启基于注解的aop-->
<aop:aspectj-autoproxy/>
  1. 配置好切入点表达式 :

声明一个方法,然后使用@Poincut注解来进行声明公共的切入点表达式 可) img

1
2
3
4
5
6
/**
* 切入点表达式的重用******重点
*/
// execution(权限修饰符 返回值 项目. 包/ * . 类/ * .方法 (..) )
@Pointcut("execution(* com_Ray.such.*.*(..))")
public void pointCut(){}

img

AOP的各种通知详解

各种通知的执行顺序:

Spring版本5.3.x以前:

1
前置通知 -->目标操作--> 后置通知--> 返回通知 / 异常通知

Spring版本5.3.x以后:

1
前置通知--> 目标操作--> 返回通知 / 异常通知--> 后置通知
  1. 前置语法:

使用@Before注解标识,在被代理的目标方法前执行

1
2
3
4
5
6
7
8
9
@Before("pointCut()")
public void beforeAdviceMethod(JoinPoint joinPoint){
//1. 获取连接点所对应方法的方法名
Signature signature = joinPoint.getSignature();
//2. 获取连接点所对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("方法名为: " + signature.getName()+ ",参数为: " + Arrays.toString(args));
// System.out.println("前置通知!");
}

joinPoint 作为前置通知的方法的参数 ,可以获取连接点多对应的方法的签名信息()

  1. 后置语法

注解 :@After(“point Cut()”)在方法执行之后的finally中执行

img

  1. 返回通知

注解: @AfterReturn(“point Cut()“) 在目标对象执行之后执行

1
2
3
4
5
6
7
8
9
10
/**
* 返回通知!
* 在目标方法返回值之后的一个通知
*/
@AfterReturning("pointCut()")
public void afterReturn(JoinPoint joinPoint){
Signature signature = joinPoint.getSignature();
System.out.println("方法: " + signature.getName()+"执行返回通知");

}
  1. 异常通知
1
2
3
4
5
6
7
8
/**
* 异常通知
*/
@AfterThrowing(value = "pointCut()",throwing = "ex")
public void AfterThrow(JoinPoint joinPoint, Throwable ex){
Signature signature = joinPoint.getSignature();
System.out.println("方法: " + signature.getName()+"的异常通知:" + ex);
}
  1. 环绕通知---@Around( )

设置环绕通知之后就可以将所有前面的通知都涵盖进去

img

基于XML的AOP

其基本与基于注解实现一样

配置xml环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<context:component-scan base-package="com.atguigu.aop.xml"></context:componentscan>
<aop:config>
<!--配置切面类-->
<aop:aspect ref="loggerAspect">
<aop:pointcut id="pointCut" expression="execution(*
com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
<aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
<aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
<aop:after-returning method="afterReturningMethod" returning="result"
pointcut-ref="pointCut"></aop:after-returning>
<aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcutref="pointCut"></aop:after-throwing>
<aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
<aop:aspect ref="validateAspect" order="1">
<aop:before method="validateBeforeMethod" pointcut-ref="pointCut">
</aop:before>
</aop:aspect>
</aop:config>