"Spring Aop" source code analysis 4: Aop source code analysis

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 cache

    public 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

Keywords: Spring source code analysis

Added by spheonix on Sat, 12 Feb 2022 04:49:25 +0200