Spring和SpringMVC作为Bean管理容器和MVC层的默认框架,已被众多WEB应用采用,而实际使用时,由于有了强大的注解功能,很多基于XML的配置方式已经被替代,但是在实际项目中,同时配置Spring和SpringMVC时会出现一些奇怪的异常,比如Bean被多次加载,多次实例化,或者依赖注入时,Bean不能被自动注入,但是明明你已经将该Bean注册了的。找原因还是要看问题的根源,我们从容器说起。
在Spring整体框架的核心概念中,容器是核心思想,就是用来管理Bean的整个生命周期的,而在一个项目中,容器不一定只有一个,Spring中可以包括多个容器,而且容器有上下层关系,目前最常见的一种场景就是在一个项目中引入Spring和SpringMVC这两个框架,其实就是2个容器,Spring是根容器,SpringMVC是其子容器,并且在Spring根容器中对于SpringMVC容器中的Bean是不可见的,而在SpringMVC容器中对于Spring根容器中的Bean是可见的,也就是子容器可以看见父容器中的注册的Bean,反之就不行。理解这点很重要,因为这是一个规则,是Spring自己设定的,但是往下看,我们会发现有些地方它并不默认使用这个规则。
当我们使用注解时,对于Bean注册这个功能的实现就不需要在给每个Bean配置XML了,只要使用统一的如下配置即可。
?
1
<context:component-scan base-package=“com.test" />
根据Spring提供的参考手册,该配置的功能是扫描默认包下的所有的@Component注解,并且自动注册到容器中,同时也扫描@Controller,@Service,@Respository这三个注解,他们是继承自@Component。
除了以上我们使用的扫描配置,在项目中我们经常见到的就是<context:annotation-config/>这个配置,其实有了以上的配置,这个是可以省略掉的。
还有一个SpringMVC相关的是<mvc:annotation-driven />配置,经过验证,这个是必须要配置的,因为它是和@RequestMapping结合使用的,这里补充下SpringMVC框架相关的知识点。

HandlerMapping,是SpringMVC中用来处理Request请求URL到具体Controller的,其自身也分成很多种类; 
HandlerAdapter,是SpringMVC中用来处理具体请求映射到具体方法的,其自身也分很多种类;

@RequestMapping这个注解的主要目的就是对具体的Controller和方法进行注册,以方便HandlerMapping用来处理请求的映射。但是@RequestMapping需要结合<mvc:annotation-driven />使用才能生效。

好了,有了以上基础知识的铺垫,我们看下现在这样的一个使用场景中,Spring与SpringMVC的容器冲突的原因在那里!
Spring配置文件applicationContext.xml,SpringMVC配置文件applicationContext-MVC.xml,这样项目中就有2个容器了,配置方式A,如下:
applicationContext.xml中配置了<context:component-scan base-package=“com.test" />,负责所有需要注册的Bean的扫描工作,applicationContext-MVC.xml中配置<mvc:annotation-driven />,负责springMVC相关注解的使用,启动项目发现,springMVC失效,无法进行跳转,开启log的DEBUG级别进行调试,发现springMVC容器中的请求好像没有映射到具体controller中;
配置方式B,如下:
为了快速验证效果,将<context:component-scan base-package=“com.test" />扫描配置到applicationContext-MVC.xml中,重启后,验证成功,springMVC跳转有效。
要想查看具体原因,翻看源码,从springMVC的DispatcherServlet开始看,在一个请求进来之后,发生了什么?漫长的查看之后,找到原因,如下。
springMVC初始化时,会寻找所有当前容器中的所有@Controller注解的Bean,来确定其是否是一个handler,而当前容器springMVC中注册的Bean中并没有@Controller注解的,注意,上面提及的配置方式A,所有的@Controller配置的Bean都注册在Spring这个父容器中了,看代码。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }
        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));
        for (String beanName : beanNames) {
            if (isHandler(getApplicationContext().getType(beanName))){
                detectHandlerMethods(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

在方法isHandler中会判断当前bean的注解是否是controller,代码如下:
?
1
2
3
protected boolean isHandler(Class<?> beanType) {
        return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;
    }

在配置方式B中,springMVC容器中包括了所有的@Controller注解的Bean,所以自然就能找到了。
以上是原因,解决办法是什么?注意看initHandlerMethods()方法中,detectHandlerMethodsInAncestorContexts这个Switch,它主要控制从那里获取容器中的bean,是否包括父容器,默认是不包括的。所以解决办法是有的,即在springMVC的配置文件中配置HandlerMapping的detectHandlerMethodsInAncestorContexts属性为true即可(这里需要根据具体项目看使用的是哪种HandlerMapping),让其检测父容器的bean。如下:
?
1
2
3
4
5
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="detectHandlerMethodsInAncestorContexts">
            <value>true</value>
        </property>
    </bean>

以上已经有了2种解决方案了,但在实际工程中,会包括很多配置,根据不同的业务模块来划分,所以我们一般思路是各负其责,明确边界,Spring根容器负责所有其他非controller的Bean的注册,而SpringMVC只负责controller相关的Bean的注册。第三种方案如下:

Spring容器配置,排除所有@controller的Bean

?
1
2
3
<context:component-scan base-package="com.fsnip.open">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

SpringMVC容器配置,让其只包括@controller的Bean

?
1
2
3
<context:component-scan base-package="com.fsnip.open" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

个人比较推荐第三种方案。引申一下,项目中使用事务的配置方案,也会在这种场景下失效,归根结底也是由于2个容器的可见性问题导致,可以结合具体问题按照上面的思路进行查找原因!
引用一下海涛的博客中的一张图。 原图地址http://jinnianshilongnian.iteye.com/blog/1602617

转载开涛博客

转载于:https://www.cnblogs.com/zyzcj/p/5286190.html

Spring 和SpringMVC 的父子容器关系相关推荐

  1. Spring和SpringMVC的父子容器关系

    容器 在Spring整体框架的核心概念中,容器是核心思想 就是用来管理Bean的整个生命周期的 在一个项目中,容器不一定只有一个 Spring中可以包括多个容器,而且,容器有上下层关系 一个项目中引入 ...

  2. Spring和SpringMVC父子容器关系初窥

    一.背景 最近由于项目的包扫描出现了问题,在解决问题的过程中,偶然发现了Spring和SpringMVC是有父子容器关系的,而且正是因为这个才往往会出现包扫描的问题,我们在此来分析和理解Spring和 ...

  3. 探究Spring和SpringMVC父子容器关系

    兄弟萌,相信有很多人想不到 Spring 和 SpringMVC 是父子容器吧,^ - ^,下面我们来一探究竟. 一般做 SSM 框架项目时,扫描 @Controller 注解类的对象是在 Sprin ...

  4. 热门框架系列 (二) -- SpringMvc的父子容器,SpringBoot是否有父子容器?

    @TOC# 热门框架系列 记录在程序走的每一步___auth:huf 从新的篇章开始;篇章阅读需要先关注; 因为笔者想参与技术文章的评选.;需要一定的粉丝量; 粉丝量达到一定数量.所有文章阅读限制将会 ...

  5. Spring和springmvc两个容器的关系

    1.如果子容器(SpringMVC)的包扫描时,把service类扫进去SpringMVC中,会出现配置的事务不生效.因为这样controller会先调用子容器里的service,而子容器里的serv ...

  6. 理解spring父子容器

    一.Spring和SpringMVC的父子容器关系 一般来说,我们在整合Spring和SpringMVC这两个框架中,web.xml会这样写到: <!-- 加载spring容器 --> & ...

  7. Spring父子容器的关系分析--用实例说话

    Spring中父子容器的实现实例Spring的父子容器可以通过ConfigurableApplicationContext或ConfigurableBeanFactory来实现,这两个接口中分别有se ...

  8. boot spring 没有父子容器_Spring 系列(二):Spring MVC的父子容器

    1.背景 在使用Spring MVC时候大部分同学都会定义两个配置文件,一个是Spring的配置文件spring.xml,另一个是Spring MVC的配置文件spring-mvc.xml. 在这里给 ...

  9. 【Spring(八)】父子容器

    Spring的容器具可以具有父子关系.具体含义就是:如果两个容器具有父子关系,那么在查找bean时,会先从子容器中查找,如果没有就查找父容器.但是父容器不能查找子容器的bean. 这个特性有什么意义? ...

  10. spring的父子容器

    在创建ssm项目工程时,经常需要读取properties资源配置文件,传统的方法当然可以. 但是spring提供了更简便的方法,@value注解. 在page.properties文件中,配置分页信息 ...

最新文章

  1. java race condition_java多线程(一)Race Condition现象及产生的原因
  2. ------ 比较二位数组大小-----
  3. 变分自编码器系列:VAE + BN = 更好的VAE
  4. websocket使用
  5. 五年级信息技术上册教案计算机主机探秘,第1课信息与信息技术探秘教案
  6. 计算机应用基础山东大学,山东大学计算机应用基础
  7. eclipse配置tomcat,并部署一个Java web项目到tomcat上
  8. SqlServer2008修改编辑前200行
  9. atitit. web 在线文件管理器最佳实践(1)--- elFinder 的使用流程解决之道 。打开浏览服务器文件夹java .net php
  10. 电商数据分析方法和指标整理
  11. 在LaTeX中添加Visio绘图
  12. jQuery 身份证验证
  13. 电力拖动自动控制系统复习(四)
  14. C/C++ isalpha、isalnum、islower、isupper函数详解
  15. 证券市场的法律法规体系
  16. 高斯win和linux,微软雅黑在 win 和 Linux 下安装使用的对比
  17. python实用程序育儿法下载_Python机器学习经典实例
  18. NA公链(Nirvana)NAC公链独步公链江湖
  19. 留住幸福的办法是不断创造幸福
  20. 利用GitHub制作免费个人主页

热门文章

  1. fiddler手机抓包 443
  2. gogs仓库代码拉取不需要用户账号验证问题
  3. Layui layer.open 弹窗 关闭按钮不见了?
  4. PHP中的数组(拆分与合并)
  5. jk-fourm.php,JK车头灯与我的新年期望(已更新完毕)
  6. relocation R_X86_64_PC32 against symbol can not be used when making a shared object recompile with
  7. SHELL下把一个文件附加到另外一个文件,注意编码问题
  8. 管理:权力是用来完成责任的
  9. WINDOWS获得当前执行程序路径的办法
  10. 没有别的厂家生产薯片?