容器是整个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. 二进制32位转十进制int

    public class BinaryToDecimal { public static int BinaryToDecimal(int binaryNumber){ int decimal = 0; ...

  2. Flask 上下文管理

    为什么用threading.local? 我们都知道线程是由进程创建出来的,CPU实际执行的也是线程,那么线程其实是没有自己独有的内存空间的,所有的线程共享进程的资源和空间,共享就会有冲突,对于多线程 ...

  3. Queue 队列的使用

    队列一个先进先出的对象集合 public class PlayChickTopicData : MonoBehaviour { Queue<TopicData> topicDatas = ...

  4. MySQL Workbench 创建数据库,添加新表,添加字段

    建立数据库 第一步: 第二步: 第三步: 如图弹出弹框,继续点击Apply按钮,最后点击Finish按钮完成数据库的建立 创建表与添加字段 双击!!!  一下刚刚建立好的数据库,然后再创建表,不然会出 ...

  5. 16. Antimalware (反病毒 3个)

    Malwarebytes反恶意软件是为Windows的恶意软件扫描程序. 作者声称使用各种技术来查找其他恶意软件扫描仪检测不到的恶意软件. 有一个有限选项的免费试用版和支持能够运行扫描计划的完整版本, ...

  6. [C++]_ELVE_Windows下QT5.12连接MySql8.0解决方案

    #0x01 准备 1)要保证QT和MySQL都是一样的位数,我的就是都安装的64位. 2)安装Qt5.12,这里主要提一点,在安装时候,有个选择插件那块,尽量都选上,里面有个database选项,记得 ...

  7. vue原生table合并单元格并可编辑

    <template> <div> <div class="el-card box-card table_container"> <div ...

  8. ifconfig 网卡 下面的参数

    ifconfig  eth1 eth1 Link encap:Ethernet HWaddr 20:12:07:04:05:00 inet addr:172.16.77.174 Bcast:172.1 ...

  9. Linux----------ftp的介绍及安装使用

    目录 一.ftp简介 二.常用的ftp软件有: 三.ftp数据连接模式 3.1命令连接是指文件管理类命令,始终保持连接,直到用户退出 3.2数据连接是指数据传输时创建和关闭的连接 四.用户验证方式 4 ...

  10. 面向对象编程导论 An Introduction to Object-Oriented Programming (Timothy 著)

    第1章 面向对象思想 第2章 抽象 第3章 面向对象设计 第4章 类和方法 第5章 消息,实例和初始化 第6章 案例研究: 八皇后问题 第7章 研究研究: 台球游戏 第8章 继承与替换 第9章 案例研 ...