Preface
Six articles have been written before to analyze the Spring Bean loading process in detail. After this part is finished, we will enter a more difficult part, that is, the principle analysis of AOP implementation. To explore the principle of AOP implementation, we first define several classes, a Dao interface:
public interface Dao {
public void select();
public void insert();
}
The implementation class of the Dao interface, DaoImpl:
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 for printing time before and after method calls, which plays the role of crosscutting concerns in AOP:
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 result of code running is not to be seen. With the above content, we can follow the code to see how Spring implements AOP.
Principle of AOP Implementation: Finding the Source of Spring Processing AOP
A big reason many friends don't want to see AOP source code is that they can't find the entrance to the implementation of AOP source code. That's true. But let's look at the test code above. Ordinary beans or AOP are used to get beans and call methods. The objects after get Bean s have printed out the contents of the TimeHandler class printTime() method. You can see that they have been processed by Spring container. .
In that case, there are only two places to deal with:
There should be special handling when loading Bean definitions
The getBean should be handled in a special way
Therefore, this article focuses on [1. There should be special processing when loading Bean definitions], first find out where Spring has done special processing for AOP. Code is located directly to the parseBean Definitions method of DefaultBean Definition Document Reader:
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 you encounter these two tags, you execute line 9, because the tag is the default Namespace. But when you encounter the following tag, it's different. It's not the default Namespace, so you'll execute line 12. Let's see:
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 before, the whole XML was parsed into org.w3c.dom.Document. org.w3c.dom.Document represents the whole XML in the form of a tree, specifically to each node is a Node.
First, line 2 gets Namespace= "http://www.spring framework.org/schema/aop" from this Node (parameter Element is a sub-interface of Node interface). Line 3 gets the corresponding Namespace Handler, the Namespace Handler, from this Namespace. Specifically, the Namespace Handler of AOP is org.spring framework. The. aop. config. Aop NamespaceHandler class is the result of line 3. Specifically in AopNamespace Handler, there are several Parser s for specific label conversion, which are:
config–>ConfigBeanDefinitionParser
aspectj-autoproxy–>AspectJAutoProxyBeanDefinitionParser
scoped-proxy–>ScopedProxyBeanDefinitionDecorator
spring-configured–>SpringConfiguredBeanDefinitionParser
Next, the code in line 8 parses the content using the parse method of AopNamespace Handler.
Analytical Enhancer advisor
AOP Bean Definition Loading - Converts and translates into a RootBean Definition called adviceDef according to the weaving method
After the above analysis, we have found that Spring is AOP processed by AopNamespace Handler, and then enter the parse method source code of AopNamespace Handler:
public BeanDefinition parse(Element element, ParserContext parserContext) { return findParserForElement(element, parserContext).parse(element, parserContext); }
First get the specific Parser, because the current node is the last column in the previous part, and config is handled by ConfigBean Definition Parser, so the code of findParser ForElement (element, parserContext) gets the ConfigBean Definition Parser, and then look at the parse method of ConfigBean Definition Parser. :
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; }
First of all, I will mention the code of line 6. The implementation of this line of code does not follow, but it is very important. The function of the configureAutoProxyCreator method can be described in a few words.
Register a Bean Name definition for the org. spring framework. aop. config. internalAutoProxyCreator to the Spring container, which can be customized or used (according to priority)
Spring defaults to the org. spring framework. aop. aspectj. autoproxy. AspectJAware Advisor AutoProxyCreator, which is the core class of AOP, and will be explained in the next section.
In this method, proxy-target-class and expose-proxy are also configured to determine whether CGLIB is used for proxy and whether the final proxy is exposed.
The next node is to execute the parseAspect code in 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 line 20 through line 37. A key judgment of this for loop is the ifAdviceNode judgment in line 22. 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 to say, the for loop is only used to deal with the five tags under the tag.
Then, if it is one of the five tags mentioned above, enter the parseAdvice method in lines 33 to 34:
private AbstractBeanDefinition parseAdvice( String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext, List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) { try { this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement))); // 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); // 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:
Create RootBean Definition based on weaving (before, after, etc.), called adviceDef, or adviceDefinition
Write the RootBean Definition created in the previous step into a new RootBean Definition and construct a new object called advisorDefinition, which is the advisor definition.
Register advisor Definition into DefaultListable BeanFactory
The first thing to do is to define the createAdviceDefinition method:
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, you can see that the created AbstractBeanDefinition instance is RootBeanDefinition, which is different from the GenericBeanDefinition instance created by ordinary beans. Then go to the getAdviceClass method in line 6 and see:
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 the Bean definition is created, it is necessary that the Bean definition should correspond to a specific Class, and different ways of entry should correspond to different Classes:
before corresponds to AspectJ Method BeforeAdvice
After corresponds to AspectJAfterAdvice
after-returning corresponds to AspectJAfter Returning Advice
after-throwing corresponds to AspectJAfter Throwing Advice
around corresponds to AspectJAroundAdvice
The remaining logic of the createAdviceDefinition method is nothing more than judging the attributes in the tag and setting the corresponding values. So far, the AbstractBeanDefinition corresponding to the two tags has been created.
AOP Bean Definition Loading - Converts the RootBean Definition named adviceDef inition to a RootBean Definition named advisorDefinition
Let's take a look at the second step, converting RootBeanD named adviceDef inition into RootBeanDefinition named advisorDefinition, and follow the code in lines 26 to 32 of the above section of the ConfigBeanDefinitionParser class parseAdvice method:
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 RootBean Definition generated in the previous step, and a new RootBean Definition comes out. The Class type is org. spring framework. aop. aspectj. AspectJPointcutAdvisor.
Lines 4 to 7 are used to determine whether there is an "order" attribute in the tag. If there is one, set the "order" attribute to control the priority of the entry method.
AOP Bean Definition Loading - Register Bean Definition into DefaultListable BeanFactory
The last step is to register BeanDefinition into DefaultListableBeanFactory. The code is the last part of the parseAdvice method of ConfigBeanDefinitionParser before:
...
// register the final advisor
parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);
...
Following the implementation of registerWithGeneratedName method:
public String registerWithGeneratedName(BeanDefinition beanDefinition) { String generatedName = generateBeanName(beanDefinition); getRegistry().registerBeanDefinition(generatedName, beanDefinition); return generatedName; }
Line 2 obtains the registered name BeanName, which is similar to the registration. It uses the way of Class full path +""+global counter, in which the Class full path is org.spring framework.aop.aspectj.AspectJPointcutAdvisor. By analogy, each BeanName should be org.spring framework.aop.aspectj.AspectointcutAdvisor. Isor 0, org. spring framework. aop. aspectj. AspectJPointcutAdvisor 1, org. spring framework. aop. aspectj. AspectJPointcutAdvisor 2 and so on.
Line 3 registers with DefaultListableBeanFactory. BeanName already exists. The rest is Bean Definition. Bean Definition's parsing process has been seen before, let's not say.
Analytical Section Processes
AOP Bean Definition Loading-AopNamespace Handler Processing Flow
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 parsing part is, this kind of label, which has already been mentioned in the previous part. Let's leave it alone. Let's take a look at the source code of the parsing part.
Lines 5 to 7 build an Aspect tag component definition and push the Apsect tag component definition into ParseContext, which is the parsing tool context. This part of the code is not the key.
Line 9 takes all the pointcut tags below, traverses them, and is processed 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; }
Lines 2 to 3 get the "id" and "expression" attributes under the label.
Line 8 pushes a Pointcut Entry to indicate that the current Spring context is parsing the Pointcut tag.
Line 9 creates the Bean definition of Pointcut, and then looks at all the other methods.
The code in line 10, regardless of it, eventually gets the Source from the NullSource Extractor's extractSource method, which is a null.
Lines 12 to 18 are used to register the acquired Bean definition, and the default pointcutBeanName is the id attribute defined in the tag:
If the id attribute is configured in the tag, the code executes lines 13 to 15, pointcutBeanName=id
If the id attribute is not configured in the tag, the code executes lines 16 to 18, the same rule as if the Bean does not configure the id attribute, pointcutBeanName = org. spring framework. aop. aspectj. AspectJExpressionPointcut serial number (cumulative from 0)
Lines 20 to 21 register a Pointcut component definition in the parsing tool context
Lines 23 to 25 of the code, finally block after the tag parsing, so that the previous pushed to the top of the stack Pointcut Entry out of the stack, indicating that the tag parsing is complete.
Finally, let's go back to the implementation of line 9, 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 label corresponding to the parsed BeanDefinition is RootBeanDefinition, and the Class in RootBenaDefinitoin is org. spring framework. aop. aspectj. AspectJExpressionPointcut
The Bean corresponding to the label is prototype, or prototype.
With such a process, the content of the tag is parsed and converted into RootBean Defintion stored in the Spring container.
Time Analysis of AOP Generating Agent for Bean
As mentioned in the previous article, the class org. spring framework. aop. aspectj. autoproxy. AspectJAware Advisor AutoProxy Creator is the core class of AOP provided by Spring to developers. AspectJAware Advisor AutoProxy Creator completes the transformation process of Class/Interface - Agent. First, let's take a look at AspectJAware Advisor AutoProxy C. The hierarchy of reator:
The most notable point here is the box in the bottom left corner. Let me summarize it in a few words.
AspectJAware Advisor AutoProxyCreator is the implementation class of BeanPostProcessor interface
The postProcessBeforeInitialization method and postProcessAfterInitialization method are implemented in the parent AbstractAutoProxyCreator
The postProcessBeforeInitialization method is an empty implementation
Logical code in postProcessAfterInitialization method
Based on the above analysis, the time for beans to generate proxies is clear: after each Bean is initialized, if necessary, call postProcess BeforeInitialization in AspectJAware Advisor AutoProxy Creator to generate proxies for beans.
Instantiation of Proxy Objects - Determining whether Proxy is Generated
The timing of Bean generation agent is analyzed above. After each Bean is initialized, the code is positioned after the Bean is initialized. First, the initializeBean method of AbstractAutowireCapableBeanFactory is initialized.
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { invokeAwareMethods(beanName, bean); return null; } }, getAccessControlContext()); } else { invokeAwareMethods(beanName, bean); } Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }
The applyBeanPostProcessors BeforeInitialization method in line 16 before initialization, and the applyBeanPostProcessors AfterInitialization method in line 29 after initialization:
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { result = beanProcessor.postProcessAfterInitialization(result, beanName); if (result == null) { return result; } } return result; }
The postProcessBeforeInitialization method for each BeanPostProcessor is invoked here. According to the previous analysis, take a look at the implementation of AbstractAutoProxyCreator's postProcessAfterInitialization method:
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.contains(cacheKey)) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
Follow the method wrapIfNecessary in line 5:
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (this.targetSourcedBeans.contains(beanName)) { return bean; } if (this.nonAdvisedBeans.contains(cacheKey)) { return bean; } if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.nonAdvisedBeans.add(cacheKey); return bean; } // Create proxy if we have advice. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.add(cacheKey); Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.nonAdvisedBeans.add(cacheKey); return bean; }
Lines 2 to 11 are scenario judgements that do not need to generate agents, which are skipped here. The first question we need to consider is: Which target objects need to generate agents? Because there are many beans in the configuration file, you can't generate proxies for every Bean, so you need a set of rules to determine whether a Bean needs to generate proxies, which is line 14 of the code getAdvices AndAdvisors ForBean:
protected List<Advisor> findEligibleAdvisors(Class beanClass, String beanName) { List<Advisor> candidateAdvisors = findCandidateAdvisors(); List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); extendAdvisors(eligibleAdvisors); if (!eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors); } return eligibleAdvisors; }
As the name implies, the method means to find the appropriate Advisor for the specified class.
Line 2, looking for candidate Advisors, according to the configuration file above, there are two candidate Advisors, one under the node and the other two, which have been transformed into RootBean Definition when parsing XML.
Skip line 3 and look at the code extension Advisors method in line 4 before focusing on line 3. The code extension Advisors method in line 4 adds an org. spring framework. aop. support. DefaultPointcut Advisor to the beginning of the candidate Advisor chain (that is, the location of List.get(0).
Line 3, according to the candidate Advisors, find the Advisor that can be used, follow the method to achieve:
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) { if (candidateAdvisors.isEmpty()) { return candidateAdvisors; } List<Advisor> eligibleAdvisors = new LinkedList<Advisor>(); for (Advisor candidate : candidateAdvisors) { if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) { eligibleAdvisors.add(candidate); } } boolean hasIntroductions = !eligibleAdvisors.isEmpty(); for (Advisor candidate : candidateAdvisors) { if (candidate instanceof IntroductionAdvisor) { // already processed continue; } if (canApply(candidate, clazz, hasIntroductions)) { eligibleAdvisors.add(candidate); } } return eligibleAdvisors; }
The main judgment of the whole method revolves around the canApply expansion method:
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) { if (advisor instanceof IntroductionAdvisor) { return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass); } else if (advisor instanceof PointcutAdvisor) { PointcutAdvisor pca = (PointcutAdvisor) advisor; return canApply(pca.getPointcut(), targetClass, hasIntroductions); } else { // It doesn't have a pointcut so we assume it applies. return true; } }
The actual type of the first parameter advisor is AspectJPointcutAdvisor, which is a subclass of PointcutAdvisor, so the method in line 7 is executed:
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) { if (!pc.getClassFilter().matches(targetClass)) { return false; } MethodMatcher methodMatcher = pc.getMethodMatcher(); IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; if (methodMatcher instanceof IntroductionAwareMethodMatcher) { introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; } Set<Class> classes = new HashSet<Class>(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); classes.add(targetClass); for (Class<?> clazz : classes) { Method[] methods = clazz.getMethods(); for (Method method : methods) { if ((introductionAwareMethodMatcher != null && introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) || methodMatcher.matches(method, targetClass)) { return true; } } } return false; }
In fact, this method is to make two judgments on the expression corresponding to the current Advisor:
The target class must satisfy the expression matching rule
The method in the target class must satisfy the expression matching rule. Of course, not all the methods need to satisfy the expression matching rule, only one method can satisfy the expression matching rule.
If both of the above are satisfied, then the container will judge the condition and need to be generated proxy object by returning an array object, which stores the corresponding Advisor.
Instance process of proxy object
Instantiation of Proxy Objects - Organizing the Context for Proxy Code Generation
After analyzing the conditions for generating agents above, let's take a formal look at how the Spring context can be used to generate agents. Back to the wrapIfNecessary method of AbstractAutoProxyCreator:
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (this.targetSourcedBeans.contains(beanName)) { return bean; } if (this.nonAdvisedBeans.contains(cacheKey)) { return bean; } if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.nonAdvisedBeans.add(cacheKey); return bean; } // Create proxy if we have advice. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.add(cacheKey); Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.nonAdvisedBeans.add(cacheKey); return bean; }
Line 14 gets the corresponding Advisor array. Line 15 determines that as long as the Advisor array is not empty, the agent will be created through line 17:
protected Object createProxy( Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { ProxyFactory proxyFactory = new ProxyFactory(); // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig. proxyFactory.copyFrom(this); if (!shouldProxyTargetClass(beanClass, beanName)) { // Must allow for introductions; can't just set interfaces to // the target's interfaces only. Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, this.proxyClassLoader); for (Class<?> targetInterface : targetInterfaces) { proxyFactory.addInterface(targetInterface); } } Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); for (Advisor advisor : advisors) { proxyFactory.addAdvisor(advisor); } proxyFactory.setTargetSource(targetSource); customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } return proxyFactory.getProxy(this.proxyClassLoader); }
Lines 4 to 6 new presents a ProxyFactory, Proxy, which, as the name implies, proxy factory, provides a simple way to use code to obtain and configure AOP proxies.
Line 8 makes a judgment that proxy-target-class= "false" or that proxy-target-class is not configured in this node, that is, CGLIB is not used to generate proxies. If the condition is satisfied, make a judgment, get all the interfaces implemented by the current Bean, and say that these interface Class objects are added to the ProxyFactory.
Lines 17 to 28 are not necessary to look at, just add some parameters to ProxyFactory. Focus on line 30, proxyFactory.getProxy(this.proxyClassLoader):
public Object getProxy(ClassLoader classLoader) { return createAopProxy().getProxy(classLoader); }
Implementing the code is one line, but it clearly tells us that we did two things:
Creating AopProxy Interface Implementation Class
Getting the corresponding proxy through the getProxy method of the implementation class of the AopProxy interface
Starting from these two points, it is analyzed in two parts.
Proxy Object Instantiation - Creating AopProxy Interface Implementation Class
Take a look at the implementation of the createAopProxy() method, which is located in the DefaultAopProxyFactory class:
protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } return getAopProxyFactory().createAopProxy(this); }
There's no need to look at the previous section. Go straight to the point: the createAopProxy method:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } if (!cglibAvailable) { throw new AopConfigException( "Cannot proxy target class because CGLIB2 is not available. " + "Add CGLIB to the class path or specify proxy interfaces."); } return CglibProxyFactory.createCglibProxy(config); } else { return new JdkDynamicAopProxy(config); } }
Usually we can summarize three sentences of AOP principle:
Use CGLIB for class generation agents
Use JDK native Proxy for interface generation agents
Proxy generation using CGLIB for interfaces can be specified through configuration files
The source of these three sentences is the createAopProxy method. See that the default line 19 code uses the Proxy generated agent that comes with JDK, with three exceptions:
ProxyConfig's isOptimize method is true, which means that Spring should optimize itself rather than specify it by the user.
ProxyConfig's isProxyTargetClass method is true, which means that proxy-target-class= "true" is configured.
ProxyConfig satisfies the hasNoUserSuppliedProxyInterfaces method and the result is true, which means that the object does not implement any interfaces or that the interface implemented is SpringProxy interface.
After entering the if judgment in the second line, the type of AopProxy to return is determined according to the type of target. To sum up, it is:
Proxy-target-class is not configured or proxy-target-class="false" returns JdkDynamicAopProxy
proxy-target-class="true" or the object does not implement any interface or only SpringProxy interface, returning Cglib2AopProxy
Of course, whether it's JdkDynamicAopProxy or Cglib2AopProxy, AdvisedSupport is passed in as a constructor parameter, which stores specific Advisor s.
Proxy Object Instance - Get the corresponding proxy by getProxy method
In fact, the code has been analyzed to JdkDynamicAopProxy and glib2AopProxy, the rest is nothing to say, just to see the familiarity of these two ways to generate agents.
Cglib2AopProxy generates proxy code is not read, not familiar with Cglib friends can see Cglib and its basic use of a text.
JdkDynamicAopProxy generates proxies in a slight way:
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
This explains the code in lines 5 and 6. The function of the code in line 5 is to get all the interfaces to be proxied. The function of the code in line 6 is to try to find out if there are equals methods and hashCode methods in these interface methods. At the same time, if there are any, mark the end of the search. The equals method and hashCode method have special treatment. .
Finally, the proxy object corresponding to the interface/class is obtained through the Proxy.newProxyInstance method in line 7. Proxy is the way JDK natively supports the generation of proxies.
Principle of proxy method invocation
The principle of generating proxies for interfaces/classes has been analyzed in detail before. After generating proxies, the method will be called. Here's a look at the principle of using JdkDynamicAopProxy to call methods.
Because JdkDynamicAopProxy itself implements the InvocationHandler interface, the logic of pre-and post-processing of specific proxy is in invoke method:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation invocation; Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Class targetClass = null; Object target = null; try { if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { // The target does not implement the equals(Object) method itself. return equals(args[0]); } if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { // The target does not implement the hashCode() method itself. return hashCode(); } if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations on ProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal; if (this.advised.exposeProxy) { // Make invocation available if necessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } // May be null. Get as late as possible to minimize the time we "own" the target, // in case it comes from a pool. target = targetSource.getTarget(); if (target != null) { targetClass = target.getClass(); } // Get the interception chain for this method. List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // Check whether we have any advice. If we don't, we can fallback on direct // reflective invocation of the target, and avoid creating a MethodInvocation. if (chain.isEmpty()) { // We can skip creating a MethodInvocation: just invoke the target directly // Note that the final invoker must be an InvokerInterceptor so we know it does // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); } else { // We need to create a method invocation... invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed(); } // Massage return value if necessary. if (retVal != null && retVal == target && method.getReturnType().isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { // Special case: it returned "this" and the return type of the method // is type-compatible. Note that we can't help if the target sets // a reference to itself in another returned object. retVal = proxy; } return retVal; } finally { if (target != null && !targetSource.isStatic()) { // Must have come from TargetSource. targetSource.releaseTarget(target); } if (setProxyContext) { // Restore old proxy. AopContext.setCurrentProxy(oldProxy); } } }
Lines 11 to 18 show that even if the equals method and hashCode method satisfy the expression rule, they will not generate proxy content for them. They call the equals method and hashCode method of JdkDynamicAopProxy. As for what these two methods do, you can check the source code for yourself.
Lines 19 to 23 show that the lass to which the method belongs is an interface and that the lass to which the method belongs is the parent or parent of AdvisedSupport, calling the method directly through reflection.
Lines 27 to 30 are used to determine whether the agent is exposed, and are configured by expose-proxy= "true/false" in the label.
Line 41 of the code, get a list of all interceptors and dynamic interceptors in AdvisedSupport for interception methods, specific to our actual code, there are three Object s in the list, respectively:
chain.get(0): ExposeInvocation Interceptor, which is the default interceptor, corresponds to the original Advisor DefaultPointcut Advisor
chain.get(1): Method BeforeAdvice Interceptor for interception before actual method calls, corresponding to the original Advisor AspectJMethod BeforeAdvice
chain.get(2): AspectJAfterAdvice for processing after actual method calls
Lines 45 to 50 are normal if the interceptor list is empty, because a method under a class/interface may not satisfy the expression matching rule, so this method is called directly by reflection.
Lines 51 to 56, if the interceptor list is not empty, according to the meaning of the comment, we need a Reflective Method Invocation, and intercept the original method through proceed method. Friends interested in proceed method can look at it. It uses the idea of recursion to layers the Object in the chain. Call.
CGLIB Agent Implementation
Let's take a look at the way CGLIB agents are created. Here we need readers to understand CGLIB and how it is created.
The interceptor chain is encapsulated in the Dynamic Advised Interceptor, and Callback is added. The Dynamic Advised Interceptor implements the CGLIB Method Interceptor, so its core logic is in the intercept method:
Here we see the same process of acquiring the interceptor chain as the JDK dynamic proxy, and CglibMethod Invokcation inherits the Reflective Method Invocation we saw in the JDK dynamic proxy, but it does not override its proceed method, it just rewrites the logic of executing the target method, so it is different in size as a whole.
At this point, the whole Spring dynamic AOP source code is analyzed, Spring also supports static AOP, there is not much to elaborate, interested readers can consult the relevant information to learn.
The author of Wechat Public No. Huang Xiaoxiao is an engineer of Ant Golden Wear JAVA, focusing on JAVA.
Back-end technology stack: SpringBoot, SSM family bucket, MySQL, distributed, middleware, micro services, but also know how to invest in financial management, adhere to learning and writing, believe in the power of lifelong learning! Respond to "Architect" after paying attention to the public number.
Free learning materials such as Java Foundation, Advancement, Project and Architect, as well as popular technology learning videos such as database, distributed, micro-service, etc., are rich in content, taking into account the principles and practices. In addition, the author's original Java learning guide, Java programmer interview guide and other dry goods resources will also be presented.