容器是整个Spring 框架的核心思想,用来管理Bean的整个生命周期。

一个项目中引入Spring和SpringMVC这两个框架,Spring是父容器,SpringMVC是其子容器,子容器可以看见父容器中的注册的Bean,反之就不行。请记住这个特性。

spring 容器基础释义

1

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

<context:component-scan base-package="com.amu.modules" />

该配置的功能是扫描配置的base-package包下的所有使用了@Component注解的类,并且将它们自动注册到容器中,同时也扫描其子类 @Controller,@Service,@Respository这三个注解

2

<context:annotation-config/>

此配置表示默认声明了@Required、@Autowired、 @PostConstruct、@PersistenceContext、@Resource、@PreDestroy等注解。

3

<mvc:annotation-driven />

SpringMVC必备配置。它声明了@RequestMapping、@RequestBody、@ResponseBody等。并且,该配置默认加载很多的参数绑定方法,比如json转换解析器等。

4 上面的配置等价于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"/>

二:案例分析

2.1 案例初探

Spring容器与其子容器Spring MVC 冲突问题的原因到底在那里?

我们知道,Spring和SpringMVC 容器配置文件分别为applicationContext.xml和applicationContext-MVC.xml。

1.在applicationContext.xml中配置了<context:component-scan base-package=“com.amu.modules" />

负责所有需要注册的Bean的扫描和注册工作。

2.在applicationContext-MVC.xml中配置<mvc:annotation-driven />

负责SpringMVC相关注解的使用。

3.DEBUG 模式下启动项目,我们发现SpringMVC无法进行跳转,将log的日志打印发现SpringMVC容器中的请求没有映射到具体controller中。
4.在applicationContext-MVC.xml中配置<context:component-scan base-package=“com.amu.modules" />

重启后,SpringMVC跳转有效。

2.2 查看源码

看源码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能找到处理器进行处理,从而正常跳转。

原因找到了,那么如何解决呢?

2.2 解决办法

在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.amu.modules">
<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.amu.modules" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

小结:

把不同类型的Bean分配到不同的容器中进行管理。

三:进阶:use-default-filters="false"的作用

3.1 初探

spring-mvc.xml 的不同配置方法

1 只扫描到带有@Controller注解的Bean

如下配置会成功扫描到带有@Controller注解的Bean,不会扫描带有@Service/@Repository注解的Bean,是正确的。

<context:component-scan base-package="com.amu.modules.controller">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
2 扫描到其他注解

但是如下方式,不仅仅扫描到带有@Controller注解的Bean,还扫描到带有@Service/@Repository注解的Bean,可能造成事务不起作用等问题。

<context:component-scan base-package="com.amu.modules">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
这是因为什么呢?

3.2 源码分析

1.<context:component-scan>会交给org.springframework.context.config.ContextNamespaceHandler处理.

registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());

2.ComponentScanBeanDefinitionParser会读取配置文件信息并组装成org.springframework.context.annotation.ClassPathBeanDefinitionScanner进行处理。

3.<context:component-scan>的use-default-filters属性默认为true,在创建ClassPathBeanDefinitionScanner时会根据use-default-filters是否为true来调用如下代码:

protected void registerDefaultFilters() {
  this.includeFilters.add(new AnnotationTypeFilter(Component.class));
  ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
  try {
    this.includeFilters.add(new AnnotationTypeFilter(
      ((Class<? extends Annotation>) cl.loadClass("javax.annotation.ManagedBean")), false));
    logger.info("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
  }
  catch (ClassNotFoundException ex) {
    // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
  }
  try {
    this.includeFilters.add(new AnnotationTypeFilter(
      ((Class<? extends Annotation>) cl.loadClass("javax.inject.Named")), false));
    logger.info("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
  }
  catch (ClassNotFoundException ex) {
    // JSR-330 API not available - simply skip.
  }
}

从源码我们可以看出默认ClassPathBeanDefinitionScanner会自动注册对@Component、@ManagedBean、@Named注解的Bean进行扫描。

4.在进行扫描时会通过include-filter/exclude-filter来判断你的Bean类是否是合法的:

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
if (!metadata.isAnnotated(Profile.class.getName())) {
return true;
}
AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);
return this.environment.acceptsProfiles(profile.getStringArray("value"));
}
}
return false;
}

从源码可看出:扫描时首先通过exclude-filter 进行黑名单过滤,然后通过include-filter 进行白名单过滤,否则默认排除。

3.3 结论

在spring-mvc.xml中进行如下配置:

<context:component-scan base-package="com.amu.modules">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

SpringMVC容器不仅仅扫描并注册带有@Controller注解的Bean,而且还扫描并注册了带有@Component的子注解@Service、@Reposity的Bean,从而造成新加载的bean覆盖了老的bean,但事务的AOP代理没有配置在spring-mvc.xml配置文件中,造成事务失效。因为use-default-filters默认为true。

结论:use-default-filters=“false”禁用掉默认。

【公众号】:一只阿木木

结合源码浅谈Spring容器与其子容器Spring MVC 冲突问题的更多相关文章

  1. 【Android测试】【第七节】Monkey——源码浅谈

    ◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/4713466.html 前言 根据上一篇我们学会了Monke ...

  2. 【Android测试】【第三节】ADB——源码浅谈

    ◆版权声明:本文出自carter_dream的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/4651724.html 前言 由于本人精力 ...

  3. 源码浅谈(一):java中的 toString()方法

    前言: toString()方法 相信大家都用到过,一般用于以字符串的形式返回对象的相关数据. 最近项目中需要对一个ArrayList<ArrayList<Integer>> ...

  4. 源码浅谈(二):java中的 Integer.parseInt(String str)方法

    这个方法是将字符串转换为整型 一.parseInt方法 ,可以看到默认又调用了parseInt(s,10) ,  第二个参数为基数,默认10 ,当然也可以自己设置  public static int ...

  5. glibc memcpy() 源码浅谈

    其实我本来只是想搞懂为什么memcpy()函数的参数类型是void *的: 我以为会在memcpy()源码中能找到答案,其实并没有,void *只是在传递参数的时候起了作用,可以让memcpy()接受 ...

  6. JUC源码分析-集合篇:并发类容器介绍

    JUC源码分析-集合篇:并发类容器介绍 同步类容器是 线程安全 的,如 Vector.HashTable 等容器的同步功能都是由 Collections.synchronizedMap 等工厂方法去创 ...

  7. Spark 源码浅读-SparkSubmit

    Spark 源码浅读-任务提交SparkSubmit main方法 main方法主要用于初始化日志,然后接着调用doSubmit方法. override def main(args: Array[St ...

  8. Spring MVC 根容器和子容器

    整合 spring mvc 根容器和子容器 public class TestWebInitializer extends AbstractAnnotationConfigDispatcherServ ...

  9. 查看Spring MVC 父容器和子容器的对象的实例

    话不多说,直接上案例 package com.oukele.web; import org.springframework.beans.factory.annotation.Autowired; im ...

随机推荐

  1. 2018-软工机试-C-和你在一起

    C. 和你在一起 单点时限: 1.0 sec 内存限制: 256 MB 我想和你在一起 直到我不爱你 宝贝 人和人 一场游戏 我愿意为你死去 如果我还爱你 宝贝 反正活着 也没意义 宝贝 我也只能 这 ...

  2. css+jquery 实现图片局部放大预览

    今天有时间开始动手,使用css+jquery实现了图片局部放大的组件,首先看看效果图: 界面设计思路如下: 1.两个div,左边放图片的缩略图 2.在左边缩略图鼠标移动的时候,区域(效果图中的网格) ...

  3. 增长java中数组的长度

    package month_201711; import java.util.Arrays;/** * 数组长度+1 * @author watchfree * */public class Main ...

  4. JIRA 7.8 版本的安装与破解

    jira的运行是依赖java环境的,也就是说需要安装jdk并且要是1.8以上版本 除此之外,我们还需要安装MySQL,为jira创建对应的数据库.用户名和密码,如下: 注意:建库名jira,字符集为U ...

  5. Kylin引入Spark引擎

    1 引入Spark引擎 Kylin v2开始引入了Spark引擎,可以在构建Cube步骤中替换MapReduce. 关于配置spark引擎的文档,下面给出官方链接以便查阅:http://kylin.a ...

  6. Hive元数据找回

    如果不小心删除了了hive的元数据文件(/user/hive/warehouse),只要先前core-site.xml文件中设置了fs.trash.interval属性就可以找回.hdfs会为用户创建 ...

  7. [LeetCode&Python] Problem 415. Add Strings

    Given two non-negative integers num1 and num2 represented as string, return the sum of num1 and num2 ...

  8. mysql数据库 事务和索引

    1.MySQL数据库特性:  原子性(atomidity) 一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一 ...

  9. Mybatis集成Oracle

    首先需要导入Oracle的驱动,这部分会有一个天坑 Maven无法直接将我们所需的Oracle驱动加入项目中,手动加入依赖也是无效(原因还在分析),而且驱动无效如果不注意的话是看不出来的,他不会在编译 ...

  10. Python 基础知识(持续更新中)

    内置数据类型:     整型     浮点型     字符串     布尔值     空值 None     列表 list     元组 tuple     字典 dict     集合 set   ...