zoukankan      html  css  js  c++  java
  • 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栈中,方便以后进行操作

  • 相关阅读:
    算法与数据结构(1):基础部分——以插入排序为例
    软件工程结对作业
    软件工程第1次作业
    软件工程第0次作业
    python爬虫随笔(2)—启动爬虫与xpath
    python爬虫随笔-scrapy框架(1)——scrapy框架的安装和结构介绍
    【面试题】String类、包装类的不可变性
    【面试题】Java类初始化和实例初始化的顺序
    【面试题】Java单例设计模式-饿汉式枚举(enum)单例
    【面试题】从JVM的角度去理解i++和++i
  • 原文地址:https://www.cnblogs.com/histlyb/p/9483017.html
Copyright © 2011-2022 走看看