[Spring source code analysis] AOP source code analysis (Part 1)

foreword

In order to explore the principle of AOP implementation, first define a Dao interface:

public interface Dao {
    
    public void select();

    public void insert();
    
}

The implementation class DaoImpl of the Dao interface:

public class DaoImpl implements Dao {

    @Override
    public void select() {
        System.out.println("Enter DaoImpl.select()");
    }

    @Override
    public void insert() {
        System.out.println("Enter DaoImpl.insert()");
    }
    
}

Define a TimeHandler to print the time before and after the method call. In AOP, this plays the role of cross-cutting concerns:

public class TimeHandler {

   public void printTime() {
       System.out.println("CurrentTime:" + System.currentTimeMillis());
   }
   
}

Define an XML file aop.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="daoImpl" class="org.xrq.action.aop.DaoImpl" />
    <bean id="timeHandler" class="org.xrq.action.aop.TimeHandler" />

    <aop:config proxy-target-class="true">
        <aop:aspect id="time" ref="timeHandler">
            <aop:pointcut id="addAllMethod" expression="execution(* org.xrq.action.aop.Dao.*(..))" />
            <aop:before method="printTime" pointcut-ref="addAllMethod" />
            <aop:after method="printTime" pointcut-ref="addAllMethod" />
        </aop:aspect>
    </aop:config>
    
</beans>

Write a test code TestAop.java:

public class TestAop {

    @Test
    public void testAop() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring/aop.xml");
        
        Dao dao = (Dao)ac.getBean("daoImpl");
        dao.select();
    }
    
}

The results of the code running will not be seen. With the above content, we can follow the code to see how Spring implements AOP.

AOP implementation principle----find the source of Spring processing AOP

A big reason why many friends are reluctant to look at the AOP source code is because they can't find where the entry to the AOP source code implementation is. This is indeed the case. However, we can look at the above test code, whether it is ordinary Bean or AOP, the Bean is obtained through the getBean method in the end and the method is called. The object after getBean has been printed before and after the TimeHandler class printTime() method. content, it is conceivable that they have been processed by the Spring container.

In this case, it is nothing more than two places to deal with:

  1. There should be special handling when loading bean definitions
  2. There should be special processing when getBean. Therefore, this article focuses on [1. There should be special processing when loading Bean definitions], and first find out where Spring does special processing for AOP. The code directly locates the parseBeanDefinitions method of DefaultBeanDefinitionDocumentReader:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    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 {
        delegate.parseCustomElement(root);
    }
}

Normally, when the two tags <bean id="daoImpl"...> and <bean id="timeHandler"...> are encountered, the code in line 9 will be executed, because the <bean> tag is Default Namespace. But it is different when encountering the following aop:config tag, aop:config is not the default Namespace, so the code on line 12 will be executed, take a look:

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;
    }
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

Because the entire XML was parsed into org.w3c.dom.Document before, org.w3c.dom.Document represents the entire XML in the form of a tree, and each node is a Node.

First, the second line gets Namespace="http://www.springframework.org/schema/aop" from the Node aop:config (parameter Element is a sub-interface of the Node interface), and the code in line 3 is obtained according to this Namespace The corresponding NamespaceHandler is the Namespace handler. Specifically, the NamespaceHandler of the aop Namespace is the org.springframework.aop.config.AopNamespaceHandler class, which is the result obtained by the third line of code. Specifically in AopNamespaceHandler, there are several Parser, which are used for specific label conversion, namely:

  • config-->ConfigBeanDefinitionParser
  • aspectj-autoproxy-->AspectJAutoProxyBeanDefinitionParser
  • scoped-proxy-->ScopedProxyBeanDefinitionDecorator
  • spring-configured-->SpringConfiguredBeanDefinitionParser Next, the code in line 8 uses the parse method of AopNamespaceHandler to parse the content under aop:config .

AOP Bean definition loading----convert aop:before and aop:after into a RootBeanDefinition named adviceDef according to the weaving method

After the above analysis, it has been found that Spring is AOP processed by AopNamespaceHandler, then enter the source code of the parse method of AopNamespaceHandler:

public BeanDefinition parse(Element element, ParserContext parserContext) {
     return findParserForElement(element, parserContext).parse(element, parserContext);
 }

First get the specific Parser, because the current node is aop:config , the last part has columns, config is processed by ConfigBeanDefinitionParser, so findParserForElement(element, parserContext) This part of the code gets ConfigBeanDefinitionParser, then look at the parse method of ConfigBeanDefinitionParser :

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);
        }
        else if (ASPECT.equals(localName)) {
            parseAspect(elt, parserContext);
        }
    }

    parserContext.popAndRegisterContainingComponent();
    return null;
}

The key point is to mention the code on line 6. The specific implementation of this line of code is not followed, but it is very important. Let me say a few words about the function of the configureAutoProxyCreator method:

  • A Bean definition whose BeanName is org.springframework.aop.config.internalAutoProxyCreator is registered with the Spring container, which can be customized or provided by Spring (according to priority)
  • Spring provides org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator by default, which is the core class of AOP and will be explained in the next article
  • In this method, whether to use CGLIB for proxying and whether to expose the final proxy is also set according to the configuration proxy-target-class and expose-proxy.

The node under aop:config is aop:aspect . I think it must be the code parseAspect on line 18, and follow up:

private void parseAspect(Element aspectElement, ParserContext parserContext) {
    String aspectId = aspectElement.getAttribute(ID);
    String aspectName = aspectElement.getAttribute(REF);

    try {
        this.parseState.push(new AspectEntry(aspectId, aspectName));
        List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
        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 = 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));
                }
                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();
    }
}

Focus on this method from the loop on lines 20 to 37. A key judgment of this for loop is the ifAdviceNode judgment on line 22. Let's see what the ifAdviceNode method does:

private boolean isAdviceNode(Node aNode, ParserContext parserContext) {
    if (!(aNode instanceof Element)) {
        return false;
    }
    else {
        String name = parserContext.getDelegate().getLocalName(aNode);
        return (BEFORE.equals(name) || AFTER.equals(name) || AFTER_RETURNING_ELEMENT.equals(name) ||
                AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name));
    }
}

That is, this for loop is only used to process aop :before , aop:after , aop:after-returning , <aop:after-throwing method="">, <aop:around method=""> under the aop :aspect tag. five tabs.

Next, if it is one of the above five tags, then enter the parseAdvice method on lines 33 to 34:

private AbstractBeanDefinition parseAdvice(
        String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,
        List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {
 5     try {
        this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));
 8         // create the method factory bean
        RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
        methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
        methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
        methodDefinition.setSynthetic(true);
14         // create instance factory definition
        RootBeanDefinition aspectFactoryDef =
                new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
        aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
        aspectFactoryDef.setSynthetic(true);

        // register the pointcut
        AbstractBeanDefinition adviceDef = createAdviceDefinition(
                adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
                beanDefinitions, beanReferences);

        // configure the advisor
        RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
        advisorDefinition.setSource(parserContext.extractSource(adviceElement));
        advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
        if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
            advisorDefinition.getPropertyValues().add(
                    ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
        }

        // register the final advisor
        parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);

        return advisorDefinition;
    }
    finally {
        this.parseState.pop();
    }
}

The method mainly does three things:

  1. Create RootBeanDefinition according to the weaving method (before, after), named adviceDef, which is advice definition
  2. Write the RootBeanDefinition created in the previous step into a new RootBeanDefinition and construct a new object named advisorDefinition, which is the advisor definition
  3. Register advisorDefinition to DefaultListableBeanFactory

Let's look at the first thing to do in the createAdviceDefinition method definition:

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);

    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;
}

First of all, you can see that the created AbstractBeanDefinition instance is RootBeanDefinition, which is different from the GenericBeanDefinition instance created by ordinary bean. Then go to the getAdviceClass method on line 6 and take a look:

private Class getAdviceClass(Element adviceElement, ParserContext parserContext) {
    String elementName = parserContext.getDelegate().getLocalName(adviceElement);
    if (BEFORE.equals(elementName)) {
        return AspectJMethodBeforeAdvice.class;
    }
    else if (AFTER.equals(elementName)) {
        return AspectJAfterAdvice.class;
    }
    else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {
        return AspectJAfterReturningAdvice.class;
    }
    else if (AFTER_THROWING_ELEMENT.equals(elementName)) {
        return AspectJAfterThrowingAdvice.class;
    }
    else if (AROUND.equals(elementName)) {
        return AspectJAroundAdvice.class;
    }
    else {
        throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");
    }
}

Since a bean definition is created, it must correspond to a specific class in the bean definition, and different entry methods correspond to different classes:

  • before对应AspectJMethodBeforeAdvice
  • After corresponds to AspectJAfterAdvice
  • after-returning对应AspectJAfterReturningAdvice
  • after-throwing对应AspectJAfterThrowingAdvice
  • around corresponds to AspectJAroundAdvice

The remaining logic of the createAdviceDefinition method is nothing but to judge the attributes in the tag and set the corresponding value. So far , the AbstractBeanDefinition corresponding to the aop:before and aop:after tags has been created.

AOP Bean definition loading----convert the RootBeanDefinition named adviceDef into a RootBeanDefinition named advisorDefinition

Let's take a look at the operation of the second step, convert the RootBeanD named adviceDef into a RootBeanDefinition named advisorDefinition, and follow the code from lines 26 to 32 of the parseAdvice method of the ConfigBeanDefinitionParser class above:

RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
advisorDefinition.setSource(parserContext.extractSource(adviceElement));
advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
    advisorDefinition.getPropertyValues().add(
            ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
}

This is equivalent to wrapping the RootBeanDefinition generated in the previous step, and creating a new RootBeanDefinition. The Class type is org.springframework.aop.aspectj.AspectJPointcutAdvisor.

The code from lines 4 to 7 is used to determine whether there is an "order" attribute in the aop:aspect tag, and if so, set it. The "order" attribute is used to control the priority of the cut-in method.

AOP Bean definition loading----register BeanDefinition in DefaultListableBeanFactory

The last step is to register the BeanDefinition in the DefaultListableBeanFactory. The code is the last part of the parseAdvice method of the previous ConfigBeanDefinitionParser:

...
 // register the final advisor
 parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
 ...

Follow the implementation of the registerWithGeneratedName method:

 public String registerWithGeneratedName(BeanDefinition beanDefinition) {
     String generatedName = generateBeanName(beanDefinition);
     getRegistry().registerBeanDefinition(generatedName, beanDefinition);
     return generatedName;
 }

The second line gets the registered name BeanName, which is similar to the registration of <bean>, using the method of Class full path + "#" + global counter, where the Class full path is org.springframework.aop.aspectj.AspectJPointcutAdvisor, followed by By analogy, each BeanName should be org.springframework.aop.aspectj.AspectJPointcutAdvisor#0, org.springframework.aop.aspectj.AspectJPointcutAdvisor#1, org.springframework.aop.aspectj.AspectJPointcutAdvisor#2 and so on.

Line 3 is registered in DefaultListableBeanFactory, BeanName already exists, and the rest is Bean definition. The parsing process of Bean definition has been seen before, so I won't talk about it.

AOP Bean definition loading----AopNamespaceHandler handles the aop:pointcut process

Back to the parseAspect method of ConfigBeanDefinitionParser:

private void parseAspect(Element aspectElement, ParserContext parserContext) {
    
        ...   

        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();
    }
}

The ellipsis part indicates that the tags aop:before and aop:after are parsed. The above part has already been said, so I won't say it. Let's take a look at the source code for parsing the aop:pointcut part.

The code from lines 5 to 7 builds an Aspect label component definition, and pushes the Apsect label component definition to the ParseContext, that is, the parsing tool context. This part of the code is not critical.

The code in line 9 gets all the pointcut tags under aop:aspect , traverses them, and processes them by the parsePointcut method:

private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
    String id = pointcutElement.getAttribute(ID);
    String expression = pointcutElement.getAttribute(EXPRESSION);

    AbstractBeanDefinition pointcutDefinition = null;
        
    try {
        this.parseState.push(new PointcutEntry(id));
        pointcutDefinition = createPointcutDefinition(expression);
        pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));

        String pointcutBeanName = id;
        if (StringUtils.hasText(pointcutBeanName)) {
            parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
        }
        else {
            pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
        }

        parserContext.registerComponent(
                new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
    }
    finally {
        this.parseState.pop();
    }

    return pointcutDefinition;
}

The code from lines 2 to 3 obtains the "id" attribute and the "expression" attribute under the aop:pointcut tag.

The code on line 8 pushes a PointcutEntry indicating that the current Spring context is resolving the Pointcut tag.

The code on line 9 creates the bean definition of Pointcut, and then we will look at the other methods first.

The code on line 10 ignores it, and finally obtains the Source from the extractSource method of NullSourceExtractor, which is a null.

The code from lines 12 to 18 is used to register the acquired bean definition. The default pointcutBeanName is the id attribute defined in the aop:pointcut tag:

If the id attribute is configured in the aop:pointcut tag, the code from lines 13 to 15 is executed. pointcutBeanName=id If the id attribute is not configured in the aop:pointcut tag, the code from lines 16 to 18 is executed. , the same rule as the bean does not configure the id attribute, pointcutBeanName=org.springframework.aop.aspectj.AspectJExpressionPointcut# serial number (accumulates from 0) The code from lines 20 to 21 registers a Pointcut component definition in the context of the parsing tool

In the code from lines 23 to 25, after the aop:pointcut tag is parsed, the finally block pops the PointcutEntry previously pushed to the top of the stack, indicating that the aop:pointcut tag is parsed this time.

Finally, let's go back to the implementation of the 9th line of code createPointcutDefinition, which is relatively simple:

protected AbstractBeanDefinition createPointcutDefinition(String expression) {
    RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);
    beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
    beanDefinition.setSynthetic(true);
    beanDefinition.getPropertyValues().add(EXPRESSION, expression);
    return beanDefinition;
}

The key is to pay attention to two points:

The parsed BeanDefinition corresponding to the aop:pointcut tag is the RootBeanDefinition, and the Class in the RootBenaDefinitoin is org.springframework.aop.aspectj.AspectJExpressionPointcut The Bean corresponding to the aop:pointcut tag is the prototype, which is the prototype. After this process, the aop:pointcut is parsed The content in the tag is converted into a RootBeanDefintion and stored in the Spring container.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325026451&siteId=291194637