假设这样一个场景,在父应用上下文中持有一个类的Bean实例,并针对这个Bean定义了相关切面,而在子容器中也持有和父应用上下文相同类的Bean实例,但未定义针对该Bean的切面,那么当从子应用上下文中获取该Bean实例并执行其方法时,定义在父应用上下文中的切面会生效吗?
会生效。因为Spring AOP 在使用应用上下文(ApplicationContext)查找切面时,会使用层次性查找。虽然在子容器中未定义切面,但是会把父容器中的切面找到。
第一步:定义一个切面类
package com.xxx.fame.hierarchy.aop.config;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
/***
* 声明一个切面
* @author junzhan
* */
@Aspect
public class HierarchyAspect {
/***
* 切点表达式支持 bean(beanNameOrBeanId) 这种方式。
* 详细了解请查看官方文档
* @see 5.4.3小节. Declaring a Pointcut
* */
@AfterReturning("bean(helloService*)")
public void afterReturning() {
System.out.println("afterReturning Hello!");
}
}
第二步,定义一个Service,这里为了演示方便,并没有定义接口,直接使用了类
package com.xxx.fame.hierarchy.aop.service;
/***
* 定义一个Service
* @author junzhan
* */
public class HelloService {
// 记录当前应用上下文的ID
private final String currentContextId;
public HelloService(String currentContextId) {
this.currentContextId = currentContextId;
}
public void say() {
System.out.printf("say :%s hello! \n", currentContextId);
}
}
第三步:定义父应用上下文配置类,将切面声明为父容器的一个Bean
package com.xxx.fame.hierarchy.aop.config;
import com.xxx.fame.hierarchy.aop.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/***
* 父Spring ApplicationContext 配置类
* @author junzhan
* @see ApplicationContext
* */
@Configuration
@EnableAspectJAutoProxy // 启用Spring AOP
public class ParentContextConfig {
@Bean
public HelloService helloService(@Autowired ApplicationContext context){
return new HelloService(context.getId());
}
@Bean
public HierarchyAspect customizedAspect(){
return new HierarchyAspect();
}
}
第四步:定义子应用上下文配置类
package com.xxx.fame.hierarchy.aop.config;
import com.xxx.fame.hierarchy.aop.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/***
* 子Spring ApplicationContext 配置
* @author junzhan
* @see ApplicationContext
* */
@Configuration
@EnableAspectJAutoProxy // 启用Spring AOP
public class SubContextConfig {
@Bean
public HelloService helloService(@Autowired ApplicationContext context){
return new HelloService(context.getId());
}
}
第五步:编写启动类(测试类)
package com.xxx.fame.hierarchy.aop;
import com.xxx.fame.hierarchy.aop.config.ParentContextConfig;
import com.xxx.fame.hierarchy.aop.config.SubContextConfig;
import com.xxx.fame.hierarchy.aop.service.HelloService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/***
* 父子容器场景中的AOP
* 启动类
* @author junzhan
* **/
public class ApplicationContextHierarchyAOPDemo {
public static void main(String[] args) {
// 创建一个父 Spring 应用上下文
AnnotationConfigApplicationContext parentContext
= new AnnotationConfigApplicationContext();
// 设置 父 Spring 应用上下文 ID
parentContext.setId("parentContext");
parentContext.register(ParentContextConfig.class);
parentContext.refresh();
// 创建一个子 Spring 应用上下文
AnnotationConfigApplicationContext subContext =
new AnnotationConfigApplicationContext();
// 设置 子 Spring 应用上下文 ID
subContext.setId("subContext");
// 设置子容器的父容器
subContext.setParent(parentContext);
subContext.register(SubContextConfig.class);
subContext.refresh();
// 从父 Spring 应用上下文 获取 HelloService 实例
HelloService helloServiceOfParentContext = parentContext.getBean(HelloService.class);
System.out.println("================开始执行目标方法================");
helloServiceOfParentContext.say();
System.out.println("================================================");
// 从子 Spring 应用上下文 获取 HelloService 实例
HelloService helloServiceOfSubContext = subContext.getBean(HelloService.class);
helloServiceOfSubContext.say();
}
}
第六步:启动main方法,查看控制台打印结果。
可以看到,虽然未在子应用上下文中声明 HierarchyAspect 切面,但是在子应用上下文中获取 HelloService实例,并执行say方法时,HierarchyAspect 切面的 afterReturning通知依然是生效的。接下来我们就开始进入底层行为分析。
要分析底层原理,就必须查看AbstractAutoProxyCreator的postProcessAfterInitialization方法(至于该类是如何注册到IoC容器的,这里就不再分析了,因为这涉及到Spring 中的模块驱动,比较繁琐,以后再单独拎出来分析)。
先查看下该类关系图,可以发现其实现了BeanPostProcessor接口,这意味着该类可以在Bean生命周期中来做一些事情。
在其实现的postProcessAfterInitialization方法中,对于传入的每一个Bean,首先调用getCacheKey方法来创建缓存Key,接下来再尝试从earlyProxyReferences集合中根据该缓存key进行移除(这涉及到IoC容器中的早期Bean以及循环依赖问题,以后单独拎出来分析),如果移除结果不等于传入的bean,执行wrapIfNecesary方法并返回该方法执行结果。
// AbstractAutoProxyCreator#postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
重点就是这个wrapIfNecessary方法,这里删除了与本次分析无关的代码,详细源码请查看AnstractAutoProxyCreator的wrapIdNecessary方法,而本次分析重点是该方法中调用的shouldSkip方法。在调用shouldSkip方法时传入了两个参数,分别为bean的class以及beanName。
// AbstractAutoProxyCreator#wrapIfNecessary
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// // 删除与本次分析无关代码......
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 删除与本次分析无关代码......
return bean;
}
shouldSkip方法定义在AbstractAutoProxyCreator类中,但该类只是一个抽象类,AspectJAwareAdvisorAutoProxyCreator重写了该方法。在该实现中,首先调用了findCandidateAdvisors,顾名思义就是查找合适的通知器。
// AspectJAwareAdvisorAutoProxyCreator#shouldSkip
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
// TODO: Consider optimization by caching the list of the aspect names
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// 删除与本次分析无关的代码.....
return super.shouldSkip(beanClass, beanName);
}
findCandidateAdvisor方法定义在AbstractAdvisorAutoProxyCreator类中,但AnnotationAwareAspectJAutoProxyCreator重写了该方法,在该方法中首先调用父类的findCandidateAdvisors方法。在Spring AOP场景中,该方法返回值通常为空,因为Spring AOP默认并未直接注册Advisor,但在声明式事务场景下,这里返回值就不为空了,因为声明式事务默认注册了一个Advisor。
接下来判断aspectJAdvisorsBuilder 是否不等于null,通常情况下判断成立(因为该属性的赋值是在initBeanFactory方法中完成的,而initBeanFactory方法在setBeanFactory方法中调用。还记得上面的AbstractAutoProxyCreator的类关系图吗?该类实现了BeanFactoryAware接口,因此IoC容器会回调其实现的setBeanFactory方法),调用aspectJAdvisorsBuilder的buildAspectJAdvisors方法。
protected List<Advisor> findCandidateAdvisors() {
// Add all the Spring advisors found according to superclass rules.
List<Advisor> advisors = super.findCandidateAdvisors();
// Build Advisors for all AspectJ aspects in the bean factory.
if (this.aspectJAdvisorsBuilder != null) {
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
// AnnotationAwareAspectJAutoProxyCreator#initBeanFactory
@Override
protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 删除与本次分析无关的代码.....
this.aspectJAdvisorsBuilder =
new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory, this.aspectJAdvisorFactory);
}
// AbstractAdvisorAutoProxyCreator#setBeanFactory
public void setBeanFactory(BeanFactory beanFactory) {
// 删除与本次分析无关的代码.....
initBeanFactory((ConfigurableListableBeanFactory) beanFactory);
}
ok,现在让我们把目光回到BeanFactoryAspectJAdvisorsBuilder的buildAspectJAdvisors方法中,至于为什么是该类而不是前面我们看到的实例化的BeanFactoryAspectJAdvisorsBuilderAdapter,是因为BeanFactoryAspectJAdvisorsBuilderAdapter继承自BeanFactoryAspectJAdvisorsBuilder,并且也没有重写这个方法。其只重写了父类的isEligiableBean方法。
由于是首次执行该方法,aspectBeanNames肯定是等等于null,满足接下来的if判断,首先使用synchronized关键字进行加锁,监视器对象为this,进行DoubleCheck,如果检查后发现aspectBeanNames确实为null,那么则调用BeanFactoryUtils的beanNamesForTypeIncludingAncestors方法,注意这是根据指定类型的层次性查找(即如果当前IoC容器存在父容器,那么也会去查找父容器中相关类型Bean的beanName,如果父容器还有父容器,也是如此),这里指定要查找的类型为Object,意味着要查找出所有IoC容器中的所有类型的beanName,因为所有类都继承自Object类。
private volatile List<String> aspectBeanNames;
// BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors
public List<Advisor> buildAspectJAdvisors() {
List<String> aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new ArrayList<>();
aspectNames = new ArrayList<>();
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true, false);
for (String beanName : beanNames) {
// 删除与本次分析无关的代码.....
}
this.aspectBeanNames = aspectNames;
return advisors;
}
}
}
// 删除与本次分析无关的代码.....
return advisors;
}
看到这里我们就能明白为什么在子容器中并未针对HelloService定义任何通知,但是HierarchyAspect 切面的 afterReturning 通知依然是生效的,这是因为Spring AOP在查找切面Bean时,是层次性的查找,虽然我们没在子容器中定义,但在父容器中定义了,所以依然会被查找到。
如果存在父子容器关系时,在父容器中针对某个Bean或者指定条件下的Bean进行增强,即使子容器并未针对这些Bean进行增强,但是从子容器中去获取这些Bean,获取到的依然是被增强后的Bean,因为Spring AOP在查找切面Bean时,是会层次性地去查找,把子容器及其父容器或者父容器的父容器…中的所有的切面Bean都找到。
既然找到切面Bean就好办了,接下来就是判断Bean是否满足切面Bean中的通知条件,如果满足,则为Bean创建代理,并将满足的这些通知设置到代理对象中,在执行Bean中的目标方法时去按顺序执行这些通知即可。