• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

SpringMVC父子容器引起的bean重复加载问题

武飞扬头像
小李_同志
帮助1

背景

在维护一个比较的老的MVC项目时,不同的service的引用同一个service方法出现的不同结果的问题。涉及的几个类关系大概是下图 学新通

X实现ApplicationListener接口,在onApplicationEvent中会对map进行初始化,并且根据firstLoad标识只会初始化一次。 当A调用X.getMap时返回的map是正常的,而B去调用X.getMap时则返回null。

问题分析过程

考虑多例问题

见到这个问题第一反应就感觉应该调用的不是同一个bean。但又被自己反驳了,如果bean多例的话,使用@Autowired注入的时候就应该抛出异常了,还是需要实际验证一下

验证beanName是否相同

通过BeanNameAware获取一下beanName,发现A和B调用时返回的beanName是一样的。看起来像是同一个Bean。

  1.  
    beanName is com.lsz.test.service.impl.XServiceImpl#0
  2.  
     
  3.  
    beanName is com.lsz.test.service.impl.XServiceImpl#0

验证hashCode是否相同

直接调用toString方法,发现的确不是同一个类。

  1.  
    com.lsz.test.service.impl.XServiceImpl@fb547d
  2.  
     
  3.  
    com.lsz.test.service.impl.XServiceImpl@adc23e

验证重复加载

根据hashCode可以看出这是两个bean无疑,给X加上无参构造方法,打印堆栈调用信息。(这里通过在构造方法中直接抛出异常,通过catch捕获打印调用链) 发现类的确被加载了两次,但调用链却不相同,可以看到从16行开始就不一样了。 学新通

mvc重复扫包

看到类被重复加载了,考虑mvc配置文件中配置 component-scan 配置项

  1.  
    <context:component-scan base-package="com.lsz1.**" name-generator="org.springframework.beans.factory.support.DefaultBeanNameGenerator" />
  2.  
    <context:component-scan base-package="com.lsz2.**" name-generator="org.springframework.beans.factory.support.DefaultBeanNameGenerator" />

发现mvc配置文件中不仅仅扫了controller包,还把项目中所有的包都扫了。再检查spring的扫包配置,和mvc几乎相同,这肯定是不合理的。

  1.  
    <context:component-scan base-package="com.lsz1" name-generator="org.springframework.beans.factory.support.DefaultBeanNameGenerator" />
  2.  
    <context:component-scan base-package="com.lsz2" name-generator="org.springframework.beans.factory.support.DefaultBeanNameGenerator" />

解决问题

修改mvc扫包配置

理论上mvc只用去扫controller类就可以了,其他的如service、mapper等等可以交给spring处理。这里的配置我是简写的,实际上很多。这是一套历史悠久的代码,为了尽量减少蝴蝶效应的产生,我不太建议改mvc的配置项。 mvc.xml

  1.  
    <context:component-scan base-package="com.lsz1.**.controller" name-generator="org.springframework.beans.factory.support.DefaultBeanNameGenerator" />
  2.  
    <context:component-scan base-package="com.lsz2.**.controller" name-generator="org.springframework.beans.factory.support.DefaultBeanNameGenerator" />

修改map初始化数据方法

估计最初通过boolean类型firstLoad控制加载时,也是为了重复加载时引起的系列问题。这里改成通过synchronized控制map初始化数据时的同步问题。对于我遇到的这个问题,是通过这种方式解决的,影响面小。

  1.  
    private static Boolean firstLoad = true;
  2.  
    private static Object LOAD_LOCK = new Object();
  3.  
     
  4.  
    @Override
  5.  
    public void onApplicationEvent(ContextRefreshedEvent event) {
  6.  
     
  7.  
    // if (firstLoad) {
  8.  
    // firstLoad = false;
  9.  
    // doSomething
  10.  
    // }
  11.  
     
  12.  
    synchronized (LOAD_LOCK) {
  13.  
    // doSomething
  14.  
    }
  15.  
    }
  16.  
     
学新通

原理分析

spring父子容器

通过spring和mvc重复扫包,想到spring父子容器的概念。

  1. 父容器和子容器之间相互隔离,所以各自内部beanName可以重复。
  2. 子容器可以访问父容器,父容器不可以访问子容器
  3. 子容器中的bean调用时优先在自身的容器中获取bean,自身容器中找不到时,再向上一级父容器中查找。

spring和mvc

MVC项目中Spring是父容器,MVC是子容器,根据以上三个概念可以分析出

  1. 因为概念1,所以验证beanName时,得到的结果两个相同的beanName,而hashcode不同,说明这两个bean却不在一个容器中。
  2. 因为概念2、3,A自己就属于父容器,所以访问的父容器中的bean,这个beanX中的map数据是初始化过的;B属于子容器访问的是子容器中的bean,子容器beanX中的map没有经过初始化。

后续再写博文分析spirngmvc父子容器源码。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbfahb
系列文章
更多 icon
同类精品
更多 icon
继续加载