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代理的实现)
这两个方法的具体内容,将在接下来的文章介绍