热门框架系列 -- SpringMvc的父子容器,SpringBoot是否有父子容器?

Posted 喜欢编码的老胡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了热门框架系列 -- SpringMvc的父子容器,SpringBoot是否有父子容器?相关的知识,希望对你有一定的参考价值。

@TOC# 热门框架系列
记录在程序走的每一步___auth:huf


从新的篇章开始;篇章阅读需要先关注; 因为笔者想参与技术文章的评选.;需要一定的粉丝量; 粉丝量达到一定数量.所有文章阅读限制将会全面放开;谢谢大家的支持
本章节为上一个章节的延续;仅此一章节的延续; 先抛出一个问题;请记住这个问题;之后看完这个篇章应该可以轻松的回答出来;

SpringBoot 既然整合了SpringMVC 那SpringBoot 有没有子容器?


我们开始这个篇章吧!
在Servlet3.X,我们可以添加 Servlet Filter Listener

我们有两种方式 一种是注解的方式;
@WebServlet @WebFilter @WebListener
还有一种方式 就是 :Spi

spi 是什么?

SPI 我们叫他服务接口扩展,(Service Provider Interface) 直译服务提供商接口, 不 要被这个名字唬到了, 其实很好理解的一个东西: 其实就是根据Servlet厂商(服务提供商)提供要求的一个接口, 在固定的目录 (META-INF/services)放上以接口全类名 为命名的文件, 文件中放入接口的实现的 全类名,该类由我们自己实现,按照这种约定的方式(即SPI规范),服务提供商会 调用文件中实现类的方法, 从而完成扩展。

在其规范中 我们可以看得到那么一句话:

也就是META-INF/services 路径下 放一个 javax.servlet.ServletContainerInitailizer
以下我们进入SPI 代码的实现:


准备一个maven 项目 里面三个子项目 一个Service 一个Client; 还有一个startUp 启动 我们按照上面的说法 我们在 META-INF/service 路径下放一个 接口全路径名的File 文件 文件内维护全路径类名的实现类

以下是测试结果:

SPI 机制就是这样;
现在很多第三方插件使用的都是SPI机制; 当然Spring也有这样的机制;
前置知识点 到这里 就铺垫的差不多了; 如果SPI又疑问的;可以单独私聊我;


我们细细的往下挖,IDEA创建spring mvc项目 如何创建 可以在其他博主内找到; 以下是需要注意的几点:

1: 需要配置Tomcat;

Tomcat下载下来 启动会有乱码 以下是windows解决乱码的方案:

在Tomcat的Config里面 logging.properties
修改:
java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.ConsoleHandler.encoding = GBK

2:需要配置dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/cache
       http://www.springframework.org/schema/cache/spring-cache.xsd">

    <context:component-scan base-package="com.huf"></context:component-scan>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

    <mvc:annotation-driven></mvc:annotation-driven>

</beans>

3:需要配置web.xml

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

如果对于这部分代码配置 或者使用idea 搭建 SpringMVC搭建 项目 过程中 遇到有什么问题 可以私聊我; 因为如果想要追溯 父子容器 那就必须要搭建这么一套东西;

我们在项目中的META-INF.services 添加 javax.servlet.ServletContainerInitializer

SpringMVC 我们分为2块来看 第一块 启动:

我们现在了解了SPI 也配置好了Tomcat 启动这一块 我暂时不细说; 后面有专门的文章来说Tomcat;
1:在我们的SPI中 我们有一个类放在META-INF下面:HufSpringServletContainerInitializer

tomcat启动
ServletContainerInitializer
SpringServletContainerInitializer
HufSpringServletContainerInitializer.onStartup

之后
我们进入到了AbstractContextLoaderInitializer.onStartup 方法;

	继承自AbstractAnnotationConfigDispatcherServletInitializer

以下是他们的继承关系

AbstractAnnotationConfigDispatcherServletInitializer
AbstractDispatcherServletInitializer
AbstractContextLoaderInitializer

以上的铺垫 全部都是为了引出 加载容器方法; 我们联合起来的逻辑:


我们知道 在子类没有onStartup方法 就会往父类中去寻找:

我们再AbstractDispatcherServletInitializer.onStartup方法中;我们可以发现;

这里 就准备开始创建我们的父容器; 点进去;


开始创建我们的父容器;
继续往里面看

开始注册我们的父容器; 只有register 没有refresh(); 这里我们就可以发现 这里仅仅是做了一个准备工作 容器并没有启动;最后返回一个容器;

创建一个监听器;

新创建一个监听器 然后把监听器 方到Listener里面;父容器到这里就阶段性结束了;然后回到 AbstractDispatcherServletInitializer.onStartup()方法中



代码部分 我就不一句一句去解释了 这里代码非常简单;如果喜欢看我博客的同学 应该都没很大问题;

这里记住 Tomcat会继续往下执行代码; 这时候会调起一个 ContextLoaderListener的contextInitialized 方法;

我们继续点进去看一下;

tomcat会继续往下执行 会调起 DispatcherServlet.init 方法 这里再上一篇文章中我写有写一部分;

重点关注这个方法;

protected WebApplicationContext initWebApplicationContext() 
		从域中提取出父容器;
		WebApplicationContext rootContext =		WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
		if (this.webApplicationContext != null) 
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) 
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) 
					if (cwac.getParent() == null) 
						设置父容器
						cwac.setParent(rootContext);
					
					开始初始化容器; 再上图中 进行 refresh
					configureAndRefreshWebApplicationContext(cwac);
				
			
		
		if (wac == null) 
			wac = findWebApplicationContext();
		
		if (wac == null) 
			wac = createWebApplicationContext(rootContext);
		
		if (!this.refreshEventReceived) 
			synchronized (this.onRefreshMonitor) 
				开始初始化Servlet里面的 组件 
				/**
				initMultipartResolver(context);
				initLocaleResolver(context);
				initThemeResolver(context);
				initHandlerMappings(context);
				initHandlerAdapters(context);
				initHandlerExceptionResolvers(context);
				initRequestToViewNameTranslator(context);
				initViewResolvers(context);
				initFlashMapManager(context);
				**/
				onRefresh(wac);
			
		
		if (this.publishContext) 
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		

		return wac;
	

这边 我们就已经把 SpringMVC的父子容器全部解释清楚了:

总结

1:Tomcat在启动时会通过SPI注册 ContextLoaderListener和DispatcherServlet对象,同时创建父子容器 :分别创建在ContextLoaderListener初始化时创建父 容器设置配置类;在DispatcherServlet初始化时创建子容器 即2个 ApplicationContext实例设置配置类

2: Tomcat在启动时执行ContextLoaderListener和DispatcherServlet对象的初始化方 法, 执行容器refresh进行加载

3:在子容器加载时 创建SpringMVC所需的Bean和预准备的数据;

4: 子容器需要注入父容器的Bean时(比如Controller中需要@Autowired Service的Bean); 会先从子容器中找,没找到会去父容器中找: 详情见 AbstractBeanFactory#doGetBean方法

 一般情况下,只有Spring和SpringMvc整合的时才会有父子容器的概念,
 作用:比如我们的Controller中注入Service的时候,
 发现我们依赖的是一个引用对象,
 那么他就 会调用getBean去把service找出来;
 但是当前所在的容器是web子容器,
 那么就会在这里的 先去父容器找

回到我们 文章当中的那个问题:

Spring和SpringMVC父子的容器之道---[上篇]

03 Spring的父子容器

Spring 父子容器

高效开发:Spring和SpringMVC的父子关系

高效开发:Spring和SpringMVC的父子关系

SpringMvc父子容器