Spring技术内幕:Spring AOP的实现原理(一)

一、SpringAOP的概述

1、AOP概念

AOP是Aspect-Oriented Programming(面向切面编程)的简称。维基百科的解释如下:

Aspect是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点(crosscutting concern)。从关注点中分离出横切关注点是面向切面的程序设计的核心。分离关注点使解决特定领域问题的代码从业务逻辑代码中独立出来,业务逻辑的代码中不再含有针对特定领用问题代码的调用,业务逻辑同特定领域问题的关系通过切面来封装、维护,这样原本分散在整个应用程序中的变动就可以很好的管理起来。

2、Advice通知

Advice定义在连接点为切面增强提供织入接口。在Spring AOP中,他主要描述Spring AOP围绕方法调用而注入的切面行为。Advice是定义在org.aopalliance.aop.Advice中的接口。在Spring AOP使用这个统一接口,并通过这个接口为AOP切面增强的织入功能做了更多的细节和扩展,比如提供了更具体的通知类型,如BeforeAdvice,AfterAdvice,ThrowsAdvice等。

2.1 BeforeAdvice

首先我们从BeforeAdvice开始:

在BeforeAdvice的继承关系中,定义了为待增强的目标方法设置的前置增强接口MethodBeforeAdvice,使用这个前置接口需要实现一个回调函数:

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

作为回调函数,before方法的实现在Advice中被配置到目标方法后,会在调用目标方法时被回调。具体的参数有:Method对象,这个参数是目标方法的反射对象;Object[]对象数组,这个对象数组中包含目标方法的输入参数。以CountingBeforeAdvice为例来说明BeforeAdvice的具体使用,CountBeforeAdvice是接口MethodBeforeAdvice的具体实现,他只是统计被调用方法的次数,作为切面增强实现,他会根据调用方法的方法名进行统计,把统计结果根据方法名和调用次数作为键值对放入一个map中。代码如下:

public class CountingBeforeAdvice extends MethodCounter implements MethodBeforeAdvice {
    //实现方法前置通知MethodBeforeAdvice接口的方法
    public void before(Method m, Object[] args, Object target) throws Throwable {
    //以目标对象方法作为参数,调用父类MethodCounter的count方法统计方法调用次数
        count(m);
    }
}
CountingBeforeAdvice的父类MethodCounter的源码如下:
public class MethodCounter implements Serializable {
    //方法名—>方法调用次数的map集合,存储方法的调用次数
    private HashMap<String, Integer> map = new HashMap<String, Integer>();
    //所有方法的总调用次数
    private int allCount;
    //统计方法调用次数,CountingBeforeAdvice的调用入口
    protected void count(Method m) {
        count(m.getName());
    }
    //统计指定名称方法的调用次数
    protected void count(String methodName) {
        //从方法名—>方法调用次数集合中获取指定名称方法的调用次数
        Integer i = map.get(methodName);
//如果调用次数不为null,则将调用次数加1,如果调用次数为null,则设置调用次数为1
        i = (i != null) ? new Integer(i.intValue() + 1) : new Integer(1);
        //重新设置集合中保存的方法调用次数
        map.put(methodName, i);
        //所有方法总调用次数加1
        ++allCount;
    }
    //获取指定名称方法的调用次数
    public int getCalls(String methodName) {
        Integer i = map.get(methodName);
        return (i != null ? i.intValue() : 0);
    }
    //获取所有方法的总调用次数
    public int getCalls() {
        return allCount;
    }
    public boolean equals(Object other) {
        return (other != null && other.getClass() == this.getClass());
    }
    public int hashCode() {
        return getClass().hashCode();
    }
}

2.2 AfterAdvice

在Advice的实现体系中,Spring还提供了AfterAdvice这种通知类型,这里以AfterReturningAdvice通知的实现为例,代码如下:

public interface AfterReturningAdvice extends AfterAdvice {
//后置通知的回调方法,在目标方法对象调用结束并成功返回之后调用
// returnValue参数为目标方法对象的返回值,method参数为目标方法对象,args为
    //目标方法对象的输入参数
    void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}

afterReturning方法也是一个回调函数,AOP应用需要在这个接口实现中提供切面增强的具体设计,在这个Advice通知被正确配置以后,在目标方法调用结束并成功返回的时候,接口会被SpringAOP调用。与前面分析的一样,在Spring AOP包中,同样可以看到CountingAfterReturningAdvice,实现基本一致:

public class CountingAfterReturningAdvice extends MethodCounter implements AfterReturningAdvice {
    //实现后置通知AfterReturningAdvice的回调方法
    public void afterReturning(Object o, Method m, Object[] args, Object target) throws Throwable {
        //调用父类MethodCounter的count方法,统计方法的调用次数
        count(m);
    }
}

在实现AfterReturningAdvice的接口方法afterReturning中,可以调用MethodCounter的count方法,从而完成根据方法名对目标方法调用次数的统计。

2.3 ThrowsAdvice

下面我们来看一下Advice通知的另一种类型ThrowsAdvice。对于ThrowsAdvice,并没有制定需要实现的接口方法,他在抛出异常时被回调,这个回调是AOP使用反射机制来完成的。可以通过CountingThrowsAdvice来了解ThrowsAdvice的使用方法:

public static class CountingThrowsAdvice extends MethodCounter implements ThrowsAdvice {
        //当抛出IO类型的异常时的回调方法,统计异常被调用的次数
        public void afterThrowing(IOException ex) throws Throwable {
            count(IOException.class.getName());
        }
        //当抛出UncheckedException类型异常时的回调方法,统计异常被调用的次数
        public void afterThrowing(UncheckedException ex) throws Throwable {
            count(UncheckedException.class.getName());
        }
    }

3、Pointcut切点

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

public interface Pointcut {
    //获取类过滤器
    ClassFilter getClassFilter();
    //获取匹配切入点的方法
    MethodMatcher getMethodMatcher();
    //总匹配的标准切入点实例
    Pointcut TRUE = TruePointcut.INSTANCE;
} 

查看Pointcut切入点的继承体系,发现Pointcut切入点的实现类非常的多,如针对注解配置的AnnotationMatchingPointcut、针对正则表达式的JdkRegexpMethodPointcut等等,我们以JdkRegexpMethodPointcut为例,分析切入点匹配的具体实现,源码如下:

public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut {
    //要编译的正则表达式模式
    private Pattern[] compiledPatterns = new Pattern[0];
    //编译时要排除的正则表达式模式
    private Pattern[] compiledExclusionPatterns = new Pattern[0];
    //将给定的模式字符串数组初始化为编译的正则表达式模式
    protected void initPatternRepresentation(String[] patterns) throws PatternSyntaxException {
        this.compiledPatterns = compilePatterns(patterns);
    }
    //将给定的模式字符串数组初始化为编译时要排除的正则表达式模式
    protected void initExcludedPatternRepresentation(String[] excludedPatterns) throws PatternSyntaxException {
        this.compiledExclusionPatterns = compilePatterns(excludedPatterns);
    }
    //使用正则表达式匹配给定的名称
    protected boolean matches(String pattern, int patternIndex) {
        Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);
        return matcher.matches();
    }
    //使用要排除的正则表达式匹配给定的名称
    protected boolean matchesExclusion(String candidate, int patternIndex) {
        Matcher matcher = this.compiledExclusionPatterns[patternIndex].matcher(candidate);
        return matcher.matches();
    }
    //将给定的字符串数组编译为正则表达的模式
    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;
    }
}

4、Advisor通知器

完成对目标方法的切面增强设计(Advice)和关注点的设计(Pointcut)以后,需要一个对象把他们结合起来,完成这个作用的就是Advisor。通过他可以定义应该使用哪个通知并在哪个关注点使用它。在DefaultPointcutAdvisor中有两个属性,分别是advice和Pointcut。通过这两个属性,可以分别配置Advice和Pointcut。源码如下:

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {
    //默认切入点
    //Pointcut.TRUE在切入点中的定义为:Pointcut TRUE = TruePointcut.INSTANCE;
    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(Pointcut pointcut) {
        this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
    }
    //获取切入点
    public Pointcut getPointcut() {
        return this.pointcut;
    }
    public String toString() {
        return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
    }
}

上述源码中,通知器的默认切入点是Pointcut.TRUE,Pointcut.TRUE在切入点中的定义为:Pointcut TRUE = TruePointcut.INSTANCE

TruePointcut的INSTANCE是一个单件,比如使用static类变量来持有单件实例,使用private私有构造函数来确保除了在当前单件实现中,单件不会被再次创建和实例化。

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

下一篇我们开始一起学习Spring AOP到底是什么实现的

未完待续……

时间: 2024-12-15 01:54:58

Spring技术内幕:Spring AOP的实现原理(一)的相关文章

Spring技术内幕——Spring的设计理念和整体架构

横看成岭侧成峰,远近高低各不同. 不识庐山真面目,只缘身在此山中. --苏轼 Spring的各个子项目 1.Spring Framework(Core):Spring项目的核心.包含了一系列IOC容器的设计,提供了反转模式的实现,同时还集成了AOP功能.另外,在Spring Framework中,还包含了其他Spring的基本模块,比如MVC.JDBC.事务处理模块的实现. 2.Spring Web Flow:建立在Spring MVC基础上的Web工作流引擎.定义了一种特定的语言来描述工作流,

Spring技术内幕——Spring Framework的IOC容器实现(一)

一.SpringIOC容器概述 IOC容器和依赖反转的模式 在面向对象的系统中,对象封装了数据和对数据的处理,对象的依赖关系常常体现在对数据和方法的依赖上.这些依赖关系可以通过把对象的依赖注入交给框架IOC容器来完成.他可以再解耦代码的同时提高了代码的可测试性. 依赖控制反转的实现由很多种方式,在Spring中,IOC容器是实现这个模式的载体,他可以再对象生成或者初始化时直接将数据注入到对象中,也可以通过将对象引用注入到对象数据域中的方式来注入对方法调用的依赖.这种依赖注入是可以递归的,对象被逐

Spring技术内幕——Spring Framework的IOC容器实现(三)

接上一篇的时序图.这里调用的loadBeanDefintions实际上是一个抽象方法,那么实际载入过程发生在哪里呢?在loadBeanDefintions中,初始化了读取器XMLBeanDefinitionReader,然后把这个读取器在IOC容器中设置好(过程和编程式使用XMLBeanFactory是类似的),最后是启动读取器来完成BeanDefinition在IOC容器中的载入,代码如下: /** * Convenient base class for {@link org.springfr

Spring技术内幕——Spring Framework的IOC容器实现(二)

三.IOC容器的初始化过程 IOC容器的初始化时由前面介绍的refresh方法来启动的,这个方法标志着IOC容器的正式启动.这个启动包括BeanDefinition的Resource定位.载入和注册.下面我们将详细分析这三个实现过程,Spring把这三个过程分开,并使用不同的模块来完成,通过这样的设计让用户更加灵活的这三个过程进行剪裁和扩展,定义出最适合自己的IOC容器的初始化过程. 第一个过程: Resource定位过程,是指BeanDefinition的资源定位,他由ResourceLoad

Spring技术内幕——Spring Framework的IOC容器实现(五)(大结局)

这里通过使用BeanDefinitionResolver来对BeanDefinition进行解析,然后注入到property中.下面到BeanDefinitionValueResolver中看一下解析过程,以对Bean reference进行解析为例 /** * Resolve a reference to another bean in the factory. * class BeanDefinitionValueResolver */ private Object resolveRefer

深入探索spring技术内幕(二): 剖析spring管理Bean的原理与配置

求二叉树的宽度和深度 给定一个二叉树,获取该二叉树的宽度和深度. 例如输入 a / \ b c / \ / \ d e f g 返回3. 详细描述: 接口说明 原型: int GetBiNodeInfo(BiNode &head, unsigned int *pulWidth, unsigned int *pulHeight) 输入参数: head 需要获取深度的二叉树头结点 输出参数(指针指向的内存区域保证有效): pulWidth 宽度 pulHeight 高度 返回值: 0 成功 1 失败

深入探索spring技术内幕(四): 剖析@Resource注解实现原理与注解注入

一.@Resource注解原理 @Resource可以标注在字段或属性的setter方法上 1.  如果指定了name属性, 那么就按name属性的名称装配; 2. 如果没有指定name属性, 那就按照默认的名称查找依赖对象; 3. 如果按默认名称查找不到依赖对象, 那么@Resource注解就会回退到按类型装配; ① 先写一个自己的@MyResource: import java.lang.annotation.Retention; import java.lang.annotation.Re

深入探索spring技术内幕(七): 配置Spring AOP面向切面编程

一. AOP一些概念 Aspect( 切面 ): 指横切性关注点的抽象即为切面, 它与类相似, 只是两者的关注点不一样, 类是对物体特征的抽象, 而切面横切性关注点的抽象. joinpoint( 连接点 ): 指那些被拦截到的点. 在spring中, 这些点指的是方法, 因为spring只支持方法类型的连接点, 实际上joinpoint还可以是field或类构造器) Pointcut( 切入点 ): 指我们要对那些joinpoint进行拦截的定义. Advice( 通知 ): 指拦截到joinp

Spring技术内幕:SpringIOC原理学习总结

前一段时候我把Spring技术内幕的关于IOC原理一章看完,感觉代码太多,不好掌握,我特意又各方搜集了一些关于IOC原理的资料,特加深一下印象,以便真正掌握IOC的原理. IOC的思想是:Spring容器来实现这些相互依赖对象的创建.协调工作.对象只需要关系业务逻辑本身就可以了. SpringIOC容器的执行步骤是: 1.资源定位,即首先要找到applicationContext.xml文件 2.BeanDefinition的载入,把XML文件中的数据统一加载到BeanDefinition中,方

Spring技术内幕:设计理念和整体架构概述

程序员都很崇拜技术大神,很大一部分是因为他们发现和解决问题的能力,特别是线上出现紧急问题时,总是能够快速定位和解决. 一方面,他们有深厚的技术基础,对应用的技术知其所以然,另一方面,在采坑的过程中不断总结,积累了很多经验. 相信大家都使用过Spring,有些人了解它的核心:IOC和AOP,但只是了解它们的基本概念.使用了反射和动态代理,关于如何管理对象.代理的具体实现了解的比较浅. 有些人使用Spring MVC,使用Spring集成数据库.事务.消息队列以简化操作,但对集成的具体设计思路和实现