SpringMVC——与Spring的父子容器关系以及包扫描问题

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> SpringMVC——与Spring的父子容器关系以及包扫描问题

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

Spring版本:4.3.13.RELEASE
IDE工具:IDEA 2017.2.6
JDK版本:1.7_u25 64位

IDE工具:IDEA 2017.2.6

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-namecontextConfigLocation/param-name
        param-value
            classpath:spring-config.xml
        /param-value
    /context-param
    listener
        listener-classorg.springframework.web.context.ContextLoaderListener/listener-class
    /listener

    !--spring mvc 入口--
    servlet
        servlet-nameblog-spring-mvc/servlet-name
        servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class
        init-param
            param-namecontextConfigLocation/param-name
            param-value
                classpath:spring-mvc.xml
            /param-value
        /init-param
        load-on-startup1/load-on-startup
    /servlet
    servlet-mapping
        servlet-nameblog-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为了保证注入类的一致性,采用了双亲委托的机制,如果父容器中存在该类的实例那么优先使用父容器中的实例,如果父容器中没有该实例才会用子容器中的实例

原文始发于微信公众号(glmapper工作室):

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> SpringMVC——与Spring的父子容器关系以及包扫描问题


 上一篇
Spring 集成调度器 Spring 集成调度器
点击上方“后端技术精选”,选择“置顶公众号” 技术文章第一时间送达! 作者:dunwu github.com/dunwu/spring-tutorial/ github.com/dunwu/spring-
2021-04-05
下一篇 
剖析Spring源码——加载IOC容器 剖析Spring源码——加载IOC容器
本文接上一篇文章 ,继续进行下面的分析 首先贴出 Spring bean容器的刷新的核心 11个步骤进行祭拜(一定要让我学会了…阿门) 1234567891011121314151617181920212223242526272829303
2021-04-05