如何實現一個簡易版的 Spring - 如何實現 AOP(終結篇)

mghio發表於2021-06-13

前言

上篇 實現了 判斷一個類的方式是符合配置的 pointcut 表示式、根據一個 Bean 的名稱和方法名,獲取 Method 物件、實現了 BeforeAdvice、AfterReturningAdvice 以及 AfterThrowingAdvice並按照指定次序呼叫 等功能,這篇再來看看剩下的 代理物件如何生成根據 XML 配置檔案生成 BeanDefintion以及如何將生成的代理物件放入到容器中 等功能,話不多說,下面進入主題。

代理物件生成

代理物件的生成策略和 Spring 框架一致,當被代理類實現了介面時採用 JDK 動態代理的方式生成代理物件,被代理物件未實現介面時使用 CGLIB 來生成代理物件,為了簡單起見這裡不支援手動指定生成代理物件的策略,JDK 動態代理的實現這裡不在介紹,感興趣可以自己實現一下,這裡主要討論 CGLIB 的生成方式。

在這裡插入圖片描述

基於面向介面程式設計的思想,這裡的生成代理物件需要定義一個統一的介面,不管是 CGLIB 生成方式還是JDK 動態代理生成方式都要實現該介面。生成代理物件是根據一些配置去生成的,同樣,這裡生成代理的配置也可以抽取一個統一的介面,在實現類中定義攔截器(也就是 Advice)以及實現的介面等,CGLIB 的基本使用可以到官網自行查詢。代理物件生成的整體的類圖如下:

在這裡插入圖片描述

其中代理建立的工廠介面 AopProxyFactory 如下,提供了不指定 ClassLoader(使用預設的 ClassLoader)和指定 ClassLoader 兩種方式建立代理物件,原始碼如下:

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

  Object getProxy();

  Object getProxy(ClassLoader classLoader);

}

使用 CGLIB 建立代理的工廠介面實現類如下所示:

/**
 * @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 ...

}

整體來看還是比較簡單的,主要是 CGLIB 第三方位元組碼生成庫的基本用法,當然,前提是你已經瞭解了 CGLIB 的基本使用。AOP 的相關配置介面 Advised 相對來說就比較簡單了,主要是一些相關屬性的增、刪、改等操作,主要部分程式碼如下:

/**
 * @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 ...

}

實現類也比較簡單,程式碼如下:

/**
 * @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 ...

}

到這裡,代理物件使用 CGLIB 生成的方式就已經實現了,核心程式碼其實比較簡單,主要是需要多考慮考慮程式碼後期的擴充套件性。

建立 BeanDefinition

我們先來看看一般 AOP 在 XML 配置檔案中是如何定義的,一個包含 BeforeAdvice、AfterReturningAdvice以及AfterThrowingAdvice 的 XML 配置檔案如下:

<?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>

有了之前解析 XML 的 Bean 定義的經驗後,很顯然這裡我們需要一個資料結構去表示這個 AOP 配置,如果你閱讀過 上篇 的話,類 AspectJExpressionPointcut 表示的是 <aop:pointcut id="placeOrder" expression="execution(* cn.mghio.service.version5.*.placeOrder(..))"/>,另外幾個 Advice 配置分別對應 AspectJBeforeAdvice、AspectJAfterReturningAdvice以及 AspectJAfterThrowingAdvice 等幾個類。
這裡只要解析 XML 配置檔案,然後使用對應的 Advice 的構造器建立對應的物件即可,解析 XML 使用的是 dom4j,主要部分程式碼如下所示:

/**
 * @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 ...

}

建立 BeanDefinition 已經完成了,現在可根據 XML 配置檔案解析出對應的 BeanDefintion 了,下面只需要在合適的時機將這些 BeanDefinition 放到容器中就完成了全部流程了。

如何放到容器中

該如何把解析出來的 BeanDefintion 放到容器當中去呢?我們知道在 Spring 框架當中提供了很多的“鉤子函式”,可以從這裡入手,Bean 的生命週期如下:

在這裡插入圖片描述

選擇在 Bean 例項化完成之後 BeanPostProcessor 的 postProcessAfterInitialization() 方法建立代理物件,AOP 使用的是 AspectJ,將建立代理物件的類命名為 AspectJAutoProxyCreator,實現 BeanPostProcessor 介面,處理代理物件的建立,AspectJAutoProxyCreator 類的核心原始碼如下:

/**
 * @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);
  }
}

最後別忘了,這裡的 BeanPostProcessor 介面是我們新加的,需要到之前定義的 DefaultFactoryBean 中加上對 BeanPostProcessor 的處理邏輯,主要修改如下:

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 ...

}

最後執行事先測試用例,正常通過符合預期。

在這裡插入圖片描述

總結

本文主要介紹了 AOP 代理物件生成、解析 XML 配置檔案並建立對應的 BeanDefinition 以及最後注入到容器中,只是介紹了大體實現思路,具體程式碼實現已上傳 mghio-spring,感興趣的朋友可以參考,到這裡,AOP 實現部分已經全部介紹完畢。

相關文章