对于相同Bean,在父应用上下文中定义的切面,在子应用上下文中会生效吗?

背景描述

假设这样一个场景,在父应用上下文中持有一个类的Bean实例,并针对这个Bean定义了相关切面,而在子容器中也持有和父应用上下文相同类的Bean实例,但未定义针对该Bean的切面,那么当从子应用上下文中获取该Bean实例并执行其方法时,定义在父应用上下文中的切面会生效吗?

结论

会生效。因为Spring AOP 在使用应用上下文(ApplicationContext)查找切面时,会使用层次性查找。虽然在子容器中未定义切面,但是会把父容器中的切面找到。

Talk is cheap. Show me the code

第一步:定义一个切面类

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方法,查看控制台打印结果。对于相同Bean,在父应用上下文中定义的切面,在子应用上下文中会生效吗?_第1张图片
可以看到,虽然未在子应用上下文中声明 HierarchyAspect 切面,但是在子应用上下文中获取 HelloService实例,并执行say方法时,HierarchyAspect 切面的 afterReturning通知依然是生效的。接下来我们就开始进入底层行为分析。

底层行为分析

要分析底层原理,就必须查看AbstractAutoProxyCreator的postProcessAfterInitialization方法(至于该类是如何注册到IoC容器的,这里就不再分析了,因为这涉及到Spring 中的模块驱动,比较繁琐,以后再单独拎出来分析)。

先查看下该类关系图,可以发现其实现了BeanPostProcessor接口,这意味着该类可以在Bean生命周期中来做一些事情。对于相同Bean,在父应用上下文中定义的切面,在子应用上下文中会生效吗?_第2张图片
在其实现的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方法。

对于相同Bean,在父应用上下文中定义的切面,在子应用上下文中会生效吗?_第3张图片

由于是首次执行该方法,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中的目标方法时去按顺序执行这些通知即可。

你可能感兴趣的:(Spring,AOP,Spring,Context,aop,spring,spring,boot,java,spring,cloud)