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

前言

上篇 實現了 判斷一個類的方式是符合配置的 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 實現部分已經全部介紹完畢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章