preface

In the previous article, we realized that the way to determine a class is through a configured Pointcut expression, based on a Bean’s name and method name, Gets Method objects, implements BeforeAdvice, AfterReturningAdvice, and AfterThrowingAdvice and calls them in the specified order. This article takes a look at how the rest of the proxy objects are generated, how BeanDefintion is generated from an XML configuration file, and how to put the generated proxy objects into a container. Without further ado, let’s move on to the topic.

Proxy object generation

The generation strategy of proxy objects is consistent with the Spring framework. JDK dynamic proxy is used to generate proxy objects when the proxied class implements an interface, and CGLIB is used to generate proxy objects when the proxied class does not implement an interface. For simplicity, manually specifying the generation strategy of proxy objects is not supported. JDK dynamic proxy implementation is not introduced here, interested in their own implementation, here mainly discusses the generation of CGLIB.

Based on the idea of interface oriented programming, the generated proxy object here needs to define a unified interface, whether CGLIB generation method or JDK dynamic proxy generation method to implement this interface. The generated proxy object is generated according to some configurations. Similarly, the configuration of the generated proxy can extract a unified interface, and define interceptors (i.e. Advice) and implemented interfaces in the implementation class. The basic use of CGLIB can be found on the official website. The overall class diagram generated by the proxy object looks like this:

The AopProxyFactory interface for proxy creation is as follows, which 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
 * @sinceThe 2021-06-13 * /
public interface AopProxyFactory {

  Object getProxy(a);

  Object getProxy(ClassLoader classLoader);

}
Copy the code

The factory interface implementation class for creating the proxy using CGLIB looks like this:

/ * * *@author mghio
 * @sinceThe 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(a) {
    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 =newClass<? >[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 ...

}
Copy the code

The overall overview is relatively simple, mainly CGLIB third-party bytecode generation library basic usage, of course, if you already understand the basic use of CGLIB. AOP related configuration interface Advised is relatively simple, mainly some related properties of the increase, delete, change operations, the main part of the code is as follows:

/ * * *@author mghio
 * @sinceThe 2021-06-13 * /
public interface Advised { Class<? > getTargetClass();boolean isInterfaceProxied(Class
        intf);

  List<Advice> getAdvices(a);

  void addAdvice(Advice advice);

  List<Advice> getAdvices(Method method);

  void addInterface(Class
        clazz);

  // omit other methods ...

}
Copy the code

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

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

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

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

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

  @Override
  public List<Advice> getAdvices(a) {
    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 ...

}
Copy the code

So far, the proxy object using CGLIB generation has been achieved, the core code is actually relatively simple, mainly needs to consider the code later extensibility.

Create a BeanDefinition

Let’s start by looking at how AOP in general is defined in an XML configuration file. An XML configuration file containing BeforeAdvice, AfterReturningAdvice, and AfterThrowingAdvice looks like this:


      
<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>
Copy the code

Given our previous experience with parsing XML Bean definitions, it is clear that we need a data structure to represent this AOP configuration. If you read the previous article, Class AspectJExpressionPointcut represents < aop: pointcut id = “placeOrder expression” = “execution (* cn.mghio.service.version5.*.placeOrder(..) ) “/ >, a few other Advice configuration corresponding AspectJBeforeAdvice, AspectJAfterReturningAdvice and AspectJAfterThrowingAdvice several classes. The XML configuration file is parsed and the corresponding Advice constructor is used to create the corresponding object. Dom4j is used to parse the XML. The main part of the code looks like this:

/ * * *@author mghio
 * @sinceThe 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(newRuntimeBeanReference(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
       
         beanDefinitions, List
        
          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 ...

}
Copy the code

Now that the creation of BeanDefinitions is complete and the corresponding BeanDefintion can be parsed from the XML configuration file, it’s just a matter of dropping these BeanDefinitions into the container at the appropriate time to complete the process.

How do you put it in a container

How do I put the resolved BeanDefintion into the container? We know that there are many “hook functions” available in the Spring framework, so we can start with this. The Bean lifecycle is as follows:

Choose after the completion of a Bean instantiation BeanPostProcessor postProcessAfterInitialization () method to create a proxy object, using the AOP is AspectJ, Create proxy object class named AspectJAutoProxyCreator, implement BeanPostProcessor interface, handle the creation of proxy object, AspectJAutoProxyCreator class core source code is as follows:

/ * * *@author mghio
 * @sinceThe 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 {
    // If the bean itself is Advice and its subclasses, no dynamic proxy is generated
    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) {
      / / additional agent
      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) {
    returnAdvice.class.isAssignableFrom(beanClass); }}Copy the code

Finally, remember that the BeanPostProcessor interface is a new one. We need to add the BeanPostProcessor processing logic to the DefaultFactoryBean. The main changes 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) {...// Non-synthetic types create proxies
        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 ...

}
Copy the code

Finally, run the pre-test case and pass as expected.

conclusion

This article mainly introduces the AOP proxy object generation, parsing XML configuration file and create the corresponding BeanDefinition and finally inject into the container, just introduces the general implementation ideas, specific code implementation has been uploaded to mghio-Spring, interested friends can refer to, here, The AOP implementation part is all covered.