背景描述

假设这样一个场景,在父应用上下文中持有一个类的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 <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-api-pointcuts">5.4.3小节. Declaring a Pointcut</a>* */@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 {// 记录当前应用上下文的IDprivate 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 {@Beanpublic HelloService helloService(@Autowired ApplicationContext context){return new HelloService(context.getId());}@Beanpublic 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 {@Beanpublic 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 应用上下文 IDparentContext.setId("parentContext");parentContext.register(ParentContextConfig.class);parentContext.refresh();// 创建一个子 Spring 应用上下文AnnotationConfigApplicationContext subContext =new AnnotationConfigApplicationContext();// 设置 子 Spring 应用上下文 IDsubContext.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 namesList<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中的目标方法时去按顺序执行这些通知即可。

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

  1. vue父组件异步获取动态数据传递给子组件获取不到值

    原理: 在父组件中使用axios获取异步数据传给子组件,但是发现子组件在渲染的时候并没有数据,在created里面打印也是空的,结果发现一开始子组件绑定的数据是空的,在请求数据没有返回数据时,子组件就 ...

  2. vue 父组件使用keep-alive和infinite-scroll导致在子组件触发父组件的infinite-scroll方法...

    (vue.js)vue 父组件使用keep-alive和infinite-scroll导致在子组件触发父组件的infinite-scroll方法"问题疑问,本网通过在网上对" (v ...

  3. mysql 递归查找父节点_MySQL递归查询树状表的子节点、父节点具体实现

    简介:mysql5.0.94版本,该版本以及较高级的版本(5.5.6等等)尚未支持循环递归查询,和sqlserver.oracle相比,mysql难于在树状表中层层遍历的子节点.本程序重点参考了下面的 ...

  4. matlab如何在文中定义函数

    matlab如何在文中定义函数 通常我们多是将函数单独在编写在单个的m文件中,再于主程序中调用.但是,有时候觉得程序太小,直接在程序末尾编写函数,又会收到"此上下文中不允许函数定义" ...

  5. Spring容器初始化Bean、销毁Bean前所做操作的定义方式汇总

    1.通过@javax.annotation.PostConstruct和@javax.annotation.PreDestroy定义 package com.xiaochuange.platform. ...

  6. vue 事件调用 传参_vue如何在父组件指定点击事件后向子组件传递参数并调用子组件的事件?...

    可以给父组件写一个ref属性,父组件可以通过ref拿到子组件中的数据和方法(即例子中子组件的say方法),这样在父组件中就可以触发子组件的事件了.而父组件向子组件传参可以通过prop属性(即例子中的f ...

  7. elementui tree获取父节点_elementUI 树状图 点击子节点获取父节点

    权限使用elementUI tree 组件,点击子节点获取对应多级的父节点 这是应用的组件 :data="hovePermissData" :default-checked- ke ...

  8. element tree 父级勾选子级也勾选,子级勾选默认父级也勾选, 子级取消勾选不影响父级勾选(前端)

    在做菜单和菜单按钮权限配置这块,用element-UI中的tree组件来做.想选中单个页面,或者单个页面的某个具体按钮,就能把所有上级的父级菜单也勾选上,而且取消菜单按钮的时候还不影响父级菜单的勾选. ...

  9. Vue父组件mounted执行完后再执行子组件mounted执行顺序问题

    文章目录 一.vue加载顺序? 二.父组件和子组件 1.封装ip地址框 2.前端从数据库取数据并且赋值 bug 解决方案 一.vue加载顺序? 父组件 created 子组件 created 子组件 ...

  10. 子之错父之过什么意思_生活|为什么子不教,父之过,这是什么意思?

    原标题:生活|为什么子不教,父之过,这是什么意思? 我在大学生父母群里发现:中国家庭一般是母亲管教孩子,母亲作为孩子的引路人.教育人.每逢小升初或中考.高考,母亲聚在一起操心.议论孩子的前途是最常见的 ...

最新文章

  1. hbase delete.deleteColumns问题
  2. Content-Type简要说明
  3. MapTask并行度决定机制、FileInputFormat切片机制、map并行度的经验之谈、ReduceTask并行度的决定、MAPREDUCE程序运行演示(来自学笔记)
  4. Golang.org不能访问解决方法
  5. u8系统怎么连接服务器,用友U8 怎么连接远程服务器
  6. 信息学奥赛一本通 1956:【11NOIP普及组】表达式的值 | 洛谷 P1310 [NOIP2011 普及组] 表达式的值
  7. Delphi-IOCP API代码的封装和流程分析
  8. 力扣589. N叉树的前序遍历 (JavaScript)
  9. sql azure 语法_Azure SQL Server自动故障转移组
  10. CISCO路由器如何删除配置及%%non-valatile configuration memory is not present无法保存配置...
  11. 微云百度云等资源 至少1M/s下载提速 无需会员
  12. 2019年7月2日 星期二(韩天峰的建议)
  13. docker之数据挂载端口暴漏
  14. MySQL索引(最左匹配查询规则)
  15. 回顾 深度学习 实验三 线性回归
  16. JQuery .find()方法查找
  17. 动手开发qq一键群发软件!附源码
  18. MySQL数据库 高级命令的使用与讲解
  19. Fabric 1.0源代码分析(34) Peer #peer chaincode命令及子命令实现
  20. Easy-Forex外汇平台

热门文章

  1. php 当请求被取消,jQuery ajax请求被取消会减慢当前请求
  2. python代码怎么样_python代码怎样清屏
  3. Otsu‘s Thresholding的工作原理
  4. Swift 5 UIStackView中添加自动换行的Label
  5. VIO,visual-inertial odometry)即视觉惯性里程计
  6. FrameLayout AbsoluteLayout GridLayout用法及实例
  7. HTML lt input gt 标签,科技常识:使用amp;lt;labelamp;gt;标签修改input[type=checkbox]的样式...
  8. android版git中国只有,GitHub - ynztlxdeai/android-app: 本项目已经迁移到 git.oschina.net ,此处不再更新!...
  9. js中立即执行函数会预编译吗_javascript引擎执行的过程的理解--执行阶段
  10. pil对图像加透明 python_分享一个骚操作,用 Python 来 P 图