头图

Preface

In , realizes the way to judge a class is in line with the configured pointcut expression, obtains the Method object according to the name and method name of a Bean, implements BeforeAdvice, AfterReturningAdvice and AfterThrowingAdvice and calls them in the specified order. Let's look at the rest of the how proxy objects generated , generate BeanDefintion based on XML configuration files and proxy object how the resulting placed in a container other functions, did not talk much, enter the following topic.

Proxy object generation

The proxy object generation strategy is consistent with the Spring framework. When the proxy class implements the interface, the JDK dynamic proxy method is used to generate the proxy object. When the proxy object does not implement the interface, CGLIB is used to generate the proxy object. For the sake of simplicity, manual specification is not supported here. The strategy of generating proxy objects, the implementation of JDK dynamic proxy is not introduced here, if you are interested, you can implement it yourself. Here we mainly discuss the generation method of CGLIB.

在这里插入图片描述

Based on the idea of interface-oriented programming, the generation of proxy objects here needs to define a unified interface, whether it is CGLIB generation mode or JDK dynamic proxy generation mode to implement this interface. The generated proxy object is generated according to some configurations. Similarly, the configuration of the generated proxy here can also extract a unified interface, and define the interceptor (that is, Advice) and the implemented interface in the implementation class. The basic use of CGLIB You can go to the official website to find it yourself. The overall class diagram generated by the proxy object is as follows:

在这里插入图片描述

The factory interface AopProxyFactory created by the proxy is as follows. It provides two ways to create proxy objects without specifying a ClassLoader (using the default ClassLoader) and specifying a ClassLoader. The source code is as follows:

/**
 * @author mghio
 * @since 2021-06-13
 */
public interface AopProxyFactory {

  Object getProxy();

  Object getProxy(ClassLoader classLoader);

}

The factory interface implementation class that uses CGLIB to create a proxy is as follows:

/**
 * @author mghio
 * @since 2021-06-13
 */
public class CglibProxyFactory implements AopProxyFactory {

  /*
   * Constants for CGLIB callback array indices
   */
  private static final int AOP_PROXY = 0;

  protected final Advised advised;

  public CglibProxyFactory(Advised config) {
    Assert.notNull(config, "AdvisedSupport must not be null");
    if (config.getAdvices().size() == 0) {
      throw new AopConfigException("No advisors and no TargetSource specified");
    }

    this.advised = config;
  }

  @Override
  public Object getProxy() {
    return getProxy(null);
  }

  @Override
  public Object getProxy(ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
      logger.debug("Creating CGLIB proxy: target class is " + this.advised.getTargetClass());
    }

    try {
      Class<?> rootClass = this.advised.getTargetClass();

      // Configure CGLIB Enhancer...
      Enhancer enhancer = new Enhancer();
      if (classLoader != null) {
        enhancer.setClassLoader(classLoader);
      }
      enhancer.setSuperclass(rootClass);
      enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);  // BySpringCGLIB
      enhancer.setInterceptDuringConstruction(false);

      Callback[] callbacks = getCallbacks(rootClass);
      Class<?>[] types = new Class<?>[callbacks.length];
      for (int i = 0; i < types.length; i++) {
        types[i] = callbacks[i].getClass();
      }
      enhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised));
      enhancer.setCallbackTypes(types);
      enhancer.setCallbacks(callbacks);

      // Generate the proxy class and create a proxy instance.
      return enhancer.create();
    }
    catch (CodeGenerationException | IllegalArgumentException ex) {
      throw new AopConfigException("Could not generate CGLIB subclass of class [" +
          this.advised.getTargetClass() + "]: " +
          "Common causes of this problem include using a final class or a non-visible class",
          ex);
    } catch (Exception ex) {
      // TargetSource.getTarget() failed
      throw new AopConfigException("Unexpected AOP exception", ex);
    }
  }

  // omit other methods ...

}

Overall, it is relatively simple, mainly the basic usage of the CGLIB third-party bytecode generation library. Of course, the premise is that you have already understood the basic usage of CGLIB. The related configuration interface Advised of AOP is relatively simple, mainly for the addition, deletion, and modification of some related attributes. The main part of the code is as follows:

/**
 * @author mghio
 * @since 2021-06-13
 */
public interface Advised {

  Class<?> getTargetClass();

  boolean isInterfaceProxied(Class<?> intf);

  List<Advice> getAdvices();

  void addAdvice(Advice advice);

  List<Advice> getAdvices(Method method);

  void addInterface(Class<?> clazz);

  // omit other methods ...

}

The implementation class is also relatively simple, the code is as follows:

/**
 * @author mghio
 * @since 2021-06-13
 */
public class AdvisedSupport implements Advised {

  private boolean proxyTargetClass = false;
  private Object targetObject = null;
  private final List<Advice> advices = new ArrayList<>();
  private final List<Class<?>> interfaces = new ArrayList<>();

  public AdvisedSupport() {
  }

  @Override
  public Class<?> getTargetClass() {
    return this.targetObject.getClass();
  }

  @Override
  public boolean isInterfaceProxied(Class<?> intf) {
    return interfaces.contains(intf);
  }

  @Override
  public List<Advice> getAdvices() {
    return this.advices;
  }

  @Override
  public void addAdvice(Advice advice) {
    this.advices.add(advice);
  }

  @Override
  public List<Advice> getAdvices(Method method) {
    List<Advice> result = new ArrayList<>();
    for (Advice advice : this.getAdvices()) {
      Pointcut pc = advice.getPointcut();
      if (pc.getMethodMatcher().matches(method)) {
        result.add(advice);
      }
    }
    return result;
  }

  @Override
  public void addInterface(Class<?> clazz) {
    this.interfaces.add(clazz);
  }

  // omit other methods ...

}

Up to this point, the way the proxy object is generated using CGLIB has been implemented. The core code is actually relatively simple, and the main reason is that you need to consider the scalability of the code later.

Create BeanDefinition

Let's first take a look at how the general AOP is defined in the XML configuration file. An XML configuration file containing BeforeAdvice, AfterReturningAdvice and AfterThrowingAdvice is as follows:

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

  <context:scann-package base-package="cn.mghio.service.version5,cn.mghio.dao.version5" />

  <bean id="tx" class="cn.mghio.tx.TransactionManager"/>

  <aop:config>
    <aop:aspect ref="tx">
      <aop:pointcut id="placeOrder" expression="execution(* cn.mghio.service.version5.*.placeOrder(..))"/>
      <aop:before pointcut-ref="placeOrder" method="start"/>
      <aop:after-returning pointcut-ref="placeOrder" method="commit"/>
      <aop:after-throwing pointcut-ref="placeOrder" method="rollback"/>
    </aop:aspect>
  </aop:config>
</beans>

With the previous experience of parsing the XML Bean definition, it is obvious that we need a data structure to represent this AOP configuration. If you have read , , 160c60164b9d15, the class AspectJExpressionPointcut means <aop:pointcut id="placeOrder" expression="execution( cn.mghio.service.version5. .placeOrder(..))"/>, the other Advice configurations correspond to AspectJBeforeAdvice, AspectJAfterReturningAdvice, AspectJAfterThrowingAdvice and other classes.
Here, just parse the XML configuration file, and then use the corresponding Advice constructor to create the corresponding object. The XML parsing uses dom4j, and the main part of the code is as follows:

/**
 * @author mghio
 * @since 2021-06-13
 */
public class ConfigBeanDefinitionParser {

  private static final String ASPECT = "aspect";
  private static final String EXPRESSION = "expression";
  private static final String ID = "id";
  private static final String REF = "ref";
  private static final String BEFORE = "before";
  private static final String AFTER = "after";
  private static final String AFTER_RETURNING_ELEMENT = "after-returning";
  private static final String AFTER_THROWING_ELEMENT = "after-throwing";
  private static final String AROUND = "around";
  private static final String POINTCUT = "pointcut";
  private static final String POINTCUT_REF = "pointcut-ref";
  private static final String ASPECT_NAME_PROPERTY = "aspectName";

  public void parse(Element element, BeanDefinitionRegistry registry) {
    List<Element> childElements = element.elements();
    for (Element el : childElements) {
      String localName = el.getName();
      if (ASPECT.equals(localName)) {
        parseAspect(el, registry);
      }
    }
  }

  private void parseAspect(Element aspectElement, BeanDefinitionRegistry registry) {
    String aspectName = aspectElement.attributeValue(REF);

    List<BeanDefinition> beanDefinitions = new ArrayList<>();
    List<RuntimeBeanReference> beanReferences = new ArrayList<>();

    // parse advice
    List<Element> elements = aspectElement.elements();
    boolean adviceFoundAlready = false;
    for (Element element : elements) {
      if (isAdviceNode(element)) {
        if (!adviceFoundAlready) {
          adviceFoundAlready = true;
          if (!StringUtils.hasText(aspectName)) {
            return;
          }
          beanReferences.add(new RuntimeBeanReference(aspectName));
        }
        GenericBeanDefinition advisorDefinition = parseAdvice(aspectName, element, registry,
            beanDefinitions, beanReferences);
        beanDefinitions.add(advisorDefinition);
      }
    }

    // parse pointcut
    List<Element> pointcuts = aspectElement.elements(POINTCUT);
    for (Element pointcut : pointcuts) {
      parsePointcut(pointcut, registry);
    }
  }

  private void parsePointcut(Element pointcutElement, BeanDefinitionRegistry registry) {
    String id = pointcutElement.attributeValue(ID);
    String expression = pointcutElement.attributeValue(EXPRESSION);

    GenericBeanDefinition pointcutDefinition = createPointcutDefinition(expression);
    if (StringUtils.hasText(id)) {
      registry.registerBeanDefinition(id, pointcutDefinition);
    } else {
      BeanDefinitionReaderUtils.registerWithGeneratedName(pointcutDefinition, registry);
    }
  }

  private GenericBeanDefinition parseAdvice(String aspectName, Element adviceElement,
      BeanDefinitionRegistry registry, List<BeanDefinition> beanDefinitions,
      List<RuntimeBeanReference> beanReferences) {

    GenericBeanDefinition methodDefinition = new GenericBeanDefinition(MethodLocatingFactory.class);
    methodDefinition.getPropertyValues().add(new PropertyValue("targetBeanName", aspectName));
    methodDefinition.getPropertyValues().add(new PropertyValue("methodName",
        adviceElement.attributeValue("method")));
    methodDefinition.setSynthetic(true);

    // create instance definition factory
    GenericBeanDefinition aspectFactoryDef = new GenericBeanDefinition(AopInstanceFactory.class);
    aspectFactoryDef.getPropertyValues().add(new PropertyValue("aspectBeanName", aspectName));
    aspectFactoryDef.setSynthetic(true);

    // register the pointcut
    GenericBeanDefinition adviceDef = createAdviceDefinition(adviceElement, aspectName,
        methodDefinition, aspectFactoryDef, beanDefinitions, beanReferences);
    adviceDef.setSynthetic(true);

    // register the final advisor
    BeanDefinitionReaderUtils.registerWithGeneratedName(adviceDef, registry);

    return adviceDef;
  }

  // omit other methods ...

}

The creation of the BeanDefinition has been completed, and now the corresponding BeanDefintion can be parsed according to the XML configuration file. The following only needs to put these BeanDefinitions in the container at the right time to complete the entire process.

How to put in the container

How to put the resolved BeanDefintion into the container? We know that there are a lot of "hook functions" provided in the Spring framework. You can start from here. The life cycle of a Bean is as follows:

在这里插入图片描述

After the Bean instantiation is completed, the postProcessAfterInitialization() method of BeanPostProcessor is selected to create the proxy object. AOP uses AspectJ. The class that creates the proxy object is named AspectJAutoProxyCreator to implement the BeanPostProcessor interface and handle the creation of proxy objects. The core source code of the AspectJAutoProxyCreator class is as follows:

/**
 * @author mghio
 * @since 2021-06-13
 */
public class AspectJAutoProxyCreator implements BeanPostProcessor {

  private ConfigurableBeanFactory beanFactory;

  @Override
  public Object beforeInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }

  @Override
  public Object afterInitialization(Object bean, String beanName) throws BeansException {
    // 如果这个 bean 本身就是 Advice 及其子类,则不生成动态代理
    if (isInfrastructureClass(bean.getClass())) {
      return bean;
    }

    List<Advice> advices = getCandidateAdvices(bean);
    if (advices.isEmpty()) {
      return bean;
    }

    return createProxy(advices, bean);
  }

  protected Object createProxy(List<Advice> advices, Object bean) {
    Advised config = new AdvisedSupport();
    for (Advice advice : advices) {
      config.addAdvice(advice);
    }

    Set<Class> targetInterfaces = ClassUtils.getAllInterfacesForClassAsSet(bean.getClass());
    for (Class targetInterface : targetInterfaces) {
      config.addInterface(targetInterface);
    }
    config.setTargetObject(bean);

    AopProxyFactory proxyFactory = null;
    if (config.getProxiedInterfaces().length == 0) {
      // CGLIB 代理
      proxyFactory = new CglibProxyFactory(config);
    } else {
      // TODO(mghio): JDK dynamic proxy ...

    }

    return proxyFactory.getProxy();
  }

  public void setBeanFactory(ConfigurableBeanFactory beanFactory) {
    this.beanFactory = beanFactory;
  }

  private List<Advice> getCandidateAdvices(Object bean) {
    List<Object> advices = this.beanFactory.getBeansByType(Advice.class);
    List<Advice> result = new ArrayList<>();
    for (Object advice : advices) {
      Pointcut pointcut = ((Advice) advice).getPointcut();
      if (canApply(pointcut, bean.getClass())) {
        result.add((Advice) advice);
      }
    }
    return result;
  }

  private boolean canApply(Pointcut pointcut, Class<?> targetClass) {
    MethodMatcher methodMatcher = pointcut.getMethodMatcher();
    Set<Class> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
    classes.add(targetClass);
    for (Class<?> clazz : classes) {
      Method[] methods = clazz.getDeclaredMethods();
      for (Method m : methods) {
        if (methodMatcher.matches(m)) {
          return true;
        }
      }
    }
    return false;
  }

  private boolean isInfrastructureClass(Class<?> beanClass) {
    return Advice.class.isAssignableFrom(beanClass);
  }
}

Finally, don't forget that the BeanPostProcessor interface here is newly added by us. The processing logic for BeanPostProcessor needs to be added to the previously defined DefaultFactoryBean. The main modifications are as follows:

public class DefaultBeanFactory extends AbstractBeanFactory implements BeanDefinitionRegistry {

    @Override
    public Object createBean(BeanDefinition bd) throws BeanCreationException {
        // 1. instantiate bean
        Object bean = instantiateBean(bd);
        // 2. populate bean
        populateBean(bd, bean);
        // 3. initialize bean
        bean = initializeBean(bd, bean);
        return bean;
    }

    protected Object initializeBean(BeanDefinition bd, Object bean) {
        
        ...

        // 非合成类型则创建代理
        if (!bd.isSynthetic()) {
            return applyBeanPostProcessorAfterInitialization(bean, bd.getId());
        }
        return bean;
    }

    private Object applyBeanPostProcessorAfterInitialization(Object existingBean, String beanName) {
        Object result = existingBean;
        for (BeanPostProcessor postProcessor : getBeanPostProcessors()) {
            result = postProcessor.afterInitialization(result, beanName);
            if (result == null) {
                return null;
            }
        }
        return result;
    }

    // omit other field and methods ...

}

Finally, the pre-test case is run, and it passes and meets expectations.

在这里插入图片描述

to sum up

This article mainly introduces the generation of AOP proxy objects, parsing the XML configuration file and creating the corresponding BeanDefinition, and finally injecting it into the container. It only introduces the general implementation ideas. The specific code implementation has been uploaded mghio-spring . Interested friends can refer to it. At this point, the AOP implementation part has all been introduced.


mghio
446 声望870 粉丝