Spring AOP源码剖析——跟进中......

之前就Spring AOP的基本术语和原理及使用有了一定的认识,作为Spring核心特性之一,AOP同样很有必要重点掌握。这次沉淀将会开启AOP源码阅读的序幕。

对于源码的理解,以注释添加在对应代码块上方

一、AOP概念回顾

为什么会有面向切面编程(AOP)?众所周知,Java是一个面向对象(OOP)的语言,但它有一些弊端,例如:当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日志、权限、事务、性能监测等功能时,只能在在每个对象里引用公共行为,这样做不便于维护,而且有大量重复代码,通常我们更希望的是这些模块可以实现热插拔特性而且无需把外围的代码入侵到核心模块中。

为了能够更好地将系统级别的代码抽离出来,去掉与对象的耦合,就产生了面向AOP(面向切面)。



如上图所示,OOP属于一种横向扩展,AOP是一种纵向扩展。AOP依托于OOP,进一步将系统级别的代码抽象出来,进行纵向排列,实现低耦合。

AOP的出现弥补了OOP的这点不足。假设现在我们把日志、权限、事务、性能监测等外围业务看作单独的关注点(也可以理解为单独的模块),每个关注点都可以在需要它们的时刻及时被运用而且无需提前整合到核心模块中。

二、源码剖析

1. Demo

  public static void main(String[] args) {
        //创建代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        //设置目标对象
        proxyFactory.setTarget(new MyLogServiceImpl());
        //前置增强
        proxyFactory.addAdvice(new MyLogBefore());
        //后置增强
        proxyFactory.addAdvice(new MyLogAfter());
        //从代理工厂中获取代理
        MyLogService myLogService = (MyLogService) proxyFactory.getProxy();
        myLogService.log("x");
   }

2. 源码剖析

在demo中,直接获取的是一个代理,不是要使用的实现类,这是因为AOP其实就是代理模式,在编译期或者运行期,给我们原来的代码增加一些功能,成为一个代理。当我们调用的时候,实际就是调用的代理类。

代码中首先创建一个代理工厂实例:

ProxyFactory proxyFactory = new ProxyFactory();

代理工厂的作用就是使用编程的方式创建AOP代理。ProxyFactory继承自AdvisedSupport,AdvicedSupport是AOP代理的配置管理器。

需要明白的是,Spring中实现AOP,就是生成一个代理,然后在使用的时候调用代理。

首先从方法proxyFactory.setTarget(new LoginServiceImpl())开始剖析源码:

public void setTarget(Object target) {
    /**首先根据给定的目标实现类,创建一个单例的TargetSource
    **然后设置TargetSource
    */
    setTargetSource(new SingletonTargetSource(target));
}

类SingletonTargetSource实现接口TargetSource :

public interface TargetSource {
    //返回目标类的类型
    Class getTargetClass();
    
    /**查看TargetSource是否是static的
    *静态的TargetSource每次都返回同一个Target
    */
    boolean isStatic();
    
    //获取目标类的实例
    Object getTarget() throws Exception;
    
    //释放目标类
    void releaseTarget(Object target) throws Exception;
}

类SingletonTargetSource定义如下:

public final class SingletonTargetSource implements TargetSource, Serializable {

    //用来保存目标类   
    private final Object target;
    //构造方法
    public SingletonTargetSource(Object target) {
        this.target = target;
    }
    //直接返回目标类的类型
    public Class getTargetClass() {
        return target.getClass();
    }
    //返回目标类
    public Object getTarget() {
        return this.target;
    }
    //释放目标类,代码逻辑为空
    public void releaseTarget(Object o) {
        // Nothing to do
    }
    //是否为静态,这里直接返回true
    public boolean isStatic() {
        return true;
    }

    //重写equals方法
    public boolean equals(Object other) {
        //相等,返回true
        if (this == other) {
            return true;
        }
        //如果不是SingletonTargetSource类型的返回false
        if (!(other instanceof SingletonTargetSource)) {
            return false;
        }
        SingletonTargetSource otherTargetSource = (SingletonTargetSource) other;
        //判断目标类是否相等
        return ObjectUtils.nullSafeEquals(this.target, otherTargetSource.target);
    }
    
    //重写toString方法
    public String toString() {
        return "SingletonTargetSource: target=(" + target + ")";
    }
}

在AdvisedSupport类中的设置目标类setTargetSource方法:

public void setTargetSource(TargetSource targetSource) {
    if (isActive() && getOptimize()) {
        throw new AopConfigException("Can't change target with an optimized CGLIB proxy: it has its own target");
    }
    //将构建的TargetSource缓存起来
    this.targetSource = targetSource;
}

设置了要代理的目标类之后,接下来就是添加通知,即添加增强类,proxyFactory.addAdvice()方法是添加增强类的方法。对应demo中是:

        //前置增强
        proxyFactory.addAdvice(new MyLogBefore());
        //后置增强
        proxyFactory.addAdvice(new MyLogAfter());

addAdvice方法的参数是一个Advice类型的类,也就是通知或者叫增强,这里先介绍有关通知Advice的代码。

Advice接口

Advice不属于Spring,是AOP联盟定义的接口。Advice接口并没有定义任何方法,是一个空的接口,用来做标记,实现了此接口的的类是一个通知类。Advice有几个子接口:

  • BeforeAdvice,前置增强,即在我们的目标类之前调用的增强,未定义任何方法;
  • AfterReturningAdvice,方法正常返回前的增强,该增强可以看到方法的返回值,但不能更改返回值;
  • ThrowsAdvice,抛出异常时候的增强,是一个标志接口,未定义任何方法;
  • Interceptor,拦截器,未定义任何方法,表示一个通用的拦截器。不属于Spring;
  • DynamicIntroductionAdvice,动态引介增强,有一个方法implementsInterface。

MethodBeforeAdvice接口,是BeforeAdvice的子接口,表示在方法前调用的增强。

public interface MethodBeforeAdvice extends BeforeAdvice {
    
    /**在给定的方法调用前,调用该方法
    *参数method是被代理的方法
    *参数args是被代理方法的参数
    *参数target是方法调用的目标,可能为null
    */
    void before(Method m, Object[] args, Object target) throws Throwable;
}

我们来看看向代理工厂中添加增强的addAdvice方法,addAdvice方法在AdvisedSupport类中:

public void addAdvice(Advice advice) throws AopConfigException {
    /**advisors是Advice列表,是一个LinkedList
    *如果被添加进来的是一个Interceptor,会先被包装成一个Advice
    *添加之前现获取advisor的大小,当做添加的Advice的位置
    */
    int pos = (this.advisors != null) ? this.advisors.size() : 0;
    //添加Advice
    addAdvice(pos, advice);
}

未完跟进中......(addAdvice(pos, advice)方法)

方法addAdvice(pos, advice)源码:

//添加Advice
public void addAdvice(int pos, Advice advice) throws AopConfigException {
    //只能处理已经实现了AOP联盟的接口的拦截器
    if (advice instanceof Interceptor && !(advice instanceof MethodInterceptor)) {
        throw new AopConfigException(getClass().getName() + " only handles AOP Alliance MethodInterceptors");
    }
    /**如果advice是IntroductionInfo接口类型,
    *不需要IntroductionAdvisor
    */
    if (advice instanceof IntroductionInfo) {
        addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
    }
    /**如果advice是动态引介增强,
    *需要IntroductionAdvisor
    */
    else if (advice instanceof DynamicIntroductionAdvice) {
        throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");
    }
    else {
        //添加增强器,需要先将增强包装成增强器,然后进行添加
        addAdvisor(pos, new DefaultPointcutAdvisor(advice));
    }
}

添加增强的过程:实际调用添加增强器这个方法

  1. 将Advice包装成一个PointCutAdvisor;
  2. 然后在添加增强器。

Advisor接口

Advisor,增强器,它持有一个增强Advice和一个过滤器,用来决定Advice的所使用的位置。

public interface Advisor {
    
    //判断Advice是否存在于每个实例中
    boolean isPerInstance();
    
    //返回当下持有的Advice
    Advice getAdvice();

}

PointcutAdvisor

PointcutAdvisor是一个持有Pointcut切点的增强器,拥有一个Advice和一个Pointcut。

public interface PointcutAdvisor extends Advisor {
    //获取Pointcut
    Pointcut 
}

Pointcut接口

即切入点,定义了哪些连接点需要被织入横切逻辑。

public interface Pointcut {
    //类过滤器,用于明确需要拦截的类
    ClassFilter getClassFilter();
    //方法匹配器,用于明确需要拦截的方法
    MethodMatcher getMethodMatcher();
    
    Pointcut TRUE = TruePointcut.INSTANCE; 
}

ClassFilter接口

public interface ClassFilter {
    //判断所给的类是否需要拦截
    boolean matches(Class clazz);

    ClassFilter TRUE = TrueClassFilter.INSTANCE;
}

MethodMatcher接口

public interface MethodMatcher {    
    //静态方法匹配
    boolean matches(Method m, Class targetClass);
    //是否是运行时动态匹配
    boolean isRuntime();
    //运行时动态匹配
    boolean matches(Method m, Class targetClass, Object[] args);
    
    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}

了解了这些相关定义之后,回到源码逻辑的剖析,让我们来看看添加增强器方法addAdvisor的具体实现:

 public void addAdvisor(int pos, Advisor advisor) throws AopConfigException {
    //如果增强器是引介增强器
    if (advisor instanceof IntroductionAdvisor) {
        addAdvisor(pos, (IntroductionAdvisor) advisor);
    }
    else {
        //其他的增强器处理
        addAdvisorInternal(pos, advisor);
    }
}

方法 addAdvisorInternal(pos, advisor)的源码:

private void addAdvisorInternal(int pos, Advisor advice) throws AopConfigException {
    if (isFrozen()) {
        throw new AopConfigException("Cannot add advisor: config is frozen");
    }
    //把Advice添加到LinkedList中指定位置pos
    this.advisors.add(pos, advice);
    //同时更新Advisors数组
    updateAdvisorArray();
    //通知监听器
    adviceChanged();
}

获取代理

上述源码是在组装代理工厂,接下类我们继而剖析代理的生成,方法proxyFactory.getProxy()这一步就是获取代理的过程:

public Object getProxy() {
    //创建AOP代理
    AopProxy proxy = createAopProxy();
    //返回代理
    return proxy.getProxy();
}

Spring中,创建代理通常有两种方式:

  1. JDK动态代理;
  2. CGLIB动态代理。
    而这里的创建AOP代理就是生成这两种代理中的一种。
protected synchronized AopProxy createAopProxy() {
    if (!this.isActive) {
        activate();
    }
    //获取AOP代理工厂,然后创建代理
    return getAopProxyFactory().createAopProxy(this);
}
public AopProxy createAopProxy(AdvisedSupport advisedSupport) throws AopConfigException {
    //判断使用ObjenesisCglibAopProxy还是JdkDynamicAopProxy
    /**如果代理的是类,
    *就使用CGLIB的方式来创建代理
    */    
boolean useCglib = advisedSupport.getOptimize() || advisedSupport.getProxyTargetClass() || advisedSupport.getProxiedInterfaces().length == 0;
    if (useCglib) {
        return CglibProxyFactory.createCglibProxy(advisedSupport);
    }
    else {
        /**如果代理的是接口,
        *就使用JDK动态代理来创建代理
        */    
        return new JdkDynamicAopProxy(advisedSupport);
    }
}

JDK动态代理

看下JDK动态代理的方式,对于方法的调用,调用的是代理类的invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation = null;
    Object oldProxy = null;
    boolean setProxyContext = false;
    //所代理的目标对象
    TargetSource targetSource = advisedSupport.targetSource;
    Class targetClass = null;
    Object target = null;       

    try {
        //equal、hashCode 等方法
        if (method.getDeclaringClass() == Object.class && "equals".equals(method.getName())) {
            return equals(args[0]) ? Boolean.TRUE : Boolean.FALSE;
        }
        else if (Advised.class == method.getDeclaringClass()) {
            return AopProxyUtils.invokeJoinpointUsingReflection(this.advisedSupport, method, args);
        }

        Object retVal = null;

        //代理目标对象
        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }
        if (this.advisedSupport.exposeProxy) {
            // Make invocation available if necessary
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        //获取此方法的拦截链
        List chain = this.advisedSupport.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                this.advisedSupport, proxy, method, targetClass);

        //如果没有配置通知
        if (chain.isEmpty()) {
            //直接调用目标对象的方法
            retVal = AopProxyUtils.invokeJoinpointUsingReflection(target, method, args);
        }
        else {
            //如果配置了通知,创建一个方法调用
            invocation = new ReflectiveMethodInvocation(proxy, target,
                                method, args, targetClass, chain);

            //执行通知链,沿着通知器链调用所有的通知
            retVal = invocation.proceed();
        }
        if (retVal != null && retVal == target) {
            //返回值为自己
            retVal = proxy;
        }
        //返回
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }

        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

上述方法步骤:

  1. 如果拦截链不为空,则创建一个ReflectiveMethodInvocation;
  2. 调用其proceed方法;3
  3. proceed方法的调用会递归调用,直到所有的MethodInterceptor调用完
  4. 如果拦截链为空,直接调用目标对象的方法。

未完跟进中......

参考

https://juejin.im/post/591d8c8ba22b9d00585007dd

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,896评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,283评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,085评论 0 367
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,010评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,015评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,492评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,858评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,829评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,374评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,409评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,527评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,131评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,858评论 3 339
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,296评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,434评论 1 276
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,087评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,597评论 2 366

推荐阅读更多精彩内容

  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 12,305评论 6 86
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,748评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,444评论 25 707
  • 等待是一种没有限度的爱,等待着你的问候,等待着你的信息,等待着你的电话,等待也是一种煎熬…… 等待承载了太多的思念...
    荣耀启程阅读 292评论 0 2
  • 每天早出晚归,跟孩子相处的时间仅有几个小时,这两天不断使用林老师的方法,让我懂得和孩子的相处要学会接受同时也要不断...
    英才日记阅读 184评论 0 0