As we all know, all aspects of AOP must be through the creation of agents (you should listen to all the basic concepts of AOP, so I won't repeat them here). But then the problem arises. We have analyzed the parsing and creation of ordinary bean s. Where does AOP create proxy objects and how to match tangent points. This article also focuses on these two issues. The analysis of dynamic agents has been finished in the previous article. Those who are interested can have a look. Portal
The problems of this study
- Creation of proxy objects
Matching tangent point
Test code
@Aspect class AdviceUsingThisJoinPoint { private String lastEntry = ""; public String getLastMethodEntered() { return this.lastEntry; } @Pointcut("execution(int *.getAge())") public void methodExecution() { } @Before("methodExecution()") public void entryTrace(JoinPoint jp) { this.lastEntry = jp.toString(); System.out.println(this.lastEntry); } }
public class TestBean implements BeanNameAware, BeanFactoryAware, ITestBean, IOther, Comparable<Object> { //The superfluous part has been deleted private String name; private int age; public TestBean() { } public TestBean(String name) { this.name = name; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } @Override public int getAge() { return age; } @Override public void setAge(int age) { this.age = age; } /** * @see org.springframework.beans.testfixture.beans.ITestBean#exceptional(Throwable) */ @Override public void exceptional(Throwable t) throws Throwable { if (t != null) { throw t; } } @Override public void unreliableFileOperation() throws IOException { throw new IOException(); } /** * @see org.springframework.beans.testfixture.beans.ITestBean#returnsThis() */ @Override public Object returnsThis() { return this; } /** * @see org.springframework.beans.testfixture.beans.IOther#absquatulate() */ @Override public void absquatulate() { } @Override public int haveBirthday() { return age++; } @Override public boolean equals(Object other) { if (this == other) { return true; } if (other == null || !(other instanceof TestBean)) { return false; } TestBean tb2 = (TestBean) other; return (ObjectUtils.nullSafeEquals(this.name, tb2.name) && this.age == tb2.age); } @Override public int hashCode() { return this.age; } @Override public int compareTo(Object other) { if (this.name != null && other instanceof TestBean) { return this.name.compareTo(((TestBean) other).getName()); } else { return 1; } } @Override public String toString() { return this.name; } }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/> <bean id="aspect" class="org.springframework.aop.aspectj.autoproxy.AdviceUsingThisJoinPoint"/> <bean id="adrian" class="org.springframework.beans.testfixture.beans.TestBean" scope="prototype"> <property name="name" value="adrian"/> <property name="age" value="34"/> </bean> </beans>
@Test public void testAdviceUsingJoinPoint() { ClassPathXmlApplicationContext bf = newContext("usesJoinPointAspect.xml"); ITestBean adrian1 = (ITestBean) bf.getBean("adrian"); adrian1.getAge(); adrian1.getDoctor(); AdviceUsingThisJoinPoint aspectInstance = (AdviceUsingThisJoinPoint) bf.getBean("aspect"); assertThat(aspectInstance.getLastMethodEntered().indexOf("TestBean.getAge())") != 0).isTrue(); }
Source code analysis
The entry to create proxy object is abstractautoproxycreator #postprocessbeforeinstance. It can be seen that the core method is getAdvicesAndAdvisorsForBean, and then create proxy object
@Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) { Object cacheKey = getCacheKey(beanClass, beanName); if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) { if (this.advisedBeans.containsKey(cacheKey)) { return null; } if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return null; } } // Create proxy here if we have a custom TargetSource. // Suppresses unnecessary default instantiation of the target bean: // The TargetSource will handle target instances in a custom fashion. TargetSource targetSource = getCustomTargetSource(beanClass, beanName); if (targetSource != null) { if (StringUtils.hasLength(beanName)) { this.targetSourcedBeans.add(beanName); } Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } return null; }
This method is to obtain the corresponding Advisor
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) { List<Advisor> candidateAdvisors = findCandidateAdvisors();//Get candidate Advisor List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);//Get Advisor for bean: for example, Pointcut match extendAdvisors(eligibleAdvisors);//Special treatment if (!eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors);//sort } return eligibleAdvisors; }
Findcandidate advisors will eventually call buildAspectJAdvisors to get the corresponding Advisor,
The first time you enter, you will find the method defined by @ Aspect, generate the corresponding Advisor (encapsulated Advice), and then get it from the cachepublic List<Advisor> buildAspectJAdvisors() { List<String> aspectNames = this.aspectBeanNames; if (aspectNames == null) { synchronized (this) { aspectNames = this.aspectBeanNames;//Concurrency problem if (aspectNames == null) { List<Advisor> advisors = new ArrayList<>(); aspectNames = new ArrayList<>(); String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(// this.beanFactory, Object.class, true, false); for (String beanName : beanNames) { if (!isEligibleBean(beanName)) { continue; } // We must be careful not to instantiate beans eagerly as in this case they // would be cached by the Spring container but would not have been weaved. Class<?> beanType = this.beanFactory.getType(beanName, false); if (beanType == null) { continue; } if (this.advisorFactory.isAspect(beanType)) {//Is it a bean decorated with @ Aspect aspectNames.add(beanName); AspectMetadata amd = new AspectMetadata(beanType, beanName); if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);//Generate Adivsor if (this.beanFactory.isSingleton(beanName)) { this.advisorsCache.put(beanName, classAdvisors); } else { this.aspectFactoryCache.put(beanName, factory); } advisors.addAll(classAdvisors); } else { // Per target or per this. if (this.beanFactory.isSingleton(beanName)) { throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect instantiation model is not singleton"); } MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName); this.aspectFactoryCache.put(beanName, factory); advisors.addAll(this.advisorFactory.getAdvisors(factory)); } } } this.aspectBeanNames = aspectNames; return advisors; } } } if (aspectNames.isEmpty()) { return Collections.emptyList(); } List<Advisor> advisors = new ArrayList<>(); for (String aspectName : aspectNames) { List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName); if (cachedAdvisors != null) { advisors.addAll(cachedAdvisors); } else { MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName); advisors.addAll(this.advisorFactory.getAdvisors(factory)); } } return advisors; }
All the Advisor sets are obtained above, and then the matching Advisor is obtained
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) { if (candidateAdvisors.isEmpty()) { return candidateAdvisors; } List<Advisor> eligibleAdvisors = new ArrayList<>(); for (Advisor candidate : candidateAdvisors) { if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) { eligibleAdvisors.add(candidate); } } boolean hasIntroductions = !eligibleAdvisors.isEmpty(); for (Advisor candidate : candidateAdvisors) {//Traverse Advisor to find matching if (candidate instanceof IntroductionAdvisor) { // already processed continue; } if (canApply(candidate, clazz, hasIntroductions)) { eligibleAdvisors.add(candidate); } } return eligibleAdvisors; }
The specific match is at org springframework. aop. support. Aoputils#canapply (org. Springframework. AOP. Pointcut, Java. Lang. class <? >, Boolean) method. First judge whether the class matches, and then judge whether the method matches
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) { Assert.notNull(pc, "Pointcut must not be null"); if (!pc.getClassFilter().matches(targetClass)) {//Match class return false; } MethodMatcher methodMatcher = pc.getMethodMatcher(); if (methodMatcher == MethodMatcher.TRUE) { // No need to iterate the methods if we're matching any method anyway... return true; } IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; if (methodMatcher instanceof IntroductionAwareMethodMatcher) { introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; } Set<Class<?>> classes = new LinkedHashSet<>(); if (!Proxy.isProxyClass(targetClass)) { classes.add(ClassUtils.getUserClass(targetClass)); } classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); for (Class<?> clazz : classes) { Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz); for (Method method : methods) { if (introductionAwareMethodMatcher != null ? introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) : methodMatcher.matches(method, targetClass)) { return true; } } } return false; }
The proxy object will be created later. Whether it is a JDK proxy or a Cglib dynamic proxy will be determined according to whether there is an interface or not. These two have been mentioned in the previous article, so I won't go into details here
@Override 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() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
summary
First scan all @ Aspect annotated objects, package them into Advisor objects, cache them, and judge whether they match circularly when creating objects.
Just say something
So far, the parts of SpringIoC and Aop have been fully analyzed.
There is also a portal in the previous articles
"IOC bean" source code analysis
"Spring IOC" source code analysis II dependency injection & dependency loop
Source code analysis 3 of "spring AOP": JDK dynamic agent & cglib