Spring AOP的实现(1)---Spring AOP概述

1. Spring AOP概述

  AOP是Aspect-Oriented Programming (面向方面编程或面向切面)的简称。
  Aspect是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点。从关注点中分离出横切关注点是面向切面的程序设计的核心概念。分离关注点使解决特定领或问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过切面来封装、维护,这样原本分散在整个应用程序中的变动就可以很好地管理起来。这里提到的概念是从模块化出发的,开发者一定不会对模块化这个概念感到陌生。
  面向对象设计其实也是一种模块化的方法,它把相关的数据及其处理方法放在了一起。与单纯使用子函数进行封装相比,面向对象的模块化特性更完备,它体现了计算的一个基本原则一一让计算尽可能靠近数据。这样一来,代码组织起来就更加整齐和清晰, 一个类就是一个基本的模块。很多程序的功能还可以通过设计类的继承关系而得到重用,进一步提高了开发效率.再后来,又出现了各种各样的设计模式,使设计程序功能变得更加得心应手。
  虽然利用面向对象的方挫可以很好地组织代码,也可
以通过继承关系实现代码重用,但是程序中总是会出现一些重复的代码,而且不太方便使用继承的方撞把它们重用和管理起来。它们功能重复并且需要用在不同的地方,虽然可以对这些代码做一些简单的封装,使之成为公共函数,但是在这种显式的调用中,使用它们并不是很方便。例如,这个公共函数在什么情况下可以使用,能不能更灵活地使用等。
  另外,在使用这些公共函数的时候,往往也需要进行一些逻辑设计,也就是需要代码实现来支持,而这些逻辑代码也是需要维护的。这时就是AOP大显身手的时候,使用AOP后,不仅可以将这些重复的代码抽取出来单独维护,在需要使用时统一调用,还可以为如何使用这些公共代码提供丰富灵活的手段。这虽然与设计公共子模块有几分相似,但在传统的公共子模块调用中,除了直接硬调用之外并没有其他的手段,而AOP为处理这一类问题提供了一套完整的理论和灵活多样的实现方法。也就是说,通过AOP提出横切的概念以后,在把模块功能正交化的同时,也在此基础上提供了一系列横切的灵活实现。比如通过使用Proxy代理对象、拦截器字节码翻译技术等一系列已有的AOP或者AOP实现技术,来实现切面应用的各
种编织实现和环绕增强。
  在Spring AOP实现中,使用Java语言来实现增强对象与切面增强应用,并为这两者的结合提供了配置环境。对于编织配置,毫无疑问,可以使用IoC容器来完成, 对于POJO对象的配置,本来就是Spring的核心IoC容器的强项。因此,对于使用Spring的AOP开发而言,使用POJO就能完成AOP任务。但是,对于其他的AOP实现方案,可能需要使用特定的实现语言、配置环境甚至是特定的编译环境。例如在AspectJ中,尽管切面增强的对象是Java对象,但却需要使用特定的Aspect语言和AspectJ编译器。AOP体系结构的第二个层次是为语言和开发环境提供支持的,在这个层次中可以看到AOP框架的高层实现, 主要包括配置和编织实现两部分内容。例如配置逻辑和编织逻辑实现本身,以及对这些实现进行抽象的一些高层API封装。这些实现和API封装,为前面提到的语言和开发环境的实现提供了有力的支持。
  对Spring平台或者说生态系统来说, AOP是Spring框架的核心功能模块之一。AOP与IoC容器的结合使用,为应用开发或Spring 自身功能的扩展都提供了许多便利。Spring AOP的实现和其他特性的实现一样,除了可以使用Spring本身提供的AOP实现之外,还封装了业界优秀的AOP解决方案AspectJ来供应用使用。在这个AOP实现中, Spring充分利用了IoC容器Proxy代理对象以及AOP拦截器的功能特性,通过这些对AOP基本功能的封装机制。为用户提供了AOP的实现框架。

1.1 Advice通知

  Advice (通知)定义在连接点做什么,为切面增强提供织入接口。在Spring AOP中,它主要描述Spring AOP围绕方法调用而注入的切面行为。Advice是一个接口,具体的接口定义在org.aopalliance.aop.Advice中。在Spring AOP的实现中,使用了这个统一接口,并通过这个接口,为AOP切面增强的织入功能做了更多的细化和扩展,比如提供了更具体的通知类型,如BeforeAdvice 、AfterAdvice 、ThrowsAdvice等。作为Spring AOP定义的接口类,具体的切面增强可以通过这些接口集成到AOP框架中去发挥作用。从接口BeforeAdvice开始,首先了解它的类层次关系,如图所示。
这里写图片描述
  在BeforeAdvice 的继承关系中,定义了为待增强的目标方法设置的前置增强接口MethodBeforeAdvice ,使用这个前置接口需要实现一个回调函数:

void before(Method method, Object[] args, Object target) throws Throwable;

  作为回调函数, before方法的实现在Advice中被配置到目标方法后,会在调用目标方法时被回调。具体的调用参数有: Method对象,这个参数是目标、方法的反射对象, Object[]对象数组,这个对象数组中包含目标方法的输入参数。以CountingBeforeAdvice为例来说明BeforeAdvice的具体使用,CountingBeforeAdvice是接口MethodBeforeAdvice的具体实现,如代码所示。

public class CountingBeforeAdvice extends MethodCounter implements MethodBeforeAdvice {

    @Override
    public void before(Method m, Object[] args, Object target) throws Throwable {
        count(m);
    }
}

可以看到,它的实现比较简单, 完成的工作是统计被调用的方法次数。作为切面增强实现,它会根据调用方法的方法名进行统计,把统计结果根据方法名和调用次数作为键值对放入一个map 中。
  这里调用了count方法,使用了目标方法的反射对象作为参数,完成对调用方法名的统计工作. count方法在CountingBeforeAdvice的基类MethodCounter中实现,如代码清所示。

@SuppressWarnings("serial")
public class MethodCounter implements Serializable {

    /** Method name --> count, does not understand overloading */
    private HashMap<String, Integer> map = new HashMap<String, Integer>();

    private int allCount;

    protected void count(Method m) {
        count(m.getName());
    }

    protected void count(String methodName) {
        Integer i = map.get(methodName);
        i = (i != null) ? new Integer(i.intValue() + 1) : new Integer(1);
        map.put(methodName, i);
        ++allCount;
    }

    public int getCalls(String methodName) {
        Integer i = map.get(methodName);
        return (i != null ? i.intValue() : 0);
    }

    public int getCalls() {
        return allCount;
    }

    /**
     * A bit simplistic: just wants the same class.
     * Doesn't worry about counts.
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object other) {
        return (other != null && other.getClass() == this.getClass());
    }

    public int hashCode() {
        return getClass().hashCode();
    }

}

  这个切面增强完成的统计实现并不复杂,它在对象中维护一个哈希表,用来存储统计数据。在统计过程中,首先通过目标方法的反射对象得到方法名,然后进行累加,把统计结果放到维护的哈希表中。如果需要统计数据,就到这个哈希表中根据key来在取。
  在Advice的实现体系中, Spring还提供了AfterAdvice这种通知类型,它的类接口关系如图所示。
这里写图片描述
  在的AfterAdvice类接口关系中,可以看到一系列对AfterAdvice的实现和接口扩展,比如AfterReturningAdvice就是其中比较常用的一个。在这里,以AfterReturningAdvice通知的实现为例,分析一下After Advice通知类型的实现原理。在AfterReturningAdvice接口中定义了接口方怯,如下所示:

void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;

  afterReturning方法也是一个回调函数, AOP应用需要在这个接口实现中提供切面增强的具体设计,在这个Advice通知被正确配置以后,在目标方法调用结束并成功返回的时候,接口会被Spring AOP 回调。对于回调参数,有目标方法的返回结果、反射对象以及调用参数(AOP把这些参数都封装在一个对象数组中传递进来)等。与前面分析BeforeAdvice一样,在Spring AOP 的包中,同样可以看到一个CountingAfterReturningAdvice ,作为熟悉AfterReturningAdvice使用的例子,它的实现基本上与CountingBeforeAdvice是一样的,如代码所示。

public class CountingAfterReturningAdvice extends MethodCounter implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object o, Method m, Object[] args, Object target) throws Throwable {
        count(m);
    }

}

  在实现AfterReturningAdvice的接口方桂afterReturning 中,可以调用MethodCounter的count方法,从而完成根据方法名来对目标方法调用的次数进行统计。count方提调用的实现与前面看到的CountingBeforeAdvice基本一样,所不同的是调用发生的时间.尽管增强逻辑相同,但是,如果它实现不同的AOP通知接口,就会被AOP编织到不同的调用场合中。尽管它们完成的增强行为是一样的,都是根据目标方法名对调用次数进行统计,但是它们的最终实现却有很大的不同, 一个是在目标方法调用前实现切面增强, 一个是在目标方法成功调用返回结果后实现切面增强。由此可见, AOP技术给应用带来的灵活性,使得相同的代码完全可以根据应用的需要灵活地出现在不同的应用场合。
  在Spring AOP中,还可以看到另外一种Advice通知类型,那就是ThrowsAdvice ,它的类层次关系如图所示。
这里写图片描述
&esmp;&esmp;对于ThrowsAdvice ,并没有指定需要实现的接口方法,它在抛出异常时被回调,这个回调是AOP使用反射机制来完成的。可以通过MyThrowsHandler 来了解ThrowsAdvice的使用方法,如代码所示。

public class MyThrowsHandler extends MethodCounter implements ThrowsAdvice {
    // Full method signature
    public void afterThrowing(Method m, Object[] args, Object target, IOException ex) {
        count("ioException");
    }
    public void afterThrowing(RemoteException ex) throws Throwable {
        count("remoteException");
    }

    /** Not valid, wrong number of arguments */
    public void afterThrowing(Method m, Exception ex) throws Throwable {
        throw new UnsupportedOperationException("Shouldn't be called");
    }
}

  在afterThrowiog方法中,从输入的异常对象中得到异常的名字并进行统计。这个count方法同样是在MethodCounter中实现的,与前面看到的两个Advice的实现相同。在这里, count方法完成的是根据异常名称统计抛出异常的次数。

1.2 Pointcut切点

  Pointcut (切点)决定Advice通知应该作用于哪个连接点,也就是说通过Pointcut来定义需要增强的方法的集合,这些集合的选取可以按照一定的规则来完成。在这种情况下,Pointcut通常意味着标识方法,例如,这些需要增强的地方可以由某个正则表达式进行标识,或根据某个方法名进行匹配等。为了方便用户使用, Spring AOP提供了具体的切点供用户使用,切点在Spring AOP中的类继承体系如图所示。
这里写图片描述
从源代码实现上同样可以得到l相应的Spring AOP的Pointcut设计,如图所示。
这里写图片描述
  在Pointcut的基本接口定义中可以看到,需要返回一个MethodMatcher。对于Point的匹配判断功能,具体是由这个返回的MethodMatcher来完成的,也就是说,由这个MethodMatcher来判断是否需要对当前方法调用进行增强,或者是否需要对当前调用方法应用配置好的Advice 通知。在Pointcut的类继承关系中,以正则表达式切点Jdk.RegexpMetbodPointcut的实现原理为例,来具体了解切点Pointcut的工作原理。Jdk.RegexpMethodPointcut类完成通过正则表达式对方法名进行匹配的功能。在JdkRegexpMethodPointcut的基类StaticMethodMatcherPointcut的实现中可以看到,设置MethodMatcher为StaticMethodMatcher,同时JdkRegexpMethodPointcut也是这个MethodMatcher的子类,它的类层次关系如图所示。
这里写图片描述
  可以看到,在Pointcut中,通过这样的类继承关系, MethodMatcher对象实际上是可以被配置成JdkRegexpMethodPointcut来完成方法的匹配判断的。在JdkRegexpMethodPointcut中,可以看到一个matches方法,这个matches方法是MethodMatcher定义的接口方法。JdkRegexpMethodPointcut的实现中,这个matches方法就是使用正则表达式来对方法名进行匹配的地方。
  matches方法的调用是在JdkDynamicAopProxy的invoke方法中触发了对matches方法的调用。很明显,熟悉Proxy使用的一定会想到,这个invoke方法应该就是Proxy对象进行代理回调的入口方法,这个invoke回调的实现是使用JDK动态代理完成AOP功能的一部分,JdkRegexpMethodPointcut的matches方法的实现如代码所示。

/**
     * Returns {@code true} if the {@link Pattern} at index {@code patternIndex}
     * matches the supplied candidate {@code String}.
     */
    @Override
    protected boolean matches(String pattern, int patternIndex) {
        Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);
        return matcher.matches();
    }

在JdkRegexpMethodPointcut中,通过JDK来实现正则表达式的匹配,在Spring AOP中,还提供了其他的MethodPointcut ,比如通过方法名匹配进行Advice 匹
配的NameMatchMethodPointcut. 它的matches方法实现很简单,匹配的条件是方法名相同或者方法名相匹配,如代码所示。

@Override
    public boolean matches(Method method, Class<?> targetClass) {
        for (String mappedName : this.mappedNames) {
            if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Return if the given method name matches the mapped name.
     * <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches,
     * as well as direct equality. Can be overridden in subclasses.
     * @param methodName the method name of the class
     * @param mappedName the name in the descriptor
     * @return if the names match
     * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
     */
    protected boolean isMatch(String methodName, String mappedName) {
        return PatternMatchUtils.simpleMatch(mappedName, methodName);
    }

1.3 Advisor通知器

  完成对目标方法的切面增强设计( Advice )和关注点的设计(Pointcut )以后,需要一个对象把它们结合起来,完成这个作用的就是Advisor (通知器)。通过Advisor ,可以定义应该使用哪个通知并在哪个关注点使用它,也就是说通过Advisor,把Advice和Pointcut结合起来,这个结合为使用IoC容器配置AOP应用,或者说即开即用地使用AOP基础设施,提供了便利。在Spring AOP 中,我们以一个Advisor的实现( DefaultPointcutAdvisor)为例,来了解Advisor的工作原理。在DefaultPointcutAdvisor中,有两个属性,分别是advice和pointcut.通过这两个属性,可以分别配置Advice和Pointcut, DefaultPointcutAdvisor的实现如代码所示。

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {

    private Pointcut pointcut = Pointcut.TRUE;


    /**
     * Create an empty DefaultPointcutAdvisor.
     * <p>Advice must be set before use using setter methods.
     * Pointcut will normally be set also, but defaults to {@code Pointcut.TRUE}.
     */
    public DefaultPointcutAdvisor() {
    }

    /**
     * Create a DefaultPointcutAdvisor that matches all methods.
     * <p>{@code Pointcut.TRUE} will be used as Pointcut.
     * @param advice the Advice to use
     */
    public DefaultPointcutAdvisor(Advice advice) {
        this(Pointcut.TRUE, advice);
    }

    /**
     * Create a DefaultPointcutAdvisor, specifying Pointcut and Advice.
     * @param pointcut the Pointcut targeting the Advice
     * @param advice the Advice to run when Pointcut matches
     */
    public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
        this.pointcut = pointcut;
        setAdvice(advice);
    }


    /**
     * Specify the pointcut targeting the advice.
     * <p>Default is {@code Pointcut.TRUE}.
     * @see #setAdvice
     */
    public void setPointcut(Pointcut pointcut) {
        this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
    }

    public Pointcut getPointcut() {
        return this.pointcut;
    }


    @Override
    public String toString() {
        return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
    }

}

  在DefaultPointcutAdvisor中, pointcut默认被设置为Pointcut.True ,这个Pointcut.True在Pointcut接口中被定义为:

Pointcut TRUE = TruePointcut.INSTANCE;

  TruePointcut的INSTANCE是一个单件。在它的实现中,可以看到单件模式的具体应用和典型使用方法,比如使用static类变量来持有单件实例,使用private私有构造函数来确保除了在当前单件实现中,单件不会被再次创建和实例化,从而保证它的“单件”特性。在TruePointcut的method.Matcher实现中,使用TrueMethodMatcher作为方法匹配器。这个方法匹配器对任何的方法匹配都要求返回true的结果,也就是说对任何方法名的匹配要求,它都会返回匹配成功的结果。和truePointcut一样, TrueMethodMatcher也是一个单件实现。
TruePointcut和TrueMethodMatcher的实现如代码所示。

/**
 * Canonical Pointcut instance that always matches.
 *
 * @author Rod Johnson
 */
@SuppressWarnings("serial")
class TruePointcut implements Pointcut, Serializable {

    public static final TruePointcut INSTANCE = new TruePointcut();

    /**
     * Enforce Singleton pattern.
     */
    private TruePointcut() {
    }

    public ClassFilter getClassFilter() {
        return ClassFilter.TRUE;
    }

    public MethodMatcher getMethodMatcher() {
        return MethodMatcher.TRUE;
    }

    /**
     * Required to support serialization. Replaces with canonical
     * instance on deserialization, protecting Singleton pattern.
     * Alternative to overriding {@code equals()}.
     */
    private Object readResolve() {
        return INSTANCE;
    }

    @Override
    public String toString() {
        return "Pointcut.TRUE";
    }

}
/**
 * Canonical MethodMatcher instance that matches all methods.
 *
 * @author Rod Johnson
 */
@SuppressWarnings("serial")
class TrueMethodMatcher implements MethodMatcher, Serializable {

    public static final TrueMethodMatcher INSTANCE = new TrueMethodMatcher();

    /**
     * Enforce Singleton pattern.
     */
    private TrueMethodMatcher() {
    }

    public boolean isRuntime() {
        return false;
    }

    public boolean matches(Method method, Class targetClass) {
        return true;
    }

    public boolean matches(Method method, Class targetClass, Object[] args) {
        // Should never be invoked as isRuntime returns false.
        throw new UnsupportedOperationException();
    }

    /**
     * Required to support serialization. Replaces with canonical
     * instance on deserialization, protecting Singleton pattern.
     * Alternative to overriding {@code equals()}.
     */
    private Object readResolve() {
        return INSTANCE;
    }

    @Override
    public String toString() {
        return "MethodMatcher.TRUE";
    }

}

猜你喜欢

转载自blog.csdn.net/gchd19921992/article/details/79213396