Spring AOP 介绍与实现原理

未来已来2018-09-14 11:39

作者:倪震洋


AOP 概念

Joinpoint

在系统运行之前,AOP的功能模块都需要植入到OOP的功能模块中。所以,要进行这种植入过程,我们需要知道在系统的哪些执行点上进行植入操作,这些将要在其之上进行植入操作的系统执行点就称之为Joinpoint

常见的Joinpoint类型

方法调用(Method Call)。当某个方法被调用的时候所处的程序执行点

方法调用执行(Method Call execution)。方法执行,该Joinpoint类型代表的是某个方法内部执行开始时点,应该与方法调用类型的Joinpoint进行区分。

构造方法调用(Constructor Call)。

构造方法执行(Constructor Call Execution)。

字段设置(Field Set)。

字段获取(Field Get)。

异常处理执行(Exception Handler Execution)。该类型的Joinpoint对应程序执行过程中,在某些类型异常抛出后,对应的异常处理逻辑执行的时点。

类初始化(Class initialization)。类初始化型的Joinpoint,指的是类中某些静态类型或者静态块的初始化时点。


Pointcut

Pointcut 概念代表的是Joinpoint的表述方式。将横切逻辑植入当前系统的过程中,需要参照Pointcut规定的Joinpoint信息,才可以知道应该往系统的哪些Joinpoint上植入横切逻辑。

Joinpoint的描述

Pointcut的表述方式

1:直接指定Joinpoint所在方法名称。这种形式的Pointcut表述方式比较简单,功能单一

2:正则表达式。spring AOP 采用这种方式

Pointcut运算

Pointcut Pointcut 之间还可以进行逻辑运算。这样,我们就可以从简单的Pointcut开始,然后通过逻辑运算,得到最终需要的可能较为复杂的Pointcut


Advice

Advice 是单一横切关注点逻辑的载体,它代表将会植入到Joinpoint的横切逻辑。如果将Aspect比作OOP中的Class,那么Advice就相当于Class中的Method

按照AdviceJoinpoint位置执行时机的差异或者完成功能的不同,Advice可以分成多种具体形式。

1Before Advice

Before Advice 是在 Joinpoint 指定位置之前执行的Advice类型。通常,它不会中断程序执行流程,但如果必要,可以通过在Before Advice中抛出异常的方式来中断当前程序流程。如果当前Before Advice将被植入到方法执行类型的Joinpoint,那么这个Before Advice就会先于方法执行而执行。

通常,可以使用Before Advice做一些系统的初始化工作,比如设置系统初始值,获取必要系统资源等。当然,并非就限于这些情况。如果要用Before Advice来封装安全检查的逻辑,也不是不可以的,但通常情况下,我们会使用另一种形式的Advice

2After Advice

After Advice 就是在相应 Joinpoint 之后执行的Advice类型,但该类型的Advice还可以细分为以下三种。

After returning Advice。只有当前Joinpoint处执行流程正常完成后,After returning Advice才会执行。比如方法执行正常返回而没有抛出异常。

After throwing Advice。又称Throws Advice,只有在当前Joinpoint执行过程中抛出异常的情况下,才会执行。

After Advise。或许叫AfterFinallyAdvice更为确切,该类型Advice不管Joinpoint处执行流程是正常终了还是抛出异常都会执行,就好像Java中的finally块一样。

3Around Advice

Around Advice 对附加其上的Joinpoint进行“包裹”,可以在Joinpoint之前和之后都指定相应的逻辑,甚至于中断或者忽略Joinpoint处原来程序流程的执行。

Servlet 规范中提供的Filter功能就是 Around Advice 的一种体现。

4Introduction

Introduction 不是根据横切逻辑在 Joinpoint 处的执行时机来区分的,而是根据它可以完成的功能而区别于其他Advice类型。

Introduction可以为原有的对象添加新的特性或者行为


Aspect

Aspect 是对系统中的横切关注点逻辑进行模块化封装的AOP概念实体。通常情况下,Aspect可以包含多个Pointcut以及相关Advice定义。

ProxyFactory Spring AOP 中最通用的植入器

Spring AOP Spring IoC 容器 以及 Spring 对其他 JavaEE 服务的集成共同组成了 Spring 框架的 “质量三角”

Spring AOP 的实现机制

Spring AOP 属于第二代AOP,采用动态代理机制和字节码生成技术实现。与最初的 AspectJ 采用编译器将横切逻辑植入目标对象不同,动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象,而将横切逻辑植入到这个代理对象中,系统最终使用的是植入了横切逻辑的代理对象,而不是真正的目标对象。

JDK 1.3 之后引入了一种称之为动态代理(Dynamic Proxy)的机制。使用该机制,我们可以为指定的接口在系统运行期间动态的生成代理对象,从而帮助我们走出最初使用静态代理实现 AOP 的窘境。

动态代理机制的实现主要由一个类和一个接口组成,即java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

InvocationHandler 就是我们实现横切逻辑的地方,它是横切逻辑的载体,作用跟 Advice 是一样的。所以,在使用动态代理机制实现AOP的过程中,我们可以在InvocationHandler的基础上细化程序结构,并根据Advice的类型,分化出对应不同Advice类型的程序结构。

动态代理虽好,但不能满足所有的需求。因为动态代理机制只能对实现了相应Interface的类使用,如果某个类没有实现任何的Interface,就无法使用动态代理机制为其生成相应的动态代理对象。对于没有实现任何Interface的目标对象,Spring AOP 会尝试使用一个 CGLIB 的开源动态字节码生成类库,为目标对象生成动态的代理对象实例。

动态字节码生成

使用动态字节码生成技术扩展对象行为的原理是,我们可以对目标对象进行继承扩展,为其生成相应的子类,而子类可以通过覆盖写来扩展父类的行为,只要将横切逻辑的实现放到子类中,然后让系统使用扩展后的目标对象的子类,就可以达到与代理模式相同的效果了。SubClass instanceof SuperClass == true 不是吗


Proxy Factory

会返回目标对象的代理对象

使用ProxyFactory只需要指定如下两个最基本的东西

1:对其进行织入的目标对象

2:将要应用到目标对象的Aspect,在Spring里面叫做Advisor

pointcut match targetSource method

pointcut 核心就是 match —— 支持逻辑运算 |

advice 横切逻辑的实现,aspect 中的 method

一般的advice根据切入点的执行时机来区分

introduction则不是,introduction 可以为原有的对象添加新的特性或者行为

aspect pointcut advice 的载体,spring 中称之为advisor


spring Pointcut 实现

public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}

public interface MethodMatcher {
boolean matches(Method method, Class targetClass);
boolean isRuntime();
boolean matches(Method method, Class targetClass, Object[] args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}

isRuntime() 来区分该 pointcut 拦截是否需要对方法调用时的参数进行拦截

在对对象具体方法进行拦截的时候,可以忽略每次方法执行的时候调用者传入的参数,也可以每次都检查这些方法调用参数,以强化拦截条件

isRuntime() 返回 false,表示不会考虑具体Joinpoint的方法参数,这种类型的MethodMatcher称之为StaticMethodMatcher,对于同样类型的方法匹配结果,可以在框架内部缓存

pointcut match


advice

advice实现了将被织入到Pointcut规定的Joinpoint处的横切逻辑。在Spring中,Advice按照其自身实例能否在目标对象类的所有实例中共享这一标准,可以划分为两大类,即per-class类型的Adviceper-instance类型的Advice

per-class 类型的Advice,通常只是提供方法拦截的功能

per-instance 类型的Adviceintroduction,可以在不改动目标类定义的情况下,为目标类添加新的属性以及行为

spring 中的 aspect

spring aspect advisoradvisor通常只持有一个Pointcut和一个Advise,而 Aspect定义中可以有多个Pointcut和多个Advice

PointcutAdvisor 是真正的定义一个Pointcut和一个AdviceAdvisor

IoC容器中使用ProxyFactoryBean进行横切逻辑的织入

Spring AOP 给出了 autoproxy 机制,用以帮助解决使用ProxyFactoryBean配置工作量比较大的问题


autoproxy 实现原理

建立在IoC容器的BeanPostProcessor概念之上。通过BeanPostProcessor,我们可以在遍历容器中所有bean的基础上,对遍历到的bean进行一些操作。

要检查当前bean是否符合拦截条件,首先需要知道拦截条件是什么

1:配置

2:注解

都可以提供相应的自动代理实现类来读取这些信息,并为相应的目标对象自动生成代理对象

可用的AutoProxyCreator

Spring AOP 提供了两个常用的,即BeanNameAutoProxyCreatorDefaultAdvisorAutoProxyCreator

BeanNameAutoProxyCreator

通过beanNames,我们可以指定要对容器中的哪些bean自动生成代理对象。通过interceptorNames,我们可以指定将要应用到目标对象的拦截器、Advice或者Advisor等。

DefaultAdvisorAutoProxyCreator

只需要在ApplicationContext的配置文件中注册一下DefaultAdvisorAutoProxyCreatorbean定义就可以了。

DefaultAdvisorAutoProxyCreator注册到容器后,它就会自动搜寻容器内的所有Advisor,然后根据各个Advisor所提供的拦截信息,为符合条件的容器中的目标对象生成相应的代理对象。(只对Advisor有效)

扫描之后,我们从容器中取得的对象实例,就都是代理后已经包含了织入的横切逻辑的代理对象了,除非该对象不符合Pointcut规定的拦截条件

所有的AutoProxyCreator都是InsantiationAwareBeanPostProcessor,这种类型的BeanPostProcessor与普通的BeanPostProcessor有所不同。当Spring IoC容器检测到InstantiationAwareBeanPostProcessor类型的BeanPostProcessor的时候,会直接通过InsantiationAwareBeanPostProcessor的逻辑构造对象实例返回,而不会走正常的对象实例化流程

这样,相应的AutoProxyCreator会直接构造目标对象的代理对象返回,而不是原来的目标对象

TargetSource

目标对象容器,可以控制每次方法调用作用到的具体对象实例:

1:提供一个目标对象池,每次从TargetSource取得的目标对象都从这个目标对象池中取得

2:让一个TargetSource实现类持有多个目标对象实例,然后按照某种规则,在每次方法调用时,返回相应的目标对象实例

spring aspectj 形式的 aop
@Aspect
public class myAspect {
@Pointcut("pointcut expression") // pointcut_expression
public void pointcutMethod(){}; // pointcut_signature
}

@AspectJ 形式的Pointcut声明包含如下两个部分

1Pointcut Expression 载体为@Pointcut,该注解是方法级别的注解

Pointcut表达式有如下两部分组成

1】:Pointcut标志符,标志符表明该Pointcut将以什么样的行为来匹配表达式

2】:表达式匹配模式,具体的匹配模式

2Pointcut Signature

在这里具体化为一个方法定义,它是Pointcut Expression的载体。Pointcut Signature 所在的方法定义,除了返回类型必须是void之外,没有其他限制。方法修饰符所起的作用与java中语义相同,public型的Pointcut Signature可以在其他Aspect定义中引用,private则只能在当前Aspect定义中引用。可以将Pointcut Signature作为相应Pointcut Expression的标志符,在Pointcut Expression的定义中取代重复的Pointcut表达式定义

advice 定义在 method 之上,里面的 value 其实就是 pointcut

@Pointcut 定义的,相当于声明,advice 里面具体使用

原文链接:http://note.youdao.com/share/?id=29a6f6bfbb94a2c1c2ab7425baa93ee5&type=note



网易云产品免费体验馆无套路试用,零成本体验云计算价值。  

本文来自网易实践者社区,经作者倪震洋授权发布