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

前言

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

代理對象生成

代理對象的生成策略和 Spring 框架一致,當被代理類實現了接口時採用 JDK 動態代理的方式生成代理對象,被代理對象未實現接口時使用 CGLIB 來生成代理對象,爲了簡單起見這裏不支持手動指定生成代理對象的策略,JDK 動態代理的實現這裏不在介紹,感興趣能夠本身實現一下,這裏主要討論 CGLIB 的生成方式。git

在這裏插入圖片描述

基於面向接口編程的思想,這裏的生成代理對象須要定義一個統一的接口,無論是 CGLIB 生成方式仍是JDK 動態代理生成方式都要實現該接口。生成代理對象是根據一些配置去生成的,一樣,這裏生成代理的配置也能夠抽取一個統一的接口,在實現類中定義攔截器(也就是 Advice)以及實現的接口等,CGLIB 的基本使用能夠到官網自行查找。代理對象生成的總體的類圖以下:github

在這裏插入圖片描述

其中代理建立的工廠接口 AopProxyFactory 以下,提供了不指定 ClassLoader(使用默認的 ClassLoader)和指定 ClassLoader 兩種方式建立代理對象,源碼以下:spring

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

  Object getProxy();

  Object getProxy(ClassLoader classLoader);

}

使用 CGLIB 建立代理的工廠接口實現類以下所示:express

/**
 * @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 生成的方式就已經實現了,核心代碼其實比較簡單,主要是須要多考慮考慮代碼後期的擴展性。app

建立 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,主要部分代碼以下所示:dom

/**
 * @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 實現部分已經所有介紹完畢。

相關文章
相關標籤/搜索