Spring容器和Spring MVC容器

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了,只要使用统一的如下配置即可。

<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这个父容器中了,看代码。

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;
    }

在配置方式B中,springMVC容器中包括了所有的@Controller注解的Bean,所以自然就能找到了。

以上是原因,解决办法是什么?注意看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>

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

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

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

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

<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://www.open-open.com/lib/view/open1452142677292.html>

时间: 2024-10-29 13:39:20

Spring容器和Spring MVC容器的相关文章

深入分析Spring 与 Spring MVC容器

spring官方文档中对web容器的说明 spring配置文件默认名字--applicationContext.xml 参考链接: https://www.cnblogs.com/hujunzheng/p/5673377.html Spring的启动过程: 首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境: 其次,在web.xml中会提供有contextLoaderLi

Spring IOC容器和Spring MVC IOC容器的区别

web.xml <context-param> <param-name>contextConfigLocation</param-name> <!-- 指定加载application配置文件 --> <param-value>classpath:spring/application.xml</param-value> </context-param> <listener> <listener-class&

【原创】Docker容器及Spring Boot微服务应用

Docker容器及Spring Boot微服务应用 1 什么是Docker 1.1 Docker的出现 问题一:项目实施环境复杂问题 传统项目实施过程中经常会出现“程序在我这跑得好好的,在你那怎么就不行呢?! ” 这是一个典型的应用场景,Docker image中包含了程序需要的所有的运行时依赖,比如java的程序,肯定要在image中包含jdk:比如Python的程序,肯定要在image中包含对应版本的Python解释器.Docker把整个运行时环境打包放到image中,所以搞定了环境依赖问题

JAVAWEB开发之Spring详解之——Spring的入门以及IOC容器装配Bean(xml和注解的方式)、Spring整合web开发、整合Junit4测试

Spring框架学习路线 Spring的IOC Spring的AOP,AspectJ Spring的事务管理,三大框架的整合 Spring框架概述 什么是Spring? Spring是分层的JavaSE/EE full-stack(一站式)轻量级开源框架. 所谓分层: SUN提供的EE的三层结构:web层.业务层.数据访问层(也称持久层,集成层). Struts2是web层基于MVC设计模式框架. Hibernate是持久的一个ORM的框架. 所谓一站式:Spring框架有对三层的每层解决方案.

Spring 和SpringMVC 的父子容器关系

Spring和SpringMVC作为Bean管理容器和MVC层的默认框架,已被众多WEB应用采用,而实际使用时,由于有了强大的注解功能,很多基于XML的配置方式已经被替代,但是在实际项目中,同时配置Spring和SpringMVC时会出现一些奇怪的异常,比如Bean被多次加载,多次实例化,或者依赖注入时,Bean不能被自动注入,但是明明你已经将该Bean注册了的.找原因还是要看问题的根源,我们从容器说起. 在Spring整体框架的核心概念中,容器是核心思想,就是用来管理Bean的整个生命周期的,

Spring和SpringMVC父子的容器之道---[上篇]

昨天,给数据组写接口,不小心掉进坑里挣扎了半天,最后发现是spring和springmvc它们虽是父子但并不和谐,于是在此一记. Spring和SpringMVC作为Bean管理容器和MVC层的默认框架,已被众多WEB应用采用,而在实际开发中,由于有了强大的注解功能,很多基于XML的配置方式已经被替代,但在实际项目中,我们经常会同时配置Spring和SpringMVC的配置文件,分层来管理它们,但是有时候就会出现那么一些奇怪的异常,一旦进坑,让你无法自拔,就在昨天给数据组写接口时,我进坑了,就在

Spring源码分析2 — 容器启动流程

1 主要类 部署web应用时,web容器(比如Tomcat)会读取配置在web.xml中的监听器,从而启动spring容器.有了spring容器之后,我们才能使用spring的IOC AOP等特性.弄清spring容器启动流程,有利于理解spring IOC中的各种特性,比如BeanPostProcessor,MessageSource,ApplicationListener等.我们先来看下容器启动流程中涉及的主要类. ContextLoaderListener:注册在web.xml中,web应

Spring技术内幕——Spring Framework的IOC容器实现(二)

三.IOC容器的初始化过程 IOC容器的初始化时由前面介绍的refresh方法来启动的,这个方法标志着IOC容器的正式启动.这个启动包括BeanDefinition的Resource定位.载入和注册.下面我们将详细分析这三个实现过程,Spring把这三个过程分开,并使用不同的模块来完成,通过这样的设计让用户更加灵活的这三个过程进行剪裁和扩展,定义出最适合自己的IOC容器的初始化过程. 第一个过程: Resource定位过程,是指BeanDefinition的资源定位,他由ResourceLoad

Spring.Net学习笔记(一)-容器的使用

一.下载地址: http://www.springframework.net/download.html 二.相关程序集 Spring.Net容器定义在程序集Spring.Core.dll中,它依赖于Common.Logging.dll.该程序集位于 Spring.NET-1.3.1\Spring.NET\bin\net\4.0\release目录下 三.创建容器 1.编程方式的容器 使用Spring.Context.Support.StaticApplicationContxt直接创建容器 p