[Spring]Spring AOP创建动态代理的过程

Spring AOP的大致流程

我们在之前的2章中,知晓了Spring AOPBeanPostProcessor的几个关键接口做了介入,分为几个关键点:

  1. 将切面类进行解析成advisors,其中包括解析XML和被@Aspect注解标注的Java切面类.
  2. 在初始化Bean的阶段,将advisors对当前Bean进行筛选,获取到当前Bean匹配的advisors.
  3. 进行动态代理类的创建.

今天,我们就来看看动态代理类在Spring AOP中是怎样去创建出来的.

创建动态代理的入口:wrapIfNecessary

  • org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // beanName不为空,并且存在于targetSourcedBeans中,也就是自定义的TargetSource被解析过了
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    // 如果Bean为advisedBeans,也不需要被代理
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    // isInfrastructureClass和shouldSkip的作用:
    // 识别切面类,加载切面类成advisors
    // 为什么又执行一次是因为存在循环依赖的情况下无法加载advisor
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // Create proxy if we have advice.
    // 返回匹配当前Bean的所有Advice、Advisor、Interceptor
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // 创建Bean对应的代理,SingletonTargetSource用于封装实现类的信息
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }
    // 下次代理的时候直接从缓存拿出判断,不需要重复代理
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}
  1. targetSourcedBeans存储了用户自定义的targetSource,这部分的bean在org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiation中已经进行了createProxy的处理,所以在自动化代理的环节中,要过滤掉这部分的bean.
  2. advisedBeans存储了已经被Spring AOP处理过的bean,这部分的bean也是需要进行过滤的.
  3. 进行isInfrastructureClassshouldSkip的处理,这两个方法在之前的文章已经做了详细的解析,它们的主要作用是:识别切面类、解析切面类。之所以再重复调用一次,是为了收尾的工作.
  4. getAdvicesAndAdvisorsForBean获取当前bean匹配的advisors.
  5. 将当前bean缓存到advisedBeans,创建动态代理-createProxy,缓存proxyType. 这里无论是否能获取到bean的advisors,都会做缓存到advisedBeans的步骤.已确保不重复处理同一个bean.

创建动态代理:createProxy

  • org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
        @Nullable Object[] specificInterceptors, TargetSource targetSource) {
    // 如果beanFactory是ConfigurableListableBeanFactory的类型,暴露目标类
    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }
    // 创建一个ProxyFactory,当前ProxyCreator在创建代理时,将需要用到的字段赋值到ProxyFactory中去
    ProxyFactory proxyFactory = new ProxyFactory();
    // 将当前的AnnotationAwareAspectJAutoProxyCreator对象的属性赋值给ProxyFactory对象
    // 加载一些ProxyConfig
    proxyFactory.copyFrom(this);
    // 处理proxyTargetClass属性
    // 如果希望使用CGLIB进行代理,配置proxyTargetClass为true
    if (!proxyFactory.isProxyTargetClass()) {
        // 检查相应BeanDefinition的“ preserveTargetClass”属性
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        }
        else {
            // 1. 有接口的,调用一次或者多次:proxyFactory.addInterface(ifc);
            // 2. 没有接口的,调用: proxyFactory.setProxyTargetClass(true);
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }
    // 这个方法主要是对前面传递进来的横切逻辑实例进行包装
    // specificInterceptors中有Advice和Interceptor,它们都会被包装成Advisor
    // 调用的先后顺序,通过方法中的applyCommonInterceptorsFirst参数进行设置
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    // 扩展实现,子类可以定制proxyFactory
    customizeProxyFactory(proxyFactory);

    proxyFactory.setFrozen(this.freezeProxy);
    // 设置preFiltered的属性值,默认为false.子类:AbstractAdvisorAutoProxyCreator修改为true
    // preFiltered字段的意思是:是否为特定目标类筛选Advisor
    // 该字段和DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice获取所有的Advisors
    // CglibAopProxy和JdkDynamicAopProxy都会调用此方法,然后递归执行所有的advisor
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }

    return proxyFactory.getProxy(getProxyClassLoader());
}
  1. 如果当前的beanFactory属于ConfigurableListableBeanFactory类型,给当前bean对应的BeanDefinition设置上属性"originalTargetClass"为targetClass.
  2. 先new一个ProxyFactory实例,并根据AnnotationAwareAspectJAutoProxyCreator的配置进行proxyTargetClassoptimizeexposeProxyfrozenopaque的属性设置.
  3. Spring不仅提供了对所有bean生效的proxyTargetClass设置,也提供了对单个bean的动态代理配置preserveTargetClass.
  4. 调用buildAdvisors对specificInterceptors进行适配,封装成advisor.有兴趣的可以看看org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry#wrap.
  5. 配置advisorstargetSourcefrozen等属性后,进行动态代理的创建.
  6. proxyFactory.getProxy(getProxyClassLoader()),进行动态代理生成.

getProxy: 根据当前bean选择JDK或者CGLIB动态代理

  • org.springframework.aop.framework.ProxyFactory#getProxy(java.lang.ClassLoader)
public Object getProxy(@Nullable ClassLoader classLoader) {
    // 首先获取AopProxy对象,其主要有两个实现:JdkDynamicAopProxy和ObjenesisCglibAopProxy
    // 分别用于JDK和CGLIB代理类的生成,其getProxy方法则用于获取具体的代理对象
    return createAopProxy().getProxy(classLoader);
}

ProxyFactoryProxyCreatorSupport的子类,在ProxyCreatorSupport中有一个createAopProxy方法,从这里可以获取到具体的代理工厂类.

  • org.springframework.aop.framework.ProxyCreatorSupport#createAopProxy
protected final synchronized AopProxy createAopProxy() {
    // 激活AdvisedSupportListener监听器
    if (!this.active) {
        activate();
    }
    return getAopProxyFactory().createAopProxy(this);
}
  1. activate方法主要是用来激活AdvisedSupportListener的.
  2. 重点方法是这个:getAopProxyFactory().createAopProxy(this);其中,getAopProxyFactory中存储了一个DefaultAopProxyFactory,它实现了AopProxyFactory接口,接口方法createAopProxy才是真正获取代理工厂的地方.
  • org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    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.");
        }
        // 如果要代理的类本身就是接口
        // 或者它已经是JDK的代理类(Proxy子类)
        // 使用JDK动态代理
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        // 使用CGLIB动态代理
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        // 接口->JDK动态代理
        return new JdkDynamicAopProxy(config);
    }
}

这里就会看到我们很熟悉的一个逻辑:实现了接口的bean,Spring会使用JDK动态代理;否则使用CGLIB代理.

工厂模式:JdkDynamicAopProxy和ObjenesisCglibAopProxy

代理策略选择

由于篇幅的原因,我选择讲解JdkDynamicAopProxy这种模式生成代理的源码解析.

  • org.springframework.aop.framework.JdkDynamicAopProxy#getProxy(java.lang.ClassLoader)
public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }
    // 获取完整的代理接口
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    // JdkDynamicAopProxy本身实现了InvocationHandler接口
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

关于JDK如何生成动态代理的原理,这里不做过多的分析了,这在我之前的博客中有关于JDK和CGLIB生成Proxy的文章,不了解的读者可以去看看:
点我前往
在生成代理之后,Proxy就会调用InvocationHandler.invoke方法,所以关于Spring AOP的代理拦截链执行流程,我们直接去到invoke解析即可。

  • org.springframework.aop.framework.JdkDynamicAopProxy#invoke
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler

首先,JdkDynamicAopProxy实现了InvocationHandler接口,动态代理类最终会执行InvocationHandler.invoke.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    try {
        // equals方法不需要代理
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            return equals(args[0]);
        }
        // hashcode方法不需要代理
        else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // The target does not implement the hashCode() method itself.
            return hashCode();
        }
        // 如果当前方法是Spring织入的DecoratingProxy接口中的方法,返回目标对象的class类型
        else if (method.getDeclaringClass() == DecoratingProxy.class) {
            // There is only getDecoratedClass() declared -> dispatch to proxy config.
            return AopProxyUtils.ultimateTargetClass(this.advised);
        }
        // 如果被代理的对象本身实现了Advised接口,则证明该类里面的方法已经被代理了,直接执行即可
        else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;
        // 是否暴露代理引用
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        // Get as late as possible to minimize the time we "own" the target,
        // in case it comes from a pool.
        // 获取目标类
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);

        // Get the interception chain for this method.
        // 获取方法的拦截链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        // Check whether we have any advice. If we don't, we can fallback on direct
        // reflective invocation of the target, and avoid creating a MethodInvocation.
        // 如果该方法上没有匹配的拦截器,直接反射调用Method.invoke(target,args)
        if (chain.isEmpty()) {
            // We can skip creating a MethodInvocation: just invoke the target directly
            // Note that the final invoker must be an InvokerInterceptor so we know it does
            // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        }
        else {
            // We need to create a method invocation...
            // 创建ReflectiveMethodInvocation
            MethodInvocation invocation =
                    new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // Proceed to the joinpoint through the interceptor chain.
            retVal = invocation.proceed();
        }

        // Massage return value if necessary.
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target &&
                returnType != Object.class && returnType.isInstance(proxy) &&
                !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            // Special case: it returned "this" and the return type of the method
            // is type-compatible. Note that we can't help if the target sets
            // a reference to itself in another returned object.
            retVal = proxy;
        }
        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}
  1. Spring AOP不对equals和hashCode做代理.
  2. 如果当前方法是Spring织入的DecoratingProxy接口中的方法,返回目标对象的class类型.
  3. 如果被代理的对象本身实现了Advised接口,则证明该类里面的方法已经被代理了,直接执行即可
  4. 查看当前配置判断是否暴露代理引用,可以来解决this调用引起的代理失效等问题.
  5. 从targetSource中获取target信息.获取当前method的拦截链路.getInterceptorsAndDynamicInterceptionAdvice会对method的拦截链进行缓存,如果没有缓存,会执行org.springframework.aop.framework.DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice重新对method进行match.
  6. getInterceptorsAndDynamicInterceptionAdvice获取到chain之后,如果chain为空,那么直接激活method.
  7. 如果chain不为空,生成ReflectiveMethodInvocation,从JavaDoc可以知道,这是一个Spring实现了AOP Alliance MethodInvocation接口的类,关键方法:invokeJoinpoint(激活连接点方法)、proceed(执行拦截链逻辑).
  8. 执行proceed.
  9. 处理返回值。包装此返回值(如果有必要作为代理),并验证没有将null作为原语返回。这是什么意思呢:假设方法返回的是int,增强around方法返回了null,这就会报错,因为基础数据类型都有默认值.
  10. 释放资源、重新设置一次代理引用.

这里最关键的的看chain的执行过程,整个Spring AOP拦截链执行的过程采用了一种递归的方式,值得一品.

代理类执行链路

  • org.springframework.aop.framework.ReflectiveMethodInvocation#proceed
public Object proceed() throws Throwable {
    // We start with an index of -1 and increment early.
    // 如果拦截器执行完了,则执行连接点
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    // 从0开始执行,每次递归进来都会+1
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // Evaluate dynamic method matcher here: static part will already have
        // been evaluated and found to match.
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
        // 动态匹配:运行时参数是否满足匹配条件
        if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // Dynamic matching failed.
            // Skip this interceptor and invoke the next in the chain.
            // 动态匹配失败,进入下一个拦截器
            return proceed();
        }
    }
    else {
        // It's an interceptor, so we just invoke it: The pointcut will have
        // been evaluated statically before this object was constructed.
        // 执行当前拦截器
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}
  1. 在遇到AfterThrowingAfterRetruningAfter这类advice的时候,它们会执行mi.proceed回到chain中.
  2. 回到chain中后,列表索引自增,往下执行下一个advice.
  3. 依次递归,直到遇到Around通知,执行around.
  4. 在around中执行joinPoint.proceed又会回到chain中.
  5. 递归到Before,执行before,然后回到around的后置代码块中.
  6. 回溯到afterafterReturning,如果有异常,执行afterThrowing.
执行流程图
proceed

总结

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

推荐阅读更多精彩内容