Spring AOP从原理到源码(三)

接着上一节Spring AOP从原理到源码(二),本节关注spring aop创建代理对象的过程。

Spring AOP的ProxyFactory

本文希望传递的是掌握spring aop的方法,而不是抠源码,因为源码本身并没有什么难度,掌握的方法以后,基本上都能看懂。

学习源码的方法一般是从两种:

  1. 自上而下
  2. 自下而上
    其实这跟我们设计软件的思路是一样的。自上而下可以让你避免抠细节,可以从大局上知道功能的整体情况,整体流程;自下而上则是先了解每个组件的细节以后,再将所有组件整合成完成的功能。两种方法其实各有利弊。

刚开始看spring aop源码的时候也是采用自上而下的,从EnableAspectJAutoProxy注解开始,了解了aop的整体流程。但是总感觉不够深刻,因为发现里面有很多的细节(概念):AdviceAdvisorPointcutAdvisedProxyConfgAspectJ*AspectJAfterAfterAdviceAspectJAfterReturningAfterAdvice……)……被搞得有点凌乱

于是决定从ProxyFactoryProxyFactoryBean开始。

先从两段测试代码开始:

import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;

/**
 * <br/>
 *
 * @author
 * @version V1.0
 * @email
 * @date 2020-04-17 16:41
 */
public class ProxyFactoryTest {

    @Test
    public void test() {
        IMyService target = new MyServiceImpl();
        ProxyFactory factory = new ProxyFactory(target);

        // 注意这里添加的是Advice
        factory.addAdvice(new MyMethodBeforeAdvice());

        IMyService proxy = (IMyService) factory.getProxy();
        System.out.println(proxy.doService("aop"));
    }

    @Test
    public void test1() {
        IMyService target = new MyServiceImpl();
        ProxyFactory factory = new ProxyFactory(target);

        // 注意这里添加的是Advisor
        factory.addAdvisor(new DoServiceMethodPointcutAdvisor());
        IMyService proxy = (IMyService) factory.getProxy();
        System.out.println(proxy.doService("aop"));

        System.out.println("-------------------我是无情的分割线------------------");
        System.out.println(proxy.anotherService("你觉得我会被拦截吗?"));
    }
}

IMyService.java

public interface IMyService {
    String doService(String msg);
    String anotherService(String msg);
}

MyMethodBeforeAdvice.java

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(String.format("%s#before正在拦截%s方法", getClass().getSimpleName(), method));
    }
}

MyServiceImpl.java

public class MyServiceImpl implements IMyService {
    @Override
    public String doService(String msg) {
        System.out.println("MyServiceImpl#doService方法正在被调用");
        return "Hello, " + msg;
    }

    @Override
    public String anotherService(String msg) {
        System.out.println("这是MyServiceImpl#anotherService方法正在被调用");
        return "Hello, " + msg;
    }
}
public class DoServiceMethodPointcutAdvisor implements PointcutAdvisor {
    @Override
    public Pointcut getPointcut() {
        return new Pointcut() {
            @Override
            public ClassFilter getClassFilter() {
                return new ClassFilter() {
                    @Override
                    public boolean matches(Class<?> clazz) {
                        return clazz.equals(MyServiceImpl.class);
                    }
                };
            }

            @Override
            public MethodMatcher getMethodMatcher() {
                return new MethodMatcher() {
                    @Override
                    public boolean matches(Method method, Class<?> targetClass) {// 只拦截doService方法
                        return method.getName().equalsIgnoreCase("doService");
                    }

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

                    @Override
                    public boolean matches(Method method, Class<?> targetClass, Object... args) {
                        return false;
                    }
                };
            }
        };
    }

    @Override
    public Advice getAdvice() {
        return new MyMethodBeforeAdvice();
    }

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

代码详解

接着一行代码带着大家看:
前面说过AOP的核心过程,所以,看源码其实分两步看即可:(划重点、划重点、划重点)

  1. 代理对象的创建
  2. 调用方法的拦截。

1. 代理对象的创建

new ProxyFactory(target);

/**
    * Create a new ProxyFactory.
    * <p>Will proxy all interfaces that the given target implements.
    * @param target the target object to be proxied
    */
public ProxyFactory(Object target) {
    // 把被代理对象封装到SingletonTargetSource里,没什么特别的逻辑
    setTarget(target);
    // 拿到被代理对象的所有接口
    setInterfaces(ClassUtils.getAllInterfaces(target));
    // 以上两段都没什么特别的逻辑,就是保存配置,为什么说是配置呢?
    // 因为ProxyFactory是ProxyConfig的子类,它肯定要保存代理相关的配置,才能创建出代理对象
}
这一段的核心就是把被代理的对象保存起来。

factory.addAdvice(new MyMethodBeforeAdvice());

@Override
public void addAdvice(Advice advice) throws AopConfigException {
    // 插入一个通知,Advice就是特定连接点上的执行东西
    // 此处不必过于抠术语,简单理解就是插入了一个拦截器,拦截器在你执行方法的时候会进行拦截,然后增强
    // pos为advice待插入的位置,其实就是末尾!
    int pos = this.advisors.size();
    addAdvice(pos, advice);
}

/**
    * Cannot add introductions this way unless the advice implements IntroductionInfo.
    */
@Override
public void addAdvice(int pos, Advice advice) throws AopConfigException {
    Assert.notNull(advice, "Advice must not be null");
    /**
        * 一点补充知识:
        *   IntroductionAdvisor类级别的拦截(类中所有方法都会被拦截)。
        *   PointcutAdvisor可以实现方法级别的拦截。
        *   DynamicMethodMatcher方便实现根据方法参数拦截
        */
    // 这里先不纠结,直接看else
    if (advice instanceof IntroductionInfo) {
        // We don't need an IntroductionAdvisor for this kind of introduction:
        // It's fully self-describing.
        addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice));
    }
    else if (advice instanceof DynamicIntroductionAdvice) {
        // We need an IntroductionAdvisor for this kind of introduction.
        throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor");
    }
    else {
        // 把advice封装成DefaultPointcutAdvisor
        /**
            * 再次插入一点背景知识:
            *   Advisor(或者说PointcutAdvisor)是Pointcut+Advice的结合体
            *   Advice定义在连接点做什么。
            *   Pointcut是用来匹配切入点的:它能触发类匹配(ClassFilter)+方法匹配(MethodMather)
            */
        addAdvisor(pos, new DefaultPointcutAdvisor(advice));
    }
}

@Override
public void addAdvisor(int pos, Advisor advisor) throws AopConfigException {
    // 先不关注
    if (advisor instanceof IntroductionAdvisor) {
        validateIntroductionAdvisor((IntroductionAdvisor) advisor);
    }
    // 加入到list里
    addAdvisorInternal(pos, advisor);
}

private void addAdvisorInternal(int pos, Advisor advisor) throws AopConfigException {
    Assert.notNull(advisor, "Advisor must not be null");
    if (isFrozen()) {
        throw new AopConfigException("Cannot add advisor: Configuration is frozen.");
    }
    if (pos > this.advisors.size()) {
        throw new IllegalArgumentException(
                "Illegal position " + pos + " in advisor list with size " + this.advisors.size());
    }
    // 添加到末尾,没啥好说的。
    // 不知道出于什么考虑advisors用的是LinkedList
    // 不过把advisor添加到末尾倒是效率非常高的,因为LinkedList内部保存了last指针(引用)
    this.advisors.add(pos, advisor);
    updateAdvisorArray();
    // 拦截器发生改变的时候需要把缓存清一下,不然方法被拦截就不正确了
    adviceChanged();
}

整一段的核心逻辑就是把Advice封装成DefaultPointcutAdvisor加入到List里。

factory.getProxy();

/**
    * Create a new proxy according to the settings in this factory.
    * <p>Can be called repeatedly. Effect will vary if we've added
    * or removed interfaces. Can add and remove interceptors.
    * <p>Uses a default class loader: Usually, the thread context class loader
    * (if necessary for proxy creation).
    * @return the proxy object
    */
public Object getProxy() {
    // 由具体的AopProxy来创建代理对象
    return createAopProxy().getProxy();
}

分两段看:

  1. createAopProxy():
/**
    * Subclasses should call this to get a new AOP proxy. They should <b>not</b>
    * create an AOP proxy with {@code this} as an argument.
    */
protected final synchronized AopProxy createAopProxy() {
    // 当第一个代理被创建的时候active被设置成true,并且通知listener(s)
    // 观察者模式
    if (!this.active) {
        activate();
    }
    // 真正创建的工作是交给DefaultAopProxyFactory#createAopProxy的,传入的参数是this,代表AdvisedSupport(ProxyConfig)
    return getAopProxyFactory().createAopProxy(this);
}

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    // 做了这些配置,或者被代理对象(target)没有实现接口,就要cglib来创建代理对象。
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        // 有实现的接口,就用JDK来创建代理
        // 需要关注的就是JdkDynamicAopProxy持有config对象和它本身实现了InvocationHandler,
        // 所以拦截方法的时候会被调用invoke方法,不明白的先恶补JDK的动态代理原理。
        return new JdkDynamicAopProxy(config);
    }
}
  1. getProxy()
    这里会有多个实现,此处关注的是JdkDynamicAopProxy#getProxy


@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    // 对equals和hashcode方法进行标记,不是重点
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    // 创建出代理对象,InvocationHandler指向自己(this),所以拦截方法的时候会触发invoke调用
    // 至此,代理对象被创建出来,创建过程就算完成了。
    // 剩下的就是关注调用过程的方法拦截了。
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

至此,代理对象已经创建出来了。

小结

简单总结一下:

  1. 封装被代理对象;
  2. 添加Advice,封装成Advisor
  3. 创建代理对象。

下一节一起看看方法调用的拦截过程是怎么实现的。
转载请说明出处

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

推荐阅读更多精彩内容