原文地址:https://segmentfault.com/a/1190000012972619

大家好,我是猪弟,猪在我心中从来不是蠢的代名词,而是懒的代名词,本次准备记录一个在开发测试过程中遇到的问题,跟踪了三天spring和第三方RPC组件的源码,最终发现了问题是因为第三方组件没有处理好而父子容器导致的,还有一个因素是spring注解扫描重叠。

Spring版本:4.3.13.RELEASE

JDK版本:1.7_u25 64位

在SpringMVC的配置中为了防止Spring重复创建同一个类的实例,一般会用到<context:component-scan>的两个子标签<context:include-filter>&&<context:exclude-filter> 。

但它使用的时候表现的效果并不是和语义上的完全一致,现在来看一下其中的坑:

在很多配置中一般都会把spring-config.xml和spring-mvc.xml进行分开配置,这种配置可以他们保证各司其职,在web.xml的一般配置中spring-mvc.xml实例创建初始化是以DispatchServlet为入口,而spring-config.xml实例创建初始化是以ContextLoadListener为入口的,容器的加载顺序: listener -> filter -> servlet ,所以spring容器先初始化,springmvc容器后初始化 。

    <!--spring 入口-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring-config.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> <!--spring mvc 入口-->
<servlet>
<servlet-name>blog-spring-mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring-mvc.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>blog-spring-mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

如果在spring-mvc.xml中配置扫描的包和spring-config.xml中的发生重叠,那么会导致一个bean被创建两次,而且在spring中是存在父子容器的,spring容器是父容器,springmvc是子容器,springmvc创建的实例放在子容器中,spring创建的实例放在父容器中。

其实这同一个类的两个实例是不同的,springmvc创建实例默认对象不实现接口(大家都知道Controller是不用实现接口的),所以springmvc创建的实例是直接使用目标类的构造器来实例化的,而不是代理对象,即使一个类实现了接口,但如果该类是由springmvc实例化,那么springmvc也会直接使用该类的构造器直接创建一个对象(怎么去证明呢,你可以写一个定时任务,在定时任务中注入Controller的实例,然后debug查看实例对象的地址,如果是代理对象在地址上都会有一个$Proxy的标记,否则就不是代理对象),所以在controller层使用AOP时多数采用的是CGLIB子类代理。

Spring创建实例会判断目标类是否实现了接口,如果没实现接口那么就直接采用目标类构造器创建,像一般的service和dao都会采用接口方式编程,对于接口方式编程的类,spring创建的实例都是代理对象(这一点可以用debug的方式查看controller类中注入的service实例对象地址,他们都带有一个$Proxy的标记,很容易就能看出都是代理对象)。

那么为了防止重叠我们要把重叠的部分去掉,现在有下面的一个需求:

在spring-mvc.xml中只对工程中所有用@Controller注解的类进行扫描创建实例。

在spring-config.xml中要对工程中所有的非@Controller注解的类进行扫描创建实例。

现在给定一个项目的包结构:

xin.sun.blog.controlller

xin.sun.blog.service

(1)在spring-mvc.xml中有以下配置:

<!-- 只扫描 @Controller注解-->
<context:component-scanbase-package="xin.sun.blog.controlller">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

可以看出要把最后的包写上,不能包含子包,所以不能写成: base-package="xin.sun.blog" 。如果这样写,对于 include-filter 标签来讲它会扫描基包下面所有spring注解的类,而不是仅仅扫描 @Controller 。这点需要非常的注意,这一般会导致一个常见的错误,那就是事务不起作用,补救的方法是添加 use-default-filters="false"。

(2)在spring-config.xml中有如下配置:

<!-- 配置扫描注解,不扫描 @Controller注解-->
<context:component-scan base-package="xin.sun.blog">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

可以看到,他是要扫描xin.sun.blog包和子包下的所有spring注解的类,但是不包含@Controller注解的类。对于exculude-filter不存在包不精确导致都进行扫描的问题。

那么还有一个问题:当扫描的包不小心重叠了,导致类在父子容器各实例化了一遍,在 @Autowire 的时候会注入哪个容器中的对象呢?看一个Controller类,代码如下:

@Controller
public class MyController{ @Autowired
private IValidService validService;
//其他代码省略
}

答案是:Spring为了保证注入类的一致性,采用了双亲委托的机制,如果父容器中存在该类的实例那么优先使用父容器中的实例,如果父容器中没有该实例才会用子容器中的实例

『SpringMVC』<context:include-filter>&<context:exclude-filter>爬坑的更多相关文章

  1. Hbase过滤器Filter的使用心得(爬坑经验)

    Hbase 的过滤器是个好东西.. 给这种非关系型数据库本来不能复杂查询的情况得到了很好的扩展..提供了很多的帮助.. 但是Filter的种类何其之多..让人眼花缭乱.. 譬如..分页类型的PageF ...

  2. SpringMVC配置文件详解:<context:annotation-config/>和<context:component-scan base-package=""/>和<mvc:annotation-driven />

    原文地址:https://www.cnblogs.com/lcngu/p/5080702.html Spring配置文件详解:<context:annotation-config/>和&l ...

  3. EF How to use context.Set and context.Entry, which ships with EF4.1 ?

    How to use context.Set and context.Entry, which ships with EF4.1 ? Hello, I am trying to implement a ...

  4. 『WPF』DataGrid的使用

    原文 『WPF』DataGrid的使用 几点说明 这里主要是参考了MSDN中关于DataGrid的说明 这里只会简单说明在WPF中,DataGird最简单的使用方法 对于MSDN中的翻译不会很详细,也 ...

  5. 『计算机视觉』Mask-RCNN_训练网络其三:训练Model

    Github地址:Mask_RCNN 『计算机视觉』Mask-RCNN_论文学习 『计算机视觉』Mask-RCNN_项目文档翻译 『计算机视觉』Mask-RCNN_推断网络其一:总览 『计算机视觉』M ...

  6. context:exclude-filter 与 context:include-filter 转

    context:exclude-filter 与 context:include-filter 转 1 在主容器中(applicationContext.xml),将Controller的注解打消掉 ...

  7. Spring配置中<context:annotation-config> VS <context:component-scan>

    Spring 中在使用注解(Annotation)会涉及到< context:annotation-config> 和 < context:component-scan>配置, ...

  8. <context:annotation-config />和 <context:component-scan

    <context:annotation-config />和 <context:component-scan>同时存在的时候,前者会被忽略. 也就是那些@autowire,@r ...

  9. 『TensorFlow』读书笔记_VGGNet

    VGGNet网络介绍 VGG系列结构图, 『cs231n』卷积神经网络工程实践技巧_下 1,全部使用3*3的卷积核和2*2的池化核,通过不断加深网络结构来提升性能. 所有卷积层都是同样大小的filte ...

随机推荐

  1. 常用Java程序片段

    1.改变数组的大小 package com.js.ai.modules.jsa.test; public class Testxf { private static Object resizeArra ...

  2. 【Python编程:从入门到实践】chapter10 文件和异常

    chapter10 文件和异常 10.1 从文件中读取数据 10.1.1 读取整个文件 with open("pi.txt") as file_object: contents = ...

  3. php 编程笔记分享

    php获取POST数据的三种方法php 图片加水印源代码php+ajax+json的一个最简单实例php 汉字转拼音源码php遍历目录,生成目录下每个文件的md5值并写入到结果文件中php实现linu ...

  4. uva-185-暴力枚举

    请相信,这是一道水题,读了一周的题意 题意: 题目里面描述的那三个条件可以直接无视,关于罗马数字只要知道一个规则即可,映射如下 I 1       V 5X 10     L 50C 100 D 50 ...

  5. python入门-函数(一)

    1定义函数并且调用  注释语句""" """ def greet_user(): """显示简单的问候语&qu ...

  6. (13/24) css进阶:自动处理css3属性前缀

    什么是属性前缀 为了浏览器的兼容性,有时候我们必须加入-webkit,-ms,-o,-moz这些前缀.目的就是让我们写的页面在每个浏览器中都可以顺利运行. 例如: transition: width ...

  7. Vue2.0中v-for迭代语法变化(key、index)

    语法发生了变化:http://blog.csdn.net/sinat_35512245/article/details/53966788 新数组语法 value in arr (value, inde ...

  8. Unable to open file '.RES'

    Unable to open file '.RES' 另存工程,带来的隐患,工程图标也改不了. 搜索发现源码里某个man.cpp里带了prgram  resource aaa.res,换成新工程文件名 ...

  9. windows10配置java开发环境

    一.下载jdk 二.安装jdk路径,这个路径不能包含中文名 三.系统会提示安装jre,安装目录不要是jdk的安装目录,否则会覆盖掉jdk目录下的jre目录 四. .;%JAVA_HOME%\lib;% ...

  10. tensorflow笔记之学习率设置

    在使用梯度下降最小化损失函数时,如果学习率过大会导致问题不能收敛到最优解,学习率过小,虽然可以收敛到最优解,但是需要的迭代次数会大大增加,在Tensorflow中,可以用指数衰减法设置学习率,tf.t ...