springAOP源码分析

Posted 烟尘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springAOP源码分析相关的知识,希望对你有一定的参考价值。

SpringAop实现为动态代理进行实现的,实现方式有2种,JDK动态代理和CGlib动态代理
先写一个AOP的案列加以说明
配置文件代码为:

    <bean id="userDao" class="com.spring.aop.service.UserDaoImpl"/>
    
    <bean id="logger" class="com.spring.aop.log.Logger" />
    
    <!-- 切面:切入点和通知 -->
    <aop:config>
        <aop:aspect id="aspect"  ref="logger">
            <aop:pointcut expression="execution(* com.spring.aop.service..*.*(..))" id="udpateUserMethod" />
            <aop:before method="recordBefore" pointcut-ref="udpateUserMethod" />
            <aop:after method="recordAfter"   pointcut-ref="udpateUserMethod" />
        </aop:aspect>
    </aop:config>

  其中增强类Logger的实现为:

package com.spring.aop.log;

public class Logger {

     public void recordBefore(){
            System.out.println("recordBefore");
        }
        
        public void recordAfter(){
            System.out.println("recordAfter");
        }
    
} 

被曾强类UserDaoImpl和被曾强类接口的实现为:

package com.spring.aop.service;

public interface UserDao {
    void addUser();
    void deleteUser();
}

package com.spring.aop.service;

public class UserDaoImpl implements UserDao {

    @Override
    public void addUser() {
        System.out.println("add user ");
    }

    @Override
    public void deleteUser() {
         System.out.println("delete user ");
    }

}

测试方法代码:

package com.spring.aop.main;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.aop.service.UserDao;

public class testAop {

    public static void main(String[] args) {
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("springAop.xml");//BeanDefination的解析注册,代理对象的生成        
            UserDao userDao = (UserDao) applicationContext.getBean("userDao");//可以看到userDao类型是以$Proxy开头的,说明是通过JDK动态代理的方式获取的
            userDao.addUser();//增强行为发生的时刻
    }

}

运行结果:

可以看出对目标方法进行了增强。

下面开始从Spring XML解析进行源码分析

从DefaultBeanDefinitionDocumentReader类的parseBeanDefinitions方法开始进行分析,在parseBeanDefinitions方法分为spring默认标签解析和自定义标签解析,在这里解析标签<aop:config>的时候,使用到了自定义标签解析,代码如下:

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
//此时的handler指的是ConfigBeanDefinitionParser对象
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }

下面进入ConfigBeanDefinitionParser对象的parse方法进行分析:

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        CompositeComponentDefinition compositeDef =
                new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
        parserContext.pushContainingComponent(compositeDef);

        configureAutoProxyCreator(parserContext, element);

        List<Element> childElts = DomUtils.getChildElements(element);
        for (Element elt: childElts) {
            String localName = parserContext.getDelegate().getLocalName(elt);
            if (POINTCUT.equals(localName)) {
                parsePointcut(elt, parserContext);
            }
            else if (ADVISOR.equals(localName)) {
                parseAdvisor(elt, parserContext);
            }
// 在这里解析aspect标签
else if (ASPECT.equals(localName)) { parseAspect(elt, parserContext); } } parserContext.popAndRegisterContainingComponent(); return null; }

 

    private void parseAspect(Element aspectElement, ParserContext parserContext) {
        // 获取aspect标签上面定义的ID
        String aspectId = aspectElement.getAttribute(ID);
        // 获取aspect标签上面引用的增强类 logger
        String aspectName = aspectElement.getAttribute(REF);

        try {
            // 将aspectId和aspectName封装成 AspectEntry对象,并放入栈parseState中
            this.parseState.push(new AspectEntry(aspectId, aspectName));
            //把<aop:before>等通知相关的信息封装到AspectJPointcutAdvisor中,然后放到该集合里
            List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
             //把ref相关的信息如aop.xml中的logger,updateUserMethod等封装到RunTimeBeanReference中,然后放到这个集合中
            List<BeanReference> beanReferences = new ArrayList<BeanReference>();

            List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
            for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
                Element declareParentsElement = declareParents.get(i);
                beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
            }

            // We have to parse "advice" and all the advice kinds in one loop, to get the
            // ordering semantics right.
            NodeList nodeList = aspectElement.getChildNodes();
            boolean adviceFoundAlready = false;
            // 循环判断子节点是否为通知,如果是通知则进行相应的处理
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                if (isAdviceNode(node, parserContext)) {
                    if (!adviceFoundAlready) {
                        // adviceFoundAlready 保证只是放入一次引用
                        adviceFoundAlready = true;
                        if (!StringUtils.hasText(aspectName)) {
                            parserContext.getReaderContext().error(
                                    "<aspect> tag needs aspect bean reference via \'ref\' attribute when declaring advices.",
                                    aspectElement, this.parseState.snapshot());
                            return;
                        }
                        beanReferences.add(new RuntimeBeanReference(aspectName));
                    }
                    // 把通知相关信息封装到AspectJPointcutAdvisor这个类中,同时封装ref信息然后放到BeanReferences中
                    AbstractBeanDefinition advisorDefinition = parseAdvice(
                            aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
                    beanDefinitions.add(advisorDefinition);
                }
            }
            //把切面信息和通知信息封装到这个类中 
            AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
                    aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
            parserContext.pushContainingComponent(aspectComponentDefinition);

            List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
            for (Element pointcutElement : pointcuts) {
                // 解析具体的切入点
                parsePointcut(pointcutElement, parserContext);
            }

            parserContext.popAndRegisterContainingComponent();
        }
        finally {
            this.parseState.pop();
        }
    }

 


最终是将<aop:config>配置的相关信息封装成类,然后放入到containingComponents栈中,方便以后进行操作

以上是关于springAOP源码分析的主要内容,如果未能解决你的问题,请参考以下文章

SpringAOP使用及源码分析(SpringBoot下)

SpringAOP使用及源码分析(SpringBoot下)

springAOP源码分析之篇四:适配器模式

SpringAOP源码之 --- 增强

浅谈SpringAOP功能源码执行逻辑

Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段