之前写了几篇关于SpringBoot
的文章《面试高频题:springBoot自动装配的原理你能说出来吗》、《保姆级教程,手把手教你实现一个SpringBoot的starter》,这几天忽然有个读者问:能说一说Spring
的父子容器吗?说实话这其实也是Spring
八股文里面一个比较常见的问题。在个人印象里面Spring
就是父容器,SpringMvc
就是子容器,子容器能够访问父容器的内容,父容器不能访问子容器的东西。有点相似java里面的继承的味道,子类能够继承父类共有方法和变量,能够访问它们,父类不能够访问子类的方法和变量。在这里就会衍生出几个比较经典的问题:html
Spring
容器来管理?(Spring
的applicationContext.xml
中配置全局扫描)Spring-mvc
子容器里面来管理(springmvc
的spring-servlet.xml
中配置全局扫描)?Spring
的父子容器应该问题不大了。咱们能够看下官网提供的父子容器的图片WebApplicationContext
实例,为了进行区分,分别称之为:Servlet WebApplicationContext
(子容器)、Root WebApplicationContext
(父容器)。web
层进行配置,如控制器(controller
)、视图解析器(view resolvers
)等相关的bean。经过spring mvc
中提供的DispatchServlet来加载配置,一般状况下,配置文件的名称为spring-servlet.xml。service
层、dao
层进行配置,如业务bean
,数据源(DataSource
)等。一般状况下,配置文件的名称为applicationContext.xml
。在web
应用中,其通常经过ContextLoaderListener
来加载。要想很好的理解它们之间的关系,咱们就有必要先弄清楚Spring的启动流程。要弄清楚这个启动流程咱们就须要搭建一个SpringMvc
项目,说句实话,用惯了SpringBooot
开箱即用,忽然在回过头来搭建一个SpringMvc
项目还真有点不习惯,一大堆的配置文件。(虽然也能够用注解来实现)具体怎么搭建SpringMvc
项目这个就不介绍了,搭建好项目咱们运行起来能够看到控制台会输出以下日志:日志里面分别打印出了父容器和子容器分别的一个耗时。java
咱们只须要Controller
与咱们的Service
中实现ApplicationContextAware
接口,就能够得知对应的管理容器:在Service
所属的父容器里面咱们能够看到父容器对应的对象是XmlWebApplicationContext@3972
在
Controller
中对应的容器对象是XmlWebApplicationContext@4114
因而可知它们是两个不一样的容器。程序员
咱们知道SpringServletContainerInitializer
从 servlet 3.0
开始,Tomcat
启动时会自动加载实现了 ServletContainerInitializer
接口的类(须要在 META-INF/services
目录下新建配置文件)也称为 SPI(Service Provider Interface)
机制,SPI
的应用仍是挺广的好比咱们的JDBC
、还有Dubbo
框架里面都有用到,若是还有不是很了解SPI
机制的 能够去学习下。因此咱们的入口就是SpringServletContainerInitializer
的onStartup
方法,这也应该是web容器启动调用Spring
相关的第一个方法。web
若是实在找不到入口的话,咱们能够 根据控制台打印的日志,而后拿着日志进行反向查找这应该总能找到开始加载父容器的地方。启动的时候控制台应该会打印出“Root WebApplicationContext: initialization started
” 咱们拿着这个日志就能定位到代码了面试
`public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {` `if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {` `throw new IllegalStateException(` `"Cannot initialize context because there is already a root application context present - " +` `"check whether you have multiple ContextLoader* definitions in your web.xml!");` `}` `servletContext.log("Initializing Spring root WebApplicationContext");` `Log logger = LogFactory.getLog(ContextLoader.class);` `if (logger.isInfoEnabled()) {` `logger.info("Root WebApplicationContext: initialization started");` `}` `long startTime = System.currentTimeMillis();` `try {` `// Store context in local instance variable, to guarantee that` `// it is available on ServletContext shutdown.` `if (this.context == null) {` `// 经过反射去建立context` `this.context = createWebApplicationContext(servletContext);` `}` `if (this.context instanceof ConfigurableWebApplicationContext) {` `ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;` `if (!cwac.isActive()) {` `// The context has not yet been refreshed -> provide services such as` `// setting the parent context, setting the application context id, etc` `if (cwac.getParent() == null) {` `// The context instance was injected without an explicit parent ->` `// determine parent for root web application context, if any.` `ApplicationContext parent = loadParentContext(servletContext);` `cwac.setParent(parent);` `}` `// IOC容器初始化` `configureAndRefreshWebApplicationContext(cwac, servletContext);` `}` `}` `servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);` `ClassLoader ccl = Thread.currentThread().getContextClassLoader();` `if (ccl == ContextLoader.class.getClassLoader()) {` `currentContext = this.context;` `}` `else if (ccl != null) {` `currentContextPerThread.put(ccl, this.context);` `}` `if (logger.isInfoEnabled()) {` `long elapsedTime = System.currentTimeMillis() - startTime;` `logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");` `}` `return this.context;` `}` `catch (RuntimeException | Error ex) {` `logger.error("Context initialization failed", ex);` `servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);` `throw ex;` `}` `}`
这段代码就是建立父容器的地方。spring
接着咱们再来看看建立子容器的地方:在FrameworkServlet
类上述代码是否是会有个疑问咱们怎么就会执行
FrameworkServlet
的initServletBean
方法。这是因为咱们在web.xml
里面配置了DispatcherServlet
,而后web容器就会去调用DispatcherServlet
的init
方法,而且这个方法只会被执行一次。经过init方法就会去执行到initWebApplicationContext
这个方法了,这就是web子容器的一个启动执行顺序。express
`<servlet>` `<servlet-name>dispatcher</servlet-name>` `<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>` `// 若是不配置这个load-on-startup 1 不会再项目启动的时候执行inti方法。而是首次访问再启动` `<load-on-startup>1</load-on-startup>` `</servlet>`
大概流程以下:从上述代码咱们能够发现子容器是本身从新经过反射
new
了一个新的容器做为子容器, 而且设置本身的父容器为Spring
初始化建立的WebApplicationContext
。而后就是去加载咱们在web.xml
里面配置的Springmvc
的配置文件,而后经过建立的子容器去执行refresh
方法,这个方法我相信不少人应该都比较清楚了。编程
咱们知道了Sping
父容器以及SpingMvc
子容器的一个启动过程,以及每一个容器都分别干了什么事情如今再回过头来看看上述四个问题。架构
J2EE
三层架构中,在service
层咱们通常使用spring
框架来管理, 而在web
层则有多种选择,如spring mvc、struts
等。所以,一般对于web
层咱们会使用单独的配置文件。例如在上面的案例中,一开始咱们使用spring-servlet.xml
来配置web层,使用applicationContext.xml来配置service
、dao
层。若是如今咱们想把web
层从spring mvc
替换成struts
,那么只须要将spring-servlet.xml
替换成Struts
的配置文件struts.xml
便可,而applicationContext.xml
不须要改变。`<context:component-scan use-default-filters="false" base-package="cn.javajr">` `<context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />` `<context:include-filter type="annotation" expression="org.springframework.stereotype.Component" />` `<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />` `<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />` `</context:component-scan>`
而后在SpringMvc
的配置里面不配置扫描包路径。很显然这种方式是行不通的,这样会致使咱们请求接口的时候产生404
。由于在解析@ReqestMapping注解的过程当中initHandlerMethods
()函数只是对Spring MVC
容器中的bean
进行处理的,并无去查找父容器的bean
, 所以不会对父容器中含有@RequestMapping
注解的函数进行处理,更不会生成相应的handler
。因此当请求过来时找不处处理的handler
,致使404。并发
spring-servlet.xml
中这个是可行的。为何可行由于无非就是把全部的东西所有交给子容器来管理了,子容器执行了refresh
方法,把在它的配置文件里面的东西所有加载管理起来来了。虽然能够这么作不过通常应该是不推荐这么去作的,通常人也不会这么干的。若是你的项目里有用到事物、或者aop记得也须要把这部分配置须要放到Spring-mvc子容器的配置文件来,否则一部份内容在子容器和一部份内容在父容器,可能就会致使你的事物或者AOP不生效。(这里不就有个经典的八股文吗?你有遇到事物不起做用的时候,其实这也是一种状况)Spring
帮咱们处理了,可是咱们仍是须要知道有这么个东西,否则咱们有可能遇到问题的时候可能不知道如何下手。好比为啥我这个事物不起做用了,我这个aop
怎么也不行了,网上都是这么配置的。站在巨人的肩膀上摘苹果: https://www.cnblogs.com/grasp...
*推荐👍 :Java高并发编程基础三大利器之CyclicBarrier*
推荐👍 :Java高并发编程基础三大利器之CountDownLatch
推荐👍 :Java高并发编程基础三大利器之Semaphore
推荐👍 :Java高并发编程基础之AQS
推荐👍 :可恶的爬虫直接把生产6台机器爬挂了!