在我们讲解Spring Aop之前,我们先要说一下Aop。Aop是面向对象的一种补充,可以让我们以面向切面的方式进行编程。面向切面的编程是一种编程范式,旨在通过允许横切关注点的分离,提高模块化。

Aop实现的关键就是把切面应用到目标对象,实现Aop的技术可分为静态织入和动态代理。

  • 静态织入又可分为编译时织入、编译后织入和类加载时织入;
  • 动态代理则是在运行时创建代理对象。

Spring Aop和AspectJ有什么区别

Spring AOP AspectJ
由Spring使用动态代理方式实现 是一个完整的Aop解决方案,比Spring Aop更强大
不需要单独的编译过程 除非设置 LTW,否则需要 AspectJ 编译器 (ajc)
仅支持运行时织入 支持编译时、编译后和加载时织入
仅支持方法级编织 可以编织字段、方法、构造函数、静态初始值设定项、最终类/方法等
只能在由 Spring 容器管理的 bean 上实现 可以在所有域对象上实现
仅支持方法执行切入点 支持所有切入点
代理是由目标对象创建的, 并且切面应用在这些代理上 在执行应用程序之前直接在代码中进行织入
性能较低 性能高

总而言之,Spring Aop是Aop的一种解决方案,它不是一个完整的Aop实现,而是在 AOP 实现和 Spring IoC 之间提供紧密的集成,以帮助解决企业应用程序中的常见问题。AspectJ是一个与Spring无关的Aop实现。可能有的同学有疑问了,为什么Spring中也有AspectJ呢?就像我们经常在Spring使用的@AspectJ注解!

这里说明下,并不是说Spring中直接使用了AspectJ,Spring 使用 AspectJ 提供的用于切入点解析和匹配的库来解释AspectJ 5相同的注解。但是,Spring AOP 运行时仍然是纯 Spring AOP,是使用动态代理实现的,并且不依赖于 AspectJ 编译器或编织器。

Aop基本概念

通知(Advice)

通知定义了在切入点代码执行时间点附近需要做的工作。

Spring支持五种类型的通知:

  • Before:在方法调用之前执行,详情查看MethodBeforeAdviceInterceptor
  • After-returning:在方法返回之后执行,详情查看AfterReturningAdviceInterceptor
  • After-throwing: 在方法抛出异常后执行,不抛异常不执行,详情查看AspectJAfterThrowingAdvice
  • Around:在方法执行前后执行,Around的优先级比Before高,详情查看AspectJAroundAdvice
  • Introduction: 引介增强,上面的四种类型都是增强方法,引介增强是增强类,可以在运行时动态的添加某些方法。要想动态添加方法,需要定义接口,一般该方法定义的接口实现类要实现DelegatingIntroductionInterceptor类

注意:

MethodBeforeAdviceInterceptor是由AspectJMethodBeforeAdvice适配出来的,因为AspectJMethodBeforeAdvice没有实现MethodIntercetor接口。

AfterReturningAdviceInterceptor是由AspectJAfterReturningAdvice适配出来的,因为AspectJAfterReturningAdvice没有实现MethodIntercetor接口。

而AspectJAfterThrowingAdvice,AspectJAroundAdvice本身就实现了MethodIntercetor接口,所以是没有经过适配的。

这里不知道为什么同样是Advice,有的实现了MethodIntercetor接口,有的没有实现,而是通过适配器去转换,区别对待?

连接点(Joinpoint)

程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法调用前、异常抛出时、方法返回后等。这个概念已经隐含在通知Advice中了。

切入点(Pointcut)

通知定义了切面要发生的“故事”,连接点定义了“故事”发生的时机,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定。

切入点表达式有10种:

  • execution:用于匹配方法执行的连接点
  • within:用于匹配指定类型内的方法执行
  • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也* 类型匹配
  • target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配
  • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
  • @within:用于匹配所以持有指定注解类型内的方法
  • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
  • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行
  • @annotation:用于匹配当前执行方法持有指定注解的方法
  • bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法

execution格式:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
  • 其中带 ?号的 modifiers-pattern?,declaring-type-pattern?,hrows-pattern?是可选项
  • ret-type-pattern,name-pattern, parameters-pattern是必选项
  • modifier-pattern? 修饰符匹配,如public 表示匹配公有方法
  • ret-type-pattern 返回值匹配,* 表示任何返回值,全路径的类名等
  • declaring-type-pattern? 类路径匹配
  • name-pattern 方法名匹配,* 代表所有,set*,代表以set开头的所有方法
  • (param-pattern) 参数匹配,指定方法参数(声明的类型),(..)代表所有参数,(,String)代表第一个参数为任何值,第 * 二个为String类型,(..,String)代表最后一个参数是String类型
  • throws-pattern? 异常类型匹配

切面(Aspect)

通知、连接点、切入点共同组成了切面:时间、地点和要发生的“故事”。

引入(Introduction)

引入允许我们向现有的类添加新的方法和属性。

目标(Target)

即被通知的对象。

代理(proxy)

应用通知的对象。

织入(Weaving)

把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:

  • 编译时:编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器;

  • 类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;

  • 运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理是动态代理技术。

Spring Aop的基本概念

Advisor

封装了通知(Advice)和切入点(Pointcut)的对象称为Advisor。

MethodInterceptor

顾名思义,方法拦截器,该接口继承了Advice接口,定义了invoke()方法。Spring Aop所有的切面通知最种都要转换成方法拦截器才能去执行。

IntroductionInterceptor

引介增强接口,如果要实现引介增强,增强接口的实现类必须实现IntroductionInterceptor接口。

动态代理

JDK动态代理

JDK动态代理要求被代理类必须实现了接口。如果要增强的方法实现类实现了接口,默认会使用JDK动态代理,其原理是通过接口实现代理类。

CGLIB动态代理

如果被代理类没有实现接口,会使用CGLIB动态代理,其原理是通过生成该类的子类进行代理。如果proxyTargetClass为true,则强制走CGLIB动态代理。