Spring和SpringMVC父子容器关系

Spring和SpringMVC父子容器关系初窥

一、背景

  最近由于项目的包扫描出现了问题,在解决问题的过程中,偶然发现了Spring和SpringMVC是有父子容器关系的,而且正是因为这个才往往会出现包扫描的问题,我们在此来分析和理解Spring和SpringMVC的父子容器关系并且给出Spring和SpringMVC配置文件中包扫描的官方推荐方式。

二、概念理解和知识铺垫

  在Spring整体框架的核心概念中,容器是核心思想,就是用来管理Bean的整个生命周期的,而在一个项目中,容器不一定只有一个,Spring中可以包括多个容器,而且容器有上下层关系,目前最常见的一种场景就是在一个项目中引入Spring和SpringMVC这两个框架,那么它其实就是两个容器,Spring是父容器,SpringMVC是其子容器,并且在Spring父容器中注册的Bean对于SpringMVC容器中是可见的,而在SpringMVC容器中注册的Bean对于Spring父容器中是不可见的,也就是子容器可以看见父容器中的注册的Bean,反之就不行。

  我们可以使用统一的如下注解配置来对Bean进行批量注册,而不需要再给每个Bean单独使用xml的方式进行配置。

<context:component-scan base-package="com.hafiz.www" />

  从Spring提供的参考手册中我们得知该配置的功能是扫描配置的base-package包下的所有使用了@Component注解的类,并且将它们自动注册到容器中,同时也扫描@Controller,@Service,@Respository这三个注解,因为他们是继承自@Component。

  在项目中我们经常见到还有如下这个配置,其实有了上面的配置,这个是可以省略掉的,因为上面的配置会默认打开以下配置。以下配置会默认声明了@Required、@Autowired、 @PostConstruct、@PersistenceContext、@Resource、@PreDestroy等注解。

<context:annotation-config/>

  另外,还有一个和SpringMVC相关如下配置,经过验证,这个是SpringMVC必须要配置的,因为它声明了@RequestMapping、@RequestBody、@ResponseBody等。并且,该配置默认加载很多的参数绑定方法,比如json转换解析器等。

<mvc:annotation-driven />

而上面这句配置spring3.1之前的版本和以下配置方式等价

<!--配置注解控制器映射器,它是SpringMVC中用来将Request请求URL到映射到具体Controller-->
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<!--配置注解控制器映射器,它是SpringMVC中用来将具体请求映射到具体方法-->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>

spring3.1之后的版本和以下配置方式等价

<!--配置注解控制器映射器,它是SpringMVC中用来将Request请求URL到映射到具体Controller-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--配置注解控制器映射器,它是SpringMVC中用来将具体请求映射到具体方法-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

三、具体场景分析

  下面让我们来详细扒一扒Spring与SpringMVC的容器冲突的原因到底在那里?

  我们共有Spring和SpringMVC两个容器,它们的配置文件分别为applicationContext.xml和applicationContext-MVC.xml。

  1.在applicationContext.xml中配置了<context:component-scan base-package=“com.hafiz.www” />,负责所有需要注册的Bean的扫描和注册工作。

  2.在applicationContext-MVC.xml中配置<mvc:annotation-driven />,负责SpringMVC相关注解的使用。

  3.启动项目我们发现SpringMVC无法进行跳转,将log的日志打印级别设置为DEBUG进行调试,发现SpringMVC容器中的请求好像没有映射到具体controller中。

  4.在applicationContext-MVC.xml中配置<context:component-scan base-package=“com.hafiz.www” />,重启后,验证成功,springMVC跳转有效。

  下面我们来查看具体原因,翻看源码,从SpringMVC的DispatcherServlet开始往下找,我们发现SpringMVC初始化时,会寻找SpringMVC容器中的所有使用了@Controller注解的Bean,来确定其是否是一个handler。1,2两步的配置使得当前springMVC容器中并没有注册带有@Controller注解的Bean,而是把所有带有@Controller注解的Bean都注册在Spring这个父容器中了,所以springMVC找不到处理器,不能进行跳转。核心源码如下:

复制代码
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,源码如下:

protected boolean isHandler(Class<?> beanType) {
  return AnnotationUtils.findAnnotation(beanType, Controller.class) != null;
}

而在第4步配置中,SpringMVC容器中也注册了所有带有@Controller注解的Bean,故SpringMVC能找到处理器进行处理,从而正常跳转。

我们找到了出现不能正确跳转的原因,那么它的解决办法是什么呢?

  我们注意到在initHandlerMethods()方法中,detectHandlerMethodsInAncestorContexts这个Switch,它主要控制获取哪些容器中的bean以及是否包括父容器,默认是不包括的。所以解决办法就是在springMVC的配置文件中配置HandlerMapping的detectHandlerMethodsInAncestorContexts属性为true即可(这里需要根据具体项目看使用的是哪种HandlerMapping),让它检测父容器的bean。如下:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
   <property name="detectHandlerMethodsInAncestorContexts">
       <value>true</value>
   </property>
</bean>

但在实际工程中会包括很多配置,我们按照官方推荐根据不同的业务模块来划分不同容器中注册不同类型的Bean:Spring父容器负责所有其他非@Controller注解的Bean的注册,而SpringMVC只负责@Controller注解的Bean的注册,使得他们各负其责、明确边界。配置方式如下

  1.在applicationContext.xml中配置:

<!-- Spring容器中注册非@controller注解的Bean -->
<context:component-scan base-package="com.hafiz.www"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>

  2.applicationContext-MVC.xml中配置

<!-- SpringMVC容器中只注册带有@controller注解的Bean -->
<context:component-scan base-package="com.hafiz.www" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan>

关于use-default-filters=”false”的作用,请参见另一篇博客:context:component-scan标签的use-default-filters属性的作用以及原理分析

三、总结

  这样我们在清楚了spring和springMVC的父子容器关系、以及扫描注册的原理以后,根据官方建议我们就可以很好把不同类型的Bean分配到不同的容器中进行管理。再出现Bean找不到或者SpringMVC不能跳转以及事务的配置失效的问题,我们就可以很快的定位以及解决问题了。很开心,有木有~

 



    感谢您花时间阅读此篇文章,如果您觉得这篇文章你学到了东西也是为了犒劳下博主的码字不易不妨打赏一下吧,让博主能喝上一杯咖啡,在此谢过了!
    如果您觉得阅读本文对您有帮助,请点一下左下角 “推荐”按钮,您的 将是我最大的写作动力!另外您也可以选择 关注我,可以很方便找到我!
    本文版权归作者和博客园共有,来源网址: http://www.cnblogs.com/hafiz 欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利!
个人微信公众号
分类: Spring, SpringMVC
15

« 上一篇: Centos7设置IP为固定值
» 下一篇: context:component-scan标签的use-default-filters属性的作用以及原理分析
    </div>
    <div class="postDesc">posted @ <span id="post-date">2016-09-16 00:24</span> <a href="http://www.cnblogs.com/hafiz/">阿豪聊干货</a> 阅读(<span id="post_view_count">17567</span>) 评论(<span id="post_comment_count">18</span>)  <a href="https://i.cnblogs.com/EditPosts.aspx?postid=5875740" rel="nofollow">编辑</a> <a href="#" onclick="AddToWz(5875740);return false;">收藏</a></div>
</div>
<script type="text/javascript">var allowComments=true,cb_blogId=267148,cb_entryId=5875740,cb_blogApp=currentBlogApp,cb_blogUserGuid='1a7bf19a-86d5-e511-9fc1-ac853d9f53cc',cb_entryCreatedDate='2016/9/16 0:24:00';loadViewCount(cb_entryId);var cb_postType=1;</script>




  
#1楼 2017-03-08 17:14 llyishi  
  
#2楼 2017-06-10 18:45 生活它不如诗  
您好。我想请问一下,按照开始的方式所有的bean都会在spring的容器中,Controller也在父容器中,既然子容器可以访问父容器中的bean,那为什么不行,非要到springMVC中注册Controller呢?
https://i-blog.csdnimg.cn/blog_migrate/1210b1a501230247f1fb5092b7908353.png
  
#3楼 2017-07-02 21:00 博学善思。。ljd  
关系是不是说反了,springMVC容器如果可以访问Spring容器的对象,那么在Spring配置文件中扫描Controller,按说是没问题的。那为什么结果确是无法找到Controller呢?
https://i-blog.csdnimg.cn/blog_migrate/bfc7cdadd4feb67e5c34b3da26bda0de.png
  
#4楼 [ 楼主] 2017-07-17 10:56 阿豪聊干货  
@ 生活它不如诗
子容器springmvc是可以访问到父容器spring中的bean,但是默认情况下,springmvc只会在自己容器中查找所有的controller并不会去父容器中查找,所以把所有的bean都注册到spring容器中,访问不到。当然我们也有解决办法,文中已经给出,谢谢!
https://i-blog.csdnimg.cn/blog_migrate/8b4db63289e2c21f2e79b6e6419a7f96.png
  
#5楼 [ 楼主] 2017-07-17 10:58 阿豪聊干货  
@ 博学善思。。ljd
没有说反,就是springmvc是子容器,spring是父容器,但是检测controller时springmvc默认只会在自己容器中查找带有@Controller注解的bean,而不会去父容器中找,所以导致找不到,当然也有办法可以解决,文中已经给出。谢谢!
https://i-blog.csdnimg.cn/blog_migrate/8b4db63289e2c21f2e79b6e6419a7f96.png
  
#6楼 [ 楼主] 2017-07-30 18:16 阿豪聊干货  
@ llyishi
哈哈,谢谢!
https://i-blog.csdnimg.cn/blog_migrate/8b4db63289e2c21f2e79b6e6419a7f96.png
  
#7楼 2017-08-10 19:12 Youth.霖  
为什么要单独搞一个子容器出来呢,Web 中也用同一个父容器不行吗
  
#8楼 [ 楼主] 2017-08-10 19:14 阿豪聊干货  
@ Youth.霖
理论上来说是可以的,但是为了系统的扩展性以及各个组件的解耦开来,也就是隔离开,我们一般还是使用两者的结合使用。
https://i-blog.csdnimg.cn/blog_migrate/8b4db63289e2c21f2e79b6e6419a7f96.png
  
#9楼 2017-09-06 10:02 luchi123  
z这篇文章 完全解决了我的事务问题 谢谢楼主
  
#10楼 [ 楼主] 2017-09-13 10:04 阿豪聊干货  
@ luchi123
嘿嘿,能帮到你我也很开心~
https://i-blog.csdnimg.cn/blog_migrate/8b4db63289e2c21f2e79b6e6419a7f96.png
  
#11楼 2017-12-15 16:19 菜鸟小东  
点个赞楼主,问题描述的非常清晰呢,真棒,冒昧的问一下哈,楼主学java几年啦?
  
#12楼 2018-01-03 09:18 publicName  
你好,我有一个问题请教一下。
我在项目中没有application.xml文件,直接全部写在mvc.xml文件中,那么所有的bean是注册在了mvc容器中吗?
我在写定时任务的时候,使用了autowired注入service,失败了,只能使用resource注解。
但是为什么在controller service之中使用autowired注入就能成功呢?
我看网上说autowired是spring的注解,那如果是bean都注册在mvc容器中,应该哪里都不会注入成功啊?这里一直搞不懂。
希望能帮助我解答,十分感谢。
  
#13楼 [ 楼主] 2018-01-11 16:37 阿豪聊干货  
@ 菜鸟小东
引用
点个赞楼主,问题描述的非常清晰呢,真棒,冒昧的问一下哈,楼主学java几年啦?

学java已有6年有余~
https://i-blog.csdnimg.cn/blog_migrate/8b4db63289e2c21f2e79b6e6419a7f96.png
  
#14楼 [ 楼主] 2018-01-11 16:38 阿豪聊干货  
@ publicName
引用
你好,我有一个问题请教一下。
我在项目中没有application.xml文件,直接全部写在mvc.xml文件中,那么所有的bean是注册在了mvc容器中吗?
我在写定时任务的时候,使用了autowired注入service,失败了,只能使用resource注解。
但是为什么在controller service之中使用autowired注入就能成功呢?
我看网上说autowired是spring的注解,那如果是bean都注册在mvc容器中,应该哪里都不会注入成功啊?这里一直搞不懂。
希望能帮助我解答,十分感谢。

私聊你了哈~
https://i-blog.csdnimg.cn/blog_migrate/8b4db63289e2c21f2e79b6e6419a7f96.png
  
#15楼 2018-04-04 09:23 HansonYao  
在使用 springmvc的配置为
<beans xmlns=” http://www.springframework.org/schema/beans” xmlns:xsi=” http://www.w3.org/2001/XMLSchema-instance
xsi:schemaLocation=” http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd“>
</beans>
时,不需要在Dispatcher中额外声明controller,验证好像没毛病
  
#16楼 [ 楼主] 2018-05-10 09:28 阿豪聊干货  
@HansonYao
这样空的声明不会有问题?没get到你的点
https://i-blog.csdnimg.cn/blog_migrate/8b4db63289e2c21f2e79b6e6419a7f96.png
  
#17楼 2018-05-18 10:52 就叫我大哥好了  
好文,昨天遇到一个问题,自定义aspect切面的时候,<aop:aspectj-autoproxy proxy-target-class=”true”/>只有将这个配置在mvc.xml才生效,配置在spring.xml就没有生效,不知道楼主是否有这方面的了解!2.一般spring.xml都会引入<import resource=”springmvc.xml”/>,name所有spring中的bean是否都注册的到了父容器?
https://i-blog.csdnimg.cn/blog_migrate/481584e509cb5ff259f7a899bcfab93b.png
  
#18楼 [ 楼主] 3977103 2018/5/18 12:49:27 2018-05-18 12:49 阿豪聊干货  
@就叫我大哥好了
这个配置的意思是,织入所有使用@Aspectj标识的类,并使用CGLib动态代理技术织入增强。一般来说是要配置到spring.xml文件中,不配置到spring-mvc.xml中。配置到spring.xml中不生效,你要再检查下别的配置问题。bean注册到了哪个容器,要看是被哪个配置文件scan到的,跟导入配置文件,没有关系的。
https://i-blog.csdnimg.cn/blog_migrate/8b4db63289e2c21f2e79b6e6419a7f96.png
</div><!--end: forFlow -->
</div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值