• Spring AOP源码分析(二)动态A0P自定义标签


    摘要: 本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。

    之前讲过Spring中的自定义注解,如果声明了自定义的注解,那么就一定会在程序中的某个地方注册了对应的解析器。我们搜索整个代码,尝试找到注册的地方,全局搜索后我们发现了在AopNamespaceHandler中对应着这样一段函数:

    @Override
    public void init() {
        // In 2.0 XSD as well as in 2.1 XSD.
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
    
        // Only in 2.0 XSD: moved to context namespace as of 2.1
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }

     此处不再对Spring中的自定义注解方式进行讨论。有兴趣的读者可以回顾之前的内容。

    我们可以得知,在解析配置文件的时候,一旦遇到aspectj-autoproxy注解时就是使用解析器AspectJAutoProxyBeanDefinitionParser进行解析,那么我们来看一看AspectJAutoProxyBeanDefinitionParser的内部实现。

     一、注册AspectJAutoProxyBeanDefinitionParser

    所有解析器,因为是对BeanDefinitionParser接口的统一实现,入口都是从parse函数开始的,AspectJAutoProxyBeanDefinitionParser的parse函数如下:

    @Override
    @Nullable
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 注册AnnotationAwareAspectJAutoProxyCreator
        AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
        // 对于注解中子类的处理
        extendBeanDefinition(element, parserContext);
        return null;
    }

    其中registerAspectJAnnotationAutoProxyCreatorIfNecessary函数是我们比较关心的,也是关键逻辑的实现。

    /**
     * 注册AnnotationAwareAspectJAutoProxyCreator
     * @param parserContext
     * @param sourceElement
     */
    public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            ParserContext parserContext, Element sourceElement) {
        // 注册或升级AutoProxyCreator定义beanName为org.springframework.aop.config.internalAutoProxyCreator的BeanDefinition
        BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
                parserContext.getRegistry(), parserContext.extractSource(sourceElement));
        // 对于proxy-target-class以及expose-proxy属性的处理
        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
        // 注册组件并通知,便于监听器做进一步处理
        registerComponentIfNecessary(beanDefinition, parserContext);
    }

     在registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中主要完成了3件事情,基本上每行代码就是一个完整的逻辑。

    1. 注册或者升级AnnotationAwareAspectJAutoProxyCreator

    对于AOP的实现,基本上都是靠AnnotationAwareAspectJAutoProxyCreator去完成,它可以根据@Point注解定义的切点来自动代理相匹配的bean。但是为了配置简便,Spring使用了自定义配置来帮助我们自动注册AnnotationAwareAspectJAutoProxyCreator,其注册过程就是在这里实现的。

    @Nullable
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,
            @Nullable Object source) {
    
        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }
    
    @Nullable
    private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,
            @Nullable Object source) {
    
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    
        // 如果已经存在了自动代理创建器且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底需要使用哪一种
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    // 改变bean最重要的就是改变bean所对应的className属性
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }
            // 如果已经存在自动代理创建器并且与将要创建的一致,那么无需再次创建
            return null;
        }
    
        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
        beanDefinition.setSource(source);
        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
        return beanDefinition;
    }

    以上代码实现了自动注册AnnotationAwareAspectJAutoProxyCreator类的功能,同时这里还涉及了—个优先级的问题,如果巳经存在了自动代理创建器,而且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底需要使用哪个。

    2. 处理proxy-target-class以及expose-proxy属性

    useClassProxyinglfNecessary 实现了 proxy-target-class 厲件以及 expose-proxy M性的处理。

    private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
        if (sourceElement != null) {
            // 对于proxy-target-class属性的处理
            boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
            if (proxyTargetClass) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            // 对于expose-proxy属性的处理
            boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
            if (exposeProxy) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }
    
    // 强制使用的过程其实也是一个属性设置的过程
    public static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry) {
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
        }
    }
    • proxy-target-class:Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理。(建议尽量使用JDK的动态代理),如果被代理的目标对象实现了至少一个接口, 則会使用JDK动态代理。所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。如果你希望强制使用CGLIB代理,(例如希望代理目标对象的所有方法,而不只是实现自接口的方法)那也可以。但是需要考虑以下两个问题。
      • 无法通知(advise) Final方法,因为它们不能被覆写。
      • 你需要将CGLIB二进制发行包放在classpath下面。

    与之相较,JDK本身就提供了动态代理,强制使用CGLIB代理需要将<aop:config>的 proxy-target-class 厲性设为 true:

    <aop:config proxy-target-class = "true">...</aop:config>

    当需要使用CGLIB代理和@AspectJ自动代理支持,可以按照以下方式设罝<aop:aspectj- autoproxy>的 proxy-target-class 属性:

    <aop:aspectj-autoproxy proxy-target-class = "true"/>

    而实际使用的过程中才会发现细节问题的差別,The devil is in the detail.

    • JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
    • CGIJB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM (开源的Java字节码编辑类库)操作字节码实现的,性能比JDK强。
    • expose-proxy:有时候目标对象内部的自我调用将无法实施切面中的增强,如下示例:
    public interface AService { 
        public void a(); 
        public void b();
    }
    
    @Service()
    public class AServicelmpll implements AService {
        @Transactional(propagation = Propagation.REQUIRED) 
        public void a() { 
            this.b{);
        }
        
        @Transactional(propagation = Propagation.REQUIRES_NEW) 
        public void b() {
        }
    }

    此处的this指向目标对象,因此调用this.b()将不会执行b事务切面,即不会执行事务增强, 因此 b 方法的事务定义“@Transactional(propagation = Propagation.REQUIRES_NEW)” 将不会实施,为了解决这个问题,我们可以这样做:

    <aop:aspectj-autoproxy expose-proxy = "true"/>

    然后将以上代码中的 “this.b();” 修改为 “((AService) AopContext.currentProxy()).b();” 即可。 通过以上的修改便可以完成对a和b方法的同时增强。

    最后注册组件并通知,便于监听器做进一步处理,这里就不再一一赘述了。

  • 相关阅读:
    Python(错误的处理方法)
    WP7 QQ词典V1.1 共享源代码
    【原创】Windows Phone真机抓包并分析应用程序的网络通讯(Android、iPhone也适用)
    使用uiautomation自动化重命名pdf书签,使全大写字母变成首字母大写
    分享python分析wave, pcm音频文件
    最近在做的一个wp7地图应用
    HTML5初探 基本的HTML5模版
    HTML5页面架构元素 <header>标签
    Wix 打包(1)[转载]
    windows 域验证 IIS7.0
  • 原文地址:https://www.cnblogs.com/warehouse/p/9426342.html
Copyright © 2020-2023  润新知