menu

AOP Notes

Posted on 10/01/2019

AOP

一般概念

名词解释

  • 增强(Advice):包含横切逻辑和方位信息。

  • 切点(PointCut):程序运行的方法体,通过类和方法定位。

  • 切面(Aspect):切点和增强构成一个切面。

  • 目标对象(Target object):需要创建代理的对象。

  • 织入(Weaving):将切面注入目标对象创建代理对象的过程。织入技术如下:

    • 编译期织入,需要使用特殊的编译器
    • 类加载期织入,需要使用特殊的类加载器
    • 动态代理织入,运行期为目标类添加增强生成子类

​ Spring采用动态代理织入,AspectJ采用编译期织入和类加载期织入。

引介(Introduction):是一类特殊的增强,为类添加一些属性和方法。这样可以动态地为没有实现某个接口的类添加该接口实现逻辑,使该类成为某个接口的实现类。

连接点(join point):方法体之间的位置。

Introduction

面向切面编程(Aspect Oriented Programming):

增强包含了横切代码逻辑和方位信息(方法前、后等),但不指定类和方法,所以可以织入到任意类或方法中。如果需要指定特定的类或方法织入增强,则需通过切点对类和方法的定位。结合增强和切点的信息就得到切面,切面则能织入特定的类或方法中。

Spring是通过ProxyFactory或ProxyFactoryBean将增强或切面织入到目标对象中,从而得到代理对象。ProxyFactory、ProxyFactoryBean内部使用JDK动态代理或CGLib动态代理技术实现织入功能。

所以利用Spring到AOP编程的一般思路如下:

  1. 确定目标对象

  2. 根据需要定义增强(重点)

  3. 如何通过切点定位到特定的类和方法中(重点)

  4. 将增强和切点结合,得到切面

  5. 通过ProxyFactory或ProxyFactoryBean将切面织入到目标对象中。

    ProxyFactoryBean创建织入切面的代理,配置麻烦,Spring通过BeanPostProcessor接口提供了自动代理创建功能。目前有3个实现类:

    1. BeanNameAutoProxyCreator:通过指定目标对象的名称创建代理对象。
    2. DefaultAdvisorAutoProxyCreator:根据目标对象和切面信息自动创建代理对象。
    3. AnnotationAwareAspectJAutoProxyCreator:为包含AspectJ注解的Bean自动创建代理对象。

增强

前置增强

实现以下接口即可创建前置增强:

public interface MethodBeforeAdvice{
    public void before(Method method, Object[] args, Object obj) throws Throwable;
}

后置增强

public interface AfterReturningAdvice{
    public void afterReturning(Object returnObj, Method method, Object[] args,
                              Object obj) throws Throwable;
}

环绕增强

public interface MethodInterceptor{
    public Object invoke(MethodInvocation invocation) throws Throwable;
}

异常增强

public interface ThrowAdvice{
    public void afterThrowing(Method method, Object[] args, Object target,
                             Exception ex) throws Throwable;
}

引介增强

引介增强不是在目标类方法周围织入增强,而是为目标类引入新的属性和方法,所以其连接点是类级别的,而不是方法级别的。

Spring定义了一个没有方法的IntroductionInterceptor接口,但为该接口提供了DelegatingIntroductionInterceptor实现类。一般情况下通过扩展该实现类,实现需要织入的接口,就可以定义所需要的引介增强。

public class 

切点

切点用于定位目标类和方法。Spring支持通过Pointcut接口、注解以及字符串表达式3种方式定义切点。以下为Pointcut接口的关系图:

pointcuts

org.springframework.aop.Pointcut接口定义如下:

public interface Pointcut{
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}

ClassFilter接口定义如下:

public interface ClassFilter{
    boolean matches(Class clazz);	//返回值为true则找到匹配的类
}

MethodMatcher接口定义如下:

public interface MethodMatcher{
    boolean matches(Method m, Class targetClass);	//静态方法匹配器
    boolean isRunning();
    boolean matches(Method m, Class targetClass, Object[] args);	//动态方法匹配器
}

一个Pointcut由ClassFilter和MethodMatcher构成,通过ClassFilter定位到目标类,再通过MethodMatcher定位到特定的方法上,这样,Pointcut就有了定位目标类和方法的能力。

MethodMatcher的静态方法匹配器在创建AOP代理对象时运行。动态方法匹配器会在运行期间每次方法的调用进行匹配。只要创建代理对象均会运行静态匹配器,只有isRunning返回值为true时动态匹配器才会用到。

Spring提供了6种类型的Pointcut切点。

  • StaticMethodMatcherPointcut 是静态方法切点的抽象基类,默认匹配所有的类。包含两个子类,分别是:
    • NameMatchMethodPointcut 提供简单的字符串匹配方法签名。
    • AbstractRegexpMatchMethodPointcut 使用正则表达式匹配方法签名。
  • DymicMethodMacherPointcut 是动态方法切点的抽象基类,默认情况下匹配所有的类。

  • AnnotationMatchingPointcut 使用AnnotationMatchingPointcut支持在Bean中直接通过Java 5.0注解定义切点。

  • ExpressionPointcut

为支持AspectJ切点表达式语法而定义的接口,是一种更简单高效的方式。

AspectJ 5.0切点表达式由关键字和操作参数组成,如切点表达式execution(* greetTo(..))中,execution为关键字(表达式函数),* greetTo(..)为操作参数(参数)。

Spring支持9个@Aspect切点表达式函数,用不同方式描述目标类的连接点,根据描述对象的不同,可以大致分为4种类型:

  1. 方法切点函数
    1. execution()
    2. @annotation()
  2. 方法参数切点函数
    1. args():目标方法的参数是指定的类及其子类。
    2. @agrs()
  3. 目标类切点函数
    1. within()
    2. target()
    3. @within()
    4. @target()
  4. 代理类切点函数
    1. this():一般情况下与target()用法和意义相同。在匹配目标对象织入引介切面所生成的代理对象时,this()的参数为引介切面所引入的接口,则匹配生成的代理对象所有方法。

AspectJ支持的通配符及其含义:

  1. *:匹配任意字符,但只能表达上下文中的一个元素;
  2. ..:匹配任意字符,可以表达上下文中的多个元素,在表达类时,必须和*联合使用,而在表示方法参数时,则单独使用;
  3. +:按类型匹配指定类型的所有类,必须跟在类名后面,如com.smart.Car+表示继承或扩展指定类的所有类,同时还包括指定类本身。
  • ControlFlowPointcut

特殊切点,根据程序执行堆栈信息查看目标方法是否由某一个方法直接或间接发起调用,以此判断是否为匹配的连接点。

  • ComposablePointcut

为创建多个切点而提供点方便操作类。所有方法都返回ComposablePointcut类,这样就可以使用链接表达式对切点进行操作。

切面

切面包含增强、方位以及切点3类信息。可分为3类:一般切面(Advisor),切点切面(PointcutAdvisor)和引介切面(IntroductionAdvisor),以下为Spring中切面的继承关系图:

从中可知,一般切面不包含切点信息,PointcutAdvisor与IntroductionAdvisor的不同之处在于IntroductionAdvisor只定位到类,不能定位到方法。

advisors

PointcutAdvisor

Advisor是通过扩展对应的Pointcut实现类并实现PointcutAdvisor接口进行定义的。比如:StaticMethodMatcherPointcutAdvisor切面是扩展StaticMethodMatcherPointcut类并实现PointcutAdvisor接口。

此外,Advisor都实现类org.springframework.core.Ordered接口,Spring将根据Advisor定义的顺序决定织入切面的顺序。

DefaultPointcutAdvisor

NameMatchMethodPointcutAdvisor

RegexpMethodPointcutAdvisor

DynamicMethodMatcherPointcutAdvisor

AspectJExpressionPointcutAdvisor

AspectJPointcutAdvisor

IntroductionAdvisor

引介切面是引介增强的封装器,可以更容易地为现有对象添加任何接口的实现,下图是引介切面的继承关系图:

introduction_advisor

从上图可知,IntroductionAdvisor有两个实现类,DefaultIntroductionAdvisor和DeclareParentAdvisor,前者为引介切面最常用的实现类,后者用于实现AspectJ语言的DeclareParent注解表示的引介切面。

ProxyFactory

ProxyFactory 代理工厂是 Spring 中用于生成动态代理的工厂类,底层运用 JDK 动态代理或 CGlib 为目标类生成代理。

Spring 中定义的 AopProxy 接口有两个 final 的实现类,分别为:

  • Cglib2AopProxy : 使用cglib 类库生成代理,创建代理的速度慢,但生代出来的代理经过一些优化,运行速度快,适用于 singleton 的类。如果 ProxyFactory.setOptimize(true) 时,则使用该方法创建动态代理。
  • JdkDynamicAopProxy :使用 JDK 生成动态代理,创建代理的速度要比 cglib 快,但运行速度慢,适用于 prototype 的动态代理。如果调用 ProxyFactory.setInterfaces(Class[] interfaces) 时,则使用该方法创建动态代理。

ProxyFactoryBeanFactoryBean 接口的实现类,用于动态地创建代理类,内部使用 ProxyFactory 完成动态代理的完成这项工作。以下为 ProxyFactoryBean 的几个常用的可配置属性:

  • target: 代理的目标对象
  • proxyInterfaces: 代理所要实现的接口,可以是多个接口。该属性还有一个别名属性 interfaces
  • interceptorNames: 需要织入目标对象的bean列表,采用bean的名称指定。这些bean必须是实现了 org.aopalliance.intercept.MethodInterceptororg.springframework.aop.Advisor 接口的bean,配置的顺序对应调用的顺序。
  • singleton:返回的代理是否为单实例,默认为单实例。
  • optimize: 当设置为 true 时,强制使用 CGLib 动态代理,对于 singleton 的代理,推荐使用 CGLib;对于其他作用域类型的代理,最好使用JDK代理。原因是虽然CGLib创建代理时速度慢,但其创建出的代理对象运行效率高,而JDK创建代理的表现正好相反。
  • proxyTargetClass:是否对类进行代理(而不是对接口进行代理)。当设置为 true 时,使用CGLib动态代理。
Top