天天看点

Spring AOP 源码初窥(二) 从注解开始

版本

  • spring 5.0.8.BUILD-SNAPSHOT
  • aspectjweaver 1.8.13

从注解开始

由于在本人实际应用中使用的是注解配置AOP,也更倾向于了解Spring AOP的整个实现,而不仅仅是关键实现。于是本篇源码解析,将会从注解开始。了解Spring AOP是怎么扫描Aspect配置,匹配,并生成AOP代理的。

注解@Aspect定了一个类为AOP的配置。那么,便从@Aspect的源码引用开始吧。

@Aspect的引用

先从源码中找有引用到@Aspect,用来判断Class是否有该注解的代码。找到方法。

/org/springframework/aop/aspectj/annotation/AbstractAspectJAdvisorFactory.java
...
@Override
public boolean isAspect(Class<?> clazz) {
    return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}

private boolean hasAspectAnnotation(Class<?> clazz) {
    return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
}

/**
 * We need to detect this as "code-style" AspectJ aspects should not be
 * interpreted by Spring AOP.
 */
private boolean compiledByAjc(Class<?> clazz) {
    // The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
    // annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
    // an implementation detail of the AspectJ compiler.
    for (Field field : clazz.getDeclaredFields()) {
        if (field.getName().startsWith(AJC_MAGIC)) {
            return true;
        }
    }
    return false;
}
...
           

isAspect(Class<?> clazz)用来判断clazz对象是否包含Aspect.class注解并且未被AspectJ编译过,其中hasAspectAnnotation(Class<?> clazz)内容很明显就不多提。倒是compiledByAjc(Class<?> clazz)的实现比较特别。它的实现是用字段前缀来判断是否为"code-style" aspects,看起来是一种比较Hack的方法。

这里我有一个疑惑点,就是"code-style" aspects和"annotation-style" aspects的具体所指,查了一圈也没有看到明确的解释。只在 IDEA的帮助文档 Overview of AspectJ support这段上有看到相关的解释。我的理解是"code-style"是由AspectJ Language所定义的aspect,会由AspectJ来编译,而"annotation-style"则是由使用了@Aspect注解的Java语言所定义的aspect,如有错误烦请指出。

入口在Bean的生命周期中

通过一步步阅读和调试,可以一层一层向上找到org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java中有两个入口。

postProcessBeforeInstantiation和postProcessAfterInitialization,是Bean生命周期中的两个步骤:

  • postProcessBeforeInstantiation: 在Bean实例化之前执行
  • postProcessAfterInitialization: 在Bean初始化之后执行

postProcessBeforeInstantiation:

/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
...
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
    Object cacheKey = getCacheKey(beanClass, beanName);

    if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
        if (this.advisedBeans.containsKey(cacheKey)) {
            return null;
        }
        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return null;
        }
    }

    // Create proxy here if we have a custom TargetSource.
    // Suppresses unnecessary default instantiation of the target bean:
    // The TargetSource will handle target instances in a custom fashion.
    TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
    if (targetSource != null) {
        if (StringUtils.hasLength(beanName)) {
            this.targetSourcedBeans.add(beanName);
        }
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
        Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    return null;
}
...
           

这个方法的目的是将含有custom TargetSource的bean进行增强处理。可分为两部份,前半部分利用缓存和几个方法判断是否需要增强。后半部分则进入主题判断是否含有custom TargetSource。不过这里我对custom TargetSource不是特别理解,也没有细看,因为通过@Aspect注解配置不会执行这里面的代码,留着以后有时间再看。

这里还有另外两个方法:

  • isInfrastructureClass(是否是基础类,如Advice、Pointcut、Advisor、AopInfrastructureBean 及其超类)
  • shouldSkip(主要目的是判断是否是已注册的@Aspect配置Bean,其实扫描@Aspect注解配置的方法就在这里面被调用到了,这个后面再说)

postProcessAfterInitialization

/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java
....
/**
 * Create a proxy with the configured interceptors if the bean is
 * identified as one to proxy by the subclass.
 * @see #getAdvicesAndAdvisorsForBean
 */
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

/**
 * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
 * @param bean the raw bean instance
 * @param beanName the name of the bean
 * @param cacheKey the cache key for metadata access
 * @return a proxy wrapping the bean, or the raw bean instance as-is
 */
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // Create proxy if we have advice.
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}
           

可以看到关键内容就在wrapIfNecessary里面。顾名思义:必要时转成AOP代理。前半部分判断是否是不需要增强的,跟postProcessBeforeInstantiation的前半部分有点类似。后半部分根据是否有合适的Advice方法,有则将Bean转成代理。

好了,这里其实就是整个流程最关键的两个地方了:

  • getAdvicesAndAdvisorsForBean(获取适合该Bean的Advice方法,里面包含了扫描@Aspect注解配置Bean的方法)
  • createProxy(创建AOP代理,里面包含了AOP代理的实现)

这两个方法的具体内容,将在接下来的文章介绍