首页 > 编程语言 >【Spring AOP】【二】Spring AOP源码解析-XML方式加载解析过程

【Spring AOP】【二】Spring AOP源码解析-XML方式加载解析过程

时间:2023-02-19 21:45:09浏览次数:43  
标签:parserContext Spring Element AOP new 解析 public

1  前言

这篇我们看一下,我们的AOP代码是怎么被Spring加载的进去的,那么分两种一种是XML配置的,一种就是我们常用的注解,我们从源码先看下XML方式的都是怎么被加载解析的。

2  代码准备


<context:component-scan base-package="com.virtuous.demo.spring.cycle"/>
<bean id="A" class="com.virtuous.demo.spring.cycle.A" />
<!--通知-->
<bean id="logAspect" class="com.virtuous.demo.spring.LogAspect"/>
<!--aop配置-->
<aop:config proxy-target-class="true">
    <aop:aspect ref="logAspect" order="1">
        <aop:pointcut id="logAspectPointcut" expression="execution( * com.virtuous.demo.spring.cycle.A.*(..))"/>
        <aop:before method="before" pointcut-ref="logAspectPointcut"/>
        <aop:after method="after" pointcut-ref="logAspectPointcut"/>
        <aop:around method="around" pointcut-ref="logAspectPointcut"/>
        <aop:after-returning method="afterReturning" pointcut-ref="logAspectPointcut"/>
        <aop:after-throwing method="afterThrowing" pointcut-ref="logAspectPointcut"/>
     </aop:aspect>
</aop:config>
/**
 * @author kuku
 */
public class LogAspect {

    public void before(JoinPoint point) {
        System.out.println("before");
    }

    public void after(JoinPoint point) {
        System.out.println("after");
    }

    public void afterReturning(JoinPoint point) {
        System.out.println("afterReturning");
    }

    public void afterThrowing(JoinPoint point) {
        System.out.println("afterThrowing");
    }

    public void around(ProceedingJoinPoint joinPoint)  throws Throwable {
        System.out.println("around before");
        joinPoint.proceed();
        System.out.println("around after");
    }
}
@Component
public class A {

    public void say() {
        System.out.println("111");
    }

}
// 测试类
public class ApplicationContextTest {
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testFactoryBean() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring/spring-alias.xml");
        A a = applicationContext.getBean("a", A.class);
        a.say();
    }
}

3  源码分析

(1)Spring对于AOP在XML的配置采用的是自定义标签解析的形式,入口在DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法中。每种自定义标签都有自己的Handler来处理。

// 解析XML
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 解析默认的名称空间 比如bean if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { // 解析自定义的名称空间 比如AOP的解析 delegate.parseCustomElement(root); } }
// 解析自定义标签
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 1、获取namespaceUri
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    }
    // 2、根据namespaceUri得到命名空间解析器
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    // 3、使用命名空间解析器解析自定义标签
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

(2)针对AOP的解析,对应的Handler为AopNamespaceHandler

public class AopNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // In 2.0 XSD as well as in 2.5+ XSDs
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

        // Only in 2.0 XSD: moved to context namespace in 2.5+
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }

}

(3)ConfigBeanDefinitionParser解析aop:config

public BeanDefinition parse(Element element, ParserContext parserContext) {
    CompositeComponentDefinition compositeDef =
            new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
    parserContext.pushContainingComponent(compositeDef);
    // 注册AspectJAutoProxyCreator
    configureAutoProxyCreator(parserContext, element);
    List<Element> childElts = DomUtils.getChildElements(element);
    for (Element elt: childElts) {
        String localName = parserContext.getDelegate().getLocalName(elt);
        // 解析aop:pointcut
        if (POINTCUT.equals(localName)) {
            parsePointcut(elt, parserContext);
        }
        // 解析aop:advisor
        else if (ADVISOR.equals(localName)) {
            parseAdvisor(elt, parserContext);
        }
        // 解析aop:config
        else if (ASPECT.equals(localName)) {
            parseAspect(elt, parserContext);
        }
    }
    parserContext.popAndRegisterContainingComponent();
    return null;
}

(4)passeAspect,并获取下边的通知,并进行一一创建

private void parseAspect(Element aspectElement, ParserContext parserContext) {
    // 解析 id
    String aspectId = aspectElement.getAttribute(ID);
    // 解析ref
    String aspectName = aspectElement.getAttribute(REF);
    try {
        this.parseState.push(new AspectEntry(aspectId, aspectName));
        List<BeanDefinition> beanDefinitions = new ArrayList<>();
        List<BeanReference> beanReferences = new ArrayList<>();
        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
        // 解析子元素advice 比如
        NodeList nodeList = aspectElement.getChildNodes();
        boolean adviceFoundAlready = false;
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            // 判断是不是通知的标签 也就是是不是那5个通知 before after around after-returning after-throwing
            if (isAdviceNode(node, parserContext)) {
                if (!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));
                }
                // 给5个通知创建对应的BeanDefinition
                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);
     // 解析切点 也是作为BeanDefinition List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT); for (Element pointcutElement : pointcuts) { parsePointcut(pointcutElement, parserContext); } parserContext.popAndRegisterContainingComponent(); } finally { this.parseState.pop(); } }
// 创建通知的BeanDefinition
private AbstractBeanDefinition createAdviceDefinition(
        Element adviceElement, ParserContext parserContext, String aspectName, int order,
        RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
        List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
    RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
    adviceDefinition.setSource(parserContext.extractSource(adviceElement));
    adviceDefinition.getPropertyValues().add(ASPECT_NAME_PROPERTY, aspectName);
    adviceDefinition.getPropertyValues().add(DECLARATION_ORDER_PROPERTY, order);
    if (adviceElement.hasAttribute(RETURNING)) {
        adviceDefinition.getPropertyValues().add(
                RETURNING_PROPERTY, adviceElement.getAttribute(RETURNING));
    }
    if (adviceElement.hasAttribute(THROWING)) {
        adviceDefinition.getPropertyValues().add(
                THROWING_PROPERTY, adviceElement.getAttribute(THROWING));
    }
    if (adviceElement.hasAttribute(ARG_NAMES)) {
        adviceDefinition.getPropertyValues().add(
                ARG_NAMES_PROPERTY, adviceElement.getAttribute(ARG_NAMES));
    }
    ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
    cav.addIndexedArgumentValue(METHOD_INDEX, methodDef);
    // !!!这点很关键  就是解析通知的切点pointCut 这样切点就和通知关联上了
    Object pointcut = parsePointcutProperty(adviceElement, parserContext);
    if (pointcut instanceof BeanDefinition) {
        cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
        beanDefinitions.add((BeanDefinition) pointcut);
    }
    else if (pointcut instanceof String) {
        RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
        cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
        beanReferences.add(pointcutRef);
    }
    cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);
    return adviceDefinition;
}

切点的创建和通知差不多这里就不展示了,这样我们的AOP的配置就都生成了BeanDefinition,我们来总结一下执行流程:

  1. XML中的AOP相关的名称空间,由AopNamespaceHandler来负则解析
  2. config标签的内容由ConfigBeanDefinitionParser来解析内容
  3. 解析aspect标签的内容
  4. 遍历每个通知以及切点进行创建对应的BeanDefinition并注册到Bean工厂(有一点需要注意的就是创建通知的时候会解析标签上的pointCut并关联,这样切点和通知有了联系)

4  小结

我们本文讲解了XML方式配置的AOP是如何被加载进Spring中的,主要就是对我们的配置进行解析,封装成BeanDefinition放进Bean工厂,下文我们讲解注解方式的解析过程。

 

标签:parserContext,Spring,Element,AOP,new,解析,public
From: https://www.cnblogs.com/kukuxjx/p/17135425.html

相关文章

  • Spring-IOC、AOP、事务的说明
    今天来聊一聊我对spring框架的认识,本篇章中不详细讲解具体的使用方法和实现一、spring是什么?spring是一个java语言下的bean管理的基础框架。二、spring的常用功能有那......
  • SpringBoot升级到3.0
    SpringBoot3.0出来有一段时间了,一直没时间来整理,这次来看一下吧。SpringBoot可以轻松创建独立的、生产级的基于Spring的应用程序,您可以“直接运行”。1.SpringBoo......
  • 【查找算法】解析学习四大常用的计算机查找算法 | C++
    第二十二章四大查找算法:::hljs-center目录第二十二章四大查找算法●前言●查找算法●一、顺序查找法1.什么是顺序查找法?2.案例实现●二、二分查找法1......
  • Vue 的生命周期 详细解析(使用场景等)
    Vue生命周期图:  一、生命周期图的解读newVue():首先需要创建一个Vue的实例对象InitEvents&Lifecycle:初始化:生命周期、事件(如:v-once),但数据代理还未开始(vm._d......
  • springboot mybatis (关联关系:一对一,一对多,多对多)
    例如这个学生选课的这个:这个里面课程跟老师是1对1的,而老师对课程是1对多的,一个课程只能由一个老师教,而一个老师可以教多个课程对于学生和课程之间是多对多的,一个学生可以......
  • 爬虫利用bs4解析练习demo
    同样也是爬取新闻页的简要信息importrequestsfrombs4importBeautifulSoupBase_url="https://news.cnblogs.com"Base_path="/n/page/"headers={"User-......
  • mapper-spring-boot-starter的使用
    <?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:s......
  • 爬虫利用Xpath解析练习demo
    爬取新闻页的简要信息importrequestsfromlxmlimportetreefromlxml.etreeimport_ElementBase_url="https://news.cnblogs.com"Base_path="/n/page/"heade......
  • Spring Boot
    SpringBoot​ SpringBoot是一个快速开发框架,可以迅速搭建一套基于Spring框架体系的应用,是SpringCloud的基础。​ javaConfiguration用java类替代xml的配置方式。​ ......
  • XXL-JOB 分布式任务调度框架(Cron表达式、环境搭建、整合SpringBoot、广播任务与动态分
    (目录)xxl-Job分布式任务调度1.概述1.1什么是任务调度我们可以先思考一下业务场景的解决方案:某电商系统需要在每天上午10点,下午3点,晚上8点发放一批优惠券。某银行......