SpringAOP概述(六)

OOP 面向对象编程,AOP(Aspect-Oriented Programming) 面向切面编程。

官方文档: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop

1. AOP简介

主要有以下AOP技术:

1》AspectJ: 源代码和字节码级别的编织器,用户需要使用不同于Java的新语言

2》AspectWerkz:AOP框架,使用字节码动态编织器和XML配置

3》JBoss-AOP:基于拦截器和元数据的AOP框架,运行在JBoss应用服务器上,以及在AOP中用到的一些相关的技术实现

4》BCEL(Byte-Code Engineering Library): Java字节码操作类 库

5》Javassist:Java字节码操作类型,JBOSS的一个子项目

对应于现有的AOP实现方法,AOP联盟对它们进行了一定程度的抽象,从而定义出AOP体系结构。AOP联盟定义的AOP体系结构如图:

 AOP联盟定义的体系结构把与AOP相关的概念大致分为由高到低、从使用到实现的三个层次。从上往下,最高层是语言和开发环境,在这个环境中可以看到几个重要的概念:

(1) 基础(base) 可以视为待增强对象或者说目标对象

(2) 切面(aspect) 通常包含对于基础的增强应用

(3) 配置(configuration)可以看成是一种编织,通过在AOP体系中提供这个配置环境,可以把基础和切面结合起来,从而完成切面对目标对象的编织实现

  在SpringAOP的实现中,使用Java语言来实现增强对象与切面增强应用,并为这两者的结合提供了配置环境。对于编织配置,毫无疑问,可以使用IoC容器了完成;对于POJO对象的配置,本身就是Spring核心IoC容器的强项。因此,对于使用SpringAOP开发而言,使用POJO就能完成AOP任务。但是,对于其他AOP实现方案,可能需要使用特定的实现语言、配置环境甚至是特定的编译环境。例如在AspectJ中,尽管切面增强的是Java对象,但却需要使用特定的Aspect语言和Aspect编译器。

  AOP体系的第二个层次是为语言和开发环境提供支持的,在这个层次中可以看到AOP框架的高层实现,主要包括配置和编织实现两部分内容。

  最底层是编织的具体实现模块,比如反射、程序预处理、拦截器框架、类装载器框架、元数据处理等。

2. Spring AOP简介

   AOP与IoC容器的结合使用,为应用开发或Spring自身功能的扩展都提供了许多便利。Spring AOP的实现和其他特性的实现一样,除了可以Spring本身提供的AOP实现之外,还封装了业界优秀的AOP解决方法AspectJ来供应用使用。这里主要眼界Spring自身的AOP实现原理。Spring充分利用了IoC容器Proxy代理对象以及AOP拦截器的功能特性,通过这些对AOP基本功能的封装机制,为用户提供了AOP的实现框架。所以Java的Proxy机制是Spring AOP的重点。

1. Advice通知

Advice(通知) 定义在连接点做什么,为切面增强提供织入接口。在SpringAOP中,它主要描述SpringAOP围绕方法调用而注入的切面行为。Advice是AOP联盟定义的一个接口,具体的接口在 org.aopalliance.aop 包下。在SpringAOP实现中,使用了这个统一接口,并通过这个接口,为AOP切面增强的植入功能做了更多的细化和扩展,衍生出很多接口,如下:

 以 BeforeAdvice 接口为例进行分析。Advice和BeforeAdvice 都是一个空接口。BeforeAdvice继承关系如下:

 MethodBeforeAdvice 接口定义了为待增强的目标方法设置的前置增强接口,接口包含一个方法如下:

package org.springframework.aop;

import java.lang.reflect.Method;

import org.springframework.lang.Nullable;

public interface MethodBeforeAdvice extends BeforeAdvice {

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

}

   作为回调函数,before方法的实现在Advice中被配置到目标方法后,会在调用目标方法前被回调。具体的调用参数有:Method对象,这个参数是目标方法的反射对象;Object[] 对象数组,这个对象数组包含目标方法的输入参数。以CountingBeforeAdvice为例子说明其用法。CountingBeforeAdvice  完成的工作是统计被调用的方法次数。作为切面增强实效,它会根据调用方法的方法名进行统计,把统计结果根据方法名和调用次数作为键值对存入一个map中。源码如下:

package org.springframework.tests.aop.advice;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

@SuppressWarnings("serial")
public class CountingBeforeAdvice extends MethodCounter implements MethodBeforeAdvice {

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

}

 MethodCounter源码如下:

package org.springframework.tests.aop.advice;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * Abstract superclass for counting advices etc.
 *
 * @author Rod Johnson
 * @author Chris Beams
 */
@SuppressWarnings("serial")
public class MethodCounter implements Serializable {

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

    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)
     */
    @Override
    public boolean equals(Object other) {
        return (other != null && other.getClass() == this.getClass());
    }

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

}

Spring提供了与BeforeAdvice 对等的AfterAdvice,类图如下:

 

 以AfterReturningAdvice 为例进行分析:

package org.springframework.aop;

import java.lang.reflect.Method;

import org.springframework.lang.Nullable;

public interface AfterReturningAdvice extends AfterAdvice {
    void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;

}

 afterReturning 方法在目标方法调用结束并成功返回时调用。对于回调参数有 目标方法的返回结果、反射对象、以及调用参数等。有一个实现类CountingAfterReturningAdvice,它的实现与上面CountingBeforeAdvice  基本一样:

package org.springframework.tests.aop.advice;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

@SuppressWarnings("serial")
public class CountingAfterReturningAdvice extends MethodCounter implements AfterReturningAdvice {

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

}

AfterAdvice还有一个子接口ThrowsAdvice,它在抛出异常时回调,这个回调是AOP使用反射机制完成的。可以通过查看CountingThrowsAdvice,如下:

    public static class CountingThrowsAdvice extends MethodCounter implements ThrowsAdvice {

        public void afterThrowing(IOException ex) throws Throwable {
            count(IOException.class.getName());
        }

        public void afterThrowing(UncheckedException ex) throws Throwable {
            count(UncheckedException.class.getName());
        }

    }

  在afterThrowing 方法中,从输入的异常对象中得到异常的名字并进行统计。这个count方法同样是继承MethodCounter 的方法。只是前面两个Advice统计的是方法调用次数,这里count方法统计的是抛出异常的次数。

补充:Spring AbstractAspectJAdvice类下面的五个接口代表了常见的五种通知

 对应五种通知类型:

@Before 前置通知

@AfterReturning 后置通知

@Around 环绕通知

@After 最终通知

@AfterThrowing 异常抛出通知  

 2. Pointcut 切点

  Pointcut切点决定Advice通知应该作用于哪个连接点,也就是说通过Pointcut来定义需要增强的方法的集合,这些集合的选取可以按照一定的规则来完成。在这种情况下,Pointcut通常意味着标识方法,例如:这些需要增强的地方可以由某个正则表达式进行标识,或者根据某个方法名称进行匹配。

  为了方便用户使用,Spring AOP提供了具体的切点供用户使用,切点在Spring AOP的类继承体系如下:

 Pointcut接口如下:

package org.springframework.aop;

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

    Pointcut TRUE = TruePointcut.INSTANCE;

}

  可以看到需要返回一个Methodmatcher。对于Point的匹配判断功能,具体是由这个MethodMatcher 来完成的,也就是说这个MethodMatcher需要判断是否需要对当前方法调用进行增强,或者是否需要对当前调用方法应用配置好的Advice通知。

  以JdkRegexpMethodPointcut 为例说明Pointcut的原理。其源码如下:

public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut {

    /**
     * Compiled form of the patterns.
     */
    private Pattern[] compiledPatterns = new Pattern[0];

    /**
     * Compiled form of the exclusion patterns.
     */
    private Pattern[] compiledExclusionPatterns = new Pattern[0];


    /**
     * Initialize {@link Pattern Patterns} from the supplied {@code String[]}.
     */
    @Override
    protected void initPatternRepresentation(String[] patterns) throws PatternSyntaxException {
        this.compiledPatterns = compilePatterns(patterns);
    }

    /**
     * Initialize exclusion {@link Pattern Patterns} from the supplied {@code String[]}.
     */
    @Override
    protected void initExcludedPatternRepresentation(String[] excludedPatterns) throws PatternSyntaxException {
        this.compiledExclusionPatterns = compilePatterns(excludedPatterns);
    }

    /**
     * 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();
    }

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


    /**
     * Compiles the supplied {@code String[]} into an array of
     * {@link Pattern} objects and returns that array.
     */
    private Pattern[] compilePatterns(String[] source) throws PatternSyntaxException {
        Pattern[] destination = new Pattern[source.length];
        for (int i = 0; i < source.length; i++) {
            destination[i] = Pattern.compile(source[i]);
        }
        return destination;
    }

}

  matches 方法使用正则表达式来对方法名进行匹配。关于AOP框架中对matches 方法的调用会在下面介绍。

  在SpringAOP中,还提供了其他的MethodPointcut,比如通过方法名称进行Advice匹配的NameMatchMethodPointcut, 它的匹配是方法名相同或者相匹配。如下:

    @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;
    }

    protected boolean isMatch(String methodName, String mappedName) {
        return PatternMatchUtils.simpleMatch(mappedName, methodName);
    }

3. Advisor 通知器

   完成对目标方法的切面增强设计(Advice)和关注点(Pointcut)以后,需要一个对象把他们结合起来,完成这个作用的就是Advisor(通知器)。通过Advisor,把Advice和Pointcut结合起来,这个结合为使用IoC容器配置AOP应用,或者说即开即用地使用AOP基础设施,提供了便利。在SpringAOP中,以一个Advisor的实现DefaultPointcutAdvisor 为例,了解Advisor的工作原理,其源码如下:

package org.springframework.aop.support;

import java.io.Serializable;

import org.aopalliance.aop.Advice;

import org.springframework.aop.Pointcut;
import org.springframework.lang.Nullable;

@SuppressWarnings("serial")
public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {

    private Pointcut pointcut = Pointcut.TRUE;

    public DefaultPointcutAdvisor() {
    }

    public DefaultPointcutAdvisor(Advice advice) {
        this(Pointcut.TRUE, advice);
    }

    public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
        this.pointcut = pointcut;
        setAdvice(advice);
    }

    public void setPointcut(@Nullable Pointcut pointcut) {
        this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
    }

    @Override
    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 是一个单例模式的对象,看名字就知道这个匹配器匹配任何方法。在TruePointcut 的MethodMatcher实现中,用TrueMethodMatcher 作为匹配器,同样是一个单例对象。

final class TruePointcut implements Pointcut, Serializable {

    public static final TruePointcut INSTANCE = new TruePointcut();

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

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

    @Override
    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";
    }

}

org.springframework.aop.MethodMatcher#TRUE如下:

MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

 TrueMethodMatcher 源码如下:

final class TrueMethodMatcher implements MethodMatcher, Serializable {

    public static final TrueMethodMatcher INSTANCE = new TrueMethodMatcher();

    private TrueMethodMatcher() {
    }


    @Override
    public boolean isRuntime() {
        return false;
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        return true;
    }

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


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

    private Object readResolve() {
        return INSTANCE;
    }

}

 补充: SpringAOP和AspectJ的关系

  aop是一种思想而不是一种技术。所以说,如果抛开spring,动态代理甚至静态代理都可以算是一种aop。

  spring中的aop实现分为两种,基于动态代理的aop和基于AspectJ的aop。AspectJ是完全独立于Spring存在的一个Eclipse发起的项目。AspectJ甚至可以说是一门独立的语言,在java文件编译期间,织入字节码,改变原有的类。

  我们常看到的在spring中用的@Aspect注解只不过是Spring2.0以后使用了AspectJ的风格而已,本质上还是Spring的原生实现。

 

posted @ 2021-02-25 18:40  QiaoZhi  阅读(102)  评论(0编辑  收藏  举报