以前写了几篇对于SpringBoot的文章《面试高频题:springBoot主动拆卸的原理你能说进去吗》、《保姆级教程,手把手教你实现一个SpringBoot的starter》,这几天忽然有个读者问:能说一说Spring的父子容器吗?说实话这其实也是Spring八股文外面一个比拟常见的问题。在我的印象外面Spring就是父容器,SpringMvc就是子容器,子容器能够拜访父容器的内容,父容器不能拜访子容器的货色。有点相似java外面的继承的滋味,子类能够继承父类共有办法和变量,能够拜访它们,父类不能够拜访子类的办法和变量。在这里就会衍生出几个比拟经典的问题:

  • 为什么须要父子容器?
  • 是否能够把所有类都通过Spring容器来治理?(SpringapplicationContext.xml中配置全局扫描)
  • 是否能够把咱们所需的类都放入Spring-mvc子容器外面来治理(springmvcspring-servlet.xml中配置全局扫描)?
  • 同时通过两个容器同时来治理所有的类?如果可能把下面这四个问题能够说个所以然来,集体感觉Spring的父子容器应该问题不大了。咱们能够看下官网提供的父子容器的图片上图中显示了2个WebApplicationContext实例,为了进行辨别,别离称之为:Servlet WebApplicationContext(子容器)、Root WebApplicationContext(父容器)。
  • Servlet WebApplicationContext:这是对J2EE三层架构中的web层进行配置,如控制器(controller)、视图解析器(view resolvers)等相干的bean。通过spring mvc中提供的DispatchServlet来加载配置,通常状况下,配置文件的名称为spring-servlet.xml。
  • Root WebApplicationContext:这是对J2EE三层架构中的service层、dao层进行配置,如业务bean,数据源(DataSource)等。通常状况下,配置文件的名称为applicationContext.xml。在web利用中,其个别通过ContextLoaderListener来加载。

Spring的启动

要想很好的了解它们之间的关系,咱们就有必要先弄清楚Spring的启动流程。要弄清楚这个启动流程咱们就须要搭建一个SpringMvc我的项目,说句实话,用惯了SpringBooot开箱即用,忽然在回过头来搭建一个SpringMvc我的项目还真有点不习惯,一大堆的配置文件。(尽管也能够用注解来实现)具体怎么搭建SpringMvc我的项目这个就不介绍了,搭建好我的项目咱们运行起来能够看到控制台会输入如下日志:日志外面别离打印出了父容器和子容器别离的一个耗时。

如何验证是有两个容器?

咱们只须要Controller与咱们的Service中实现ApplicationContextAware接口,就能够得悉对应的治理容器:在Service所属的父容器外面咱们能够看到父容器对应的对象是XmlWebApplicationContext@3972Controller中对应的容器对象是XmlWebApplicationContext@4114由此可见它们是两个不同的容器。

源码剖析

咱们晓得SpringServletContainerInitializerservlet 3.0 开始,Tomcat 启动时会主动加载实现了 ServletContainerInitializer
接口的类(须要在 META-INF/services 目录下新建配置文件)也称为 SPI(Service Provider Interface) 机制,SPI的利用还是挺广的比方咱们的JDBC、还有Dubbo框架外面都有用到,如果还有不是很理解SPI机制的 能够去学习下。所以咱们的入口就是SpringServletContainerInitializeronStartup办法,这也应该是web容器启动调用Spring相干的第一个办法。

初始化SpringIoc

如果切实找不到入口的话,咱们能够 依据控制台打印的日志,而后拿着日志进行反向查找这应该总能找到开始加载父容器的中央。启动的时候控制台应该会打印出“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 MVC

接着咱们再来看看创立子容器的中央:在FrameworkServlet上述代码是不是会有个疑难咱们怎么就会执行FrameworkServletinitServletBean办法。这是因为咱们在web.xml 外面配置了DispatcherServlet,而后web容器就会去调用DispatcherServletinit办法,并且这个办法只会被执行一次。通过init办法就会去执行到initWebApplicationContext这个办法了,这就是web子容器的一个启动执行程序。

`<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来配置servicedao层。如果当初咱们想把web层从spring mvc替换成struts,那么只须要将spring-servlet.xml替换成Struts的配置文件struts.xml即可,而applicationContext.xml不须要扭转。
  • 是否能够把所有类都通过Spring父容器来治理?(Spring的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-mvc子容器外面来治理(springmvc的spring-servlet.xml中配置全局扫描)?这个是把包的扫描配置spring-servlet.xml中这个是可行的。为什么可行因为无非就是把所有的货色全副交给子容器来治理了,子容器执行了refresh办法,把在它的配置文件外面的货色全副加载治理起来来了。尽管能够这么做不过个别应该是不举荐这么去做的,个别人也不会这么干的。如果你的我的项目里有用到事物、或者aop记得也须要把这部分配置须要放到Spring-mvc子容器的配置文件来,不然一部分内容在子容器和一部分内容在父容器,可能就会导致你的事物或者AOP不失效。(这里不就有个经典的八股文吗?你有遇到事物不起作用的时候,其实这也是一种状况)
  • 同时通过两个容器同时来治理所有的类?这个问题应该是比拟好答复了,必定不会通过这种形式来的,先不说会不会引发其余问题,首先两个容器外面都放一份一样的对象,造成了内存节约。再者的话子容器会笼罩父容器加载,原本可能父容器配置了事物生成的是代理对象,然而被子容器一笼罩,又成了原生对象。这就导致了你的事物不起作用了。在补充一个问题:SpringBoot 外面是否还有父子容器?咱们下篇再见!

总结

  • 其实父子容器对于程序员来说是无感的,是一个并没有什么用的知识点,都是Spring帮咱们解决了,然而咱们还是须要晓得有这么个货色,不然咱们有可能遇到问题的时候可能不晓得如何下手。比方为啥我这个事物不起作用了,我这个aop怎么也不行了,网上都是这么配置的。

完结

  • 因为本人满腹经纶,难免会有纰漏,如果你发现了谬误的中央,还望留言给我指出来,我会对其加以修改。
  • 如果你感觉文章还不错,你的转发、分享、赞叹、点赞、留言就是对我最大的激励。
  • 感谢您的浏览,非常欢送并感谢您的关注。
  • 站在伟人的肩膀上摘苹果: https://www.cnblogs.com/grasp...

    https://javajr.cn

往期精选

*举荐 :Java高并发编程根底三大利器之CyclicBarrier*

举荐 :Java高并发编程根底三大利器之CountDownLatch

举荐 :Java高并发编程根底三大利器之Semaphore

举荐 :Java高并发编程根底之AQS

举荐 :可恶的爬虫间接把生产6台机器爬挂了!