Spring MVC原理之Spring应用上下文(IoC容器)在Web容器中的启动分析

el/2024/3/2 12:25:49

Spring IoC是一个独立的模块,它并不是直接在Web容器中发挥作用的。如果要在Web环境中使用IoC容器,需要Spring为IoC设计一个启动过程,把IoC容器导人,并在Web容器中建立起来。具体说来,这个启动过程是和Web容器的启动过程集成在一起的。在这个过程中,一方面处理Web容器的启动,另一方面通过设计特定的Web容器拦截器,将IoC容器载入到Web环境中来.并将其初始化。在这个过程建立完成以后, IoC容器才能正常工作,而SpringMVC是建立在IoC容器的基础上的,这样才能建立起MVC框架的运行机制,从而响应从Web容器传递的HTTP请求。

下面以Tomcat容器为例子进行分析。在Tomcat中,web.xml是应用的部署描述文件。在web.xm.中常常看到与Spring相关的部署描述如下:

    <!-- 加载spring容器 --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/applicationContext-*.xml</param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- springmvc的前端控制器 --><servlet><servlet-name>tt-manager</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" --><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/springmvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>tt-manager</servlet-name><url-pattern>/</url-pattern></servlet-mapping>

这里看到的部署描述是Spring MVCTomcat的接口部分。在这个部署描述文件中。首先定义了一个Servlet对象,它是Spring MVCDispatcherServlet。这个DispatcherServletMVC中很重要的一个类,起着分发请求的作用。同时,在部署描述中,为这个DispatcherServlet定义了应的URL映射,这些URL映射为这个Servlet指定了需要处理的HTTP请求。

context-param参数的配里用来指定Spring IoC容器读取Bean定义的XML文件的路径,在这里。这个配且文件被定义为classpath:spring/applicationContext-*.xml下的文件中,可以看到Spring应用的Bean配置。最后,作为Spring MVC的启动类,Contex tLoaderListener被定义为一个监听器,这个监听器是与Web服务器的生命周期相关联的。由ContextLoaderListener监听器负责完成IoC容器在Web环魂中的启动工作.

IoC容器启动的基本过程

IOC容器的启动过程就是建立上下文的过程,该上下文是与SerVietContext相伴而生的,同时也是IoC容器在Web应用环境中的具体表现之一。由ConteztLoaderListener启动的上下文为根上下文。在根上下文的基础上.还有一个与Web MVC相关的上下文用来保存控制器(DispatcherServlet)需要的MVC对象,作为根上下文的子上下文,构成一个层次化的上下文体系。

在Web容器中启动Spring应用程序时.首先建立根上下文.然后建立这个上下文体系的,这个上下文体系的建立是由ContextLoder来完成的。具体如下图:

web.xml中,已经配了ContextLoaderListener,这个ConteztLoaderListenerSpring提供的类,是为在Web容器中建立IoC容器服务的。它实现了ServletContextListener接口。这个接口是在Servlet API中定义的,提供了与Servlet生命周期结合的回调,比如contextlnitialized方法和contextDestroyed方法。
而在Web容器中,建立WebApplicationContext的过程,是在contextlnitialized的接口实现中完成的。具体的载入IoC容器的过程是由ContextLoaderListener交由ContextLoader来完成的,而ContextLoader本身就是ContextLoaderListener的基类.它们之间的类关系如下图:

总之,ContextLoaderListener是SpringMVC的入口,通过父类ContextLoader来实现IoC容器的初始化,通过实现ServletContextListener接口,通过监听来创建或销毁WebApplicationContext(IoC容器)。

Web容器中的上下文设计

为了方便在Web环境中使用IoC容器,Spring为Web应用提供了上下文的扩展接口WebApplicationContext来满足启动过程的需要。

在这个类继承关系中,可以从熟悉的XmlWebApplicationContext入手来了解它的接口实现.在接口设计中,最后是通过ApplicationContex接口与BeanFactory接口对接的,而对于具体的功能实现,很多都是封装在其基类AbstractRefreshableWebApplicationContext中完成的。
WebApplicationContextXmlWebApplicationContext中定义了很多常量,如下:

public interface WebApplicationContext extends ApplicationContext {//用于在ServletContext中存取 根上下文String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";String SCOPE_REQUEST = "request";String SCOPE_SESSION = "session";String SCOPE_APPLICATION = "application";String SERVLET_CONTEXT_BEAN_NAME = "servletContext";String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";@NullableServletContext getServletContext();   //可以取得Web容器的ServletContext
}
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {//默认的Bean信息路径public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";//默认的配置文件位里在/WEB-INF/目录下public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";//默认的配置文件后缀名.xml文件public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";public XmlWebApplicationContext() {}//refresh()时启动protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);beanDefinitionReader.setEnvironment(this.getEnvironment());beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));this.initBeanDefinitionReader(beanDefinitionReader);this.loadBeanDefinitions(beanDefinitionReader);}protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {}//这个初始化过程是由refreshBeanFactory方法来完成的.这里只是负责载入BeanDefinitionprotected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {String[] configLocations = this.getConfigLocations();if (configLocations != null) {String[] var3 = configLocations;int var4 = configLocations.length;for(int var5 = 0; var5 < var4; ++var5) {String configLocation = var3[var5];reader.loadBeanDefinitions(configLocation);}}}//这里是取得Resource位置的地方.使用了设定的默认配置位置!默认的配置位置是/WEB-INF/applicationContext.xmlprotected String[] getDefaultConfigLocations() {return this.getNamespace() != null ? new String[]{"/WEB-INF/" + this.getNamespace() + ".xml"} : new String[]{"/WEB-INF/applicationContext.xml"};}}

从代码中可以看到,在XmlWebApplicationContext中,基本的上下文功能都已经通过类的继承获得,这里需要处理的是,如何获取Bean定义信息,在这里,就转化为如何在Web容器环境如这里指定的/WEB -INF/applicationContext.xml中获得Bean定义信息。这就解释了为什么我们开发的时候,web.xml文件一般放在/WEB -INF/下。

ContextLoader的设计与实现

对于Spring承载的Web应用而言,可以指定在Web应用程序启动时载人IoC容器(或者称为WebAppl icationCon text)。这个功能是由ContextLoaderListener这样的类来完成的,它是在Web容器中配置的监听器。这个ContextLoaderListener通过使用ContextLoader来完成实际的WebApplicationContext,也就是IoC容器的初始化工作。这个ContextLoader就像Spring应用程序在Web容器中的启动器。这个启动过程是在Web容器中发生的,所以需要根据Web容器部署的要求来定义ContextLoader。

下面分析具体的根上下文的载入过程。在ContextLoaderListener中,实现的是ServletContextListener接口,这个接口里的函数会结合Web容器的生命周期被调用。因为ServletContextListener是ServletContext的监听者,如果ServletContext发生变化.会触发出相应的事件,而监听器一直在对这些事件进行监听,如果接收到了监听的事件,就会做出预先设计好的响应动作。

由于ServletContext的变化而触发的监听器的响应具体包括:在服务器启动时,ServletContext被创建。服务器关闭时,ServletContext将被销毁等。对应这些事件及Web容器状态的变化,在监听器中定义了对应的事件响应的回调方法。比如在服务器启动时,ServletContextListener的contextlnitialized()方法被调用,服务器将要关闭时,ServletContextListener的contextDestroyed()方法被调用。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {public ContextLoaderListener() {}public ContextLoaderListener(WebApplicationContext context) {super(context);}//在服务器启动时,ServletContext被创建public void contextInitialized(ServletContextEvent event) {this.initWebApplicationContext(event.getServletContext()); //具体的初始化工作交给ContextLoader来完成}//服务器关闭时,ServletContext将被销毁public void contextDestroyed(ServletContextEvent event) {this.closeWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());}
}

看看ContextLoader中的创建方法:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {//判断是否已经有上下文存在,key就是之前WebApplicationContext定义的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!");} else {Log logger = LogFactory.getLog(ContextLoader.class);servletContext.log("Initializing Spring root WebApplicationContext");if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}long startTime = System.currentTimeMillis();try {if (this.context == null) {//创建根上下文this.context = this.createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;if (!cwac.isActive()) {if (cwac.getParent() == null) {ApplicationContext parent = this.loadParentContext(servletContext);//这里载入根上下文的双亲上下文cwac.setParent(parent);}this.configureAndRefreshWebApplicationContext(cwac, servletContext);}}//将根上下文存储到servletContext中,key是之前WebApplicationContext中定义的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.isDebugEnabled()) {logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");}return this.context;} catch (RuntimeException var8) {logger.error("Context initialization failed", var8);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);throw var8;} catch (Error var9) {logger.error("Context initialization failed", var9);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);throw var9;}}}//创建根上下文的方法protected WebApplicationContext createWebApplicationContext(ServletContext sc) {//这里判断使用什么样的类在Web容界中作为IoC容界Class<?> contextClass = this.determineContextClass(sc);if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");} else {//直接实例化要产生的IOC容器return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);}}

在初始化这个上下文以后,该上下文会被存储到SevletContext中,这样就建立了一个全局的关于整个应用的上下文。同时,在启动Spring MVC时.我们还会看到这个上下文被以后的DispatcherServlet在进行自己持有的上下文的初始化时,设置为DispatcherServlet自带的上下文的双亲上下文。


http://www.ngui.cc/el/4466046.html

相关文章

Spring MVC原理之Spring MVC的设计与实现

接上文 SpringMVC原理之Spring应用上下文&#xff08;Ioc容器&#xff09;在Web容器中的启动分析。 在前文的分析过程中&#xff0c;了解了 Spring的上下文体系通过ContextLoader和DispatcherServiet建立并初始化的过程。在完成对ContextLoaderListener的初始化以后&#xff0c…

Spring JDBC的设计与实现

设计原理 在Spring JDBC中&#xff0c;JdbcTemplate 是一个主要的模板类&#xff0c;它的类继承关系如图&#xff1a; 从类继承关系上来看&#xff0c;JdbcTemplate继承了基类JdbcAccessor和接口类JdbcOperation。在基类JdbcAccessor的设计中&#xff0c;对DataSource数据源…

Spring事务处理浅析

类层次结构 在Spring的事务处理模块中.可以看到的类层次结构如图&#xff1a; 可以看到&#xff0c;Spring事务处理模块是通过AOP功能来实现声明式事务处理的&#xff0c;比如事务属性的配置和读取&#xff0c;事务对象的抽象等。因此&#xff0c;在Spring事务处理中&#xff…

dubbo管理控制台dubbo-admin安装

资源&#xff1a; dubbo源码 和 中文文档 下载源码 下载dubbo-admin模块代码&#xff0c;这个还真不太好找&#xff0c;可能是因为dubbo迁移至Apache 维护后&#xff0c;代码还没迁移好&#xff1f;&#xff1f;&#xff1f; 我是在 https://github.com/apache/incubator-du…

React Native 项目环境配置、调试以及打包

去年在做React Native混合开发的时候&#xff0c;写过几篇笔记&#xff1a; react-native对android物理返回键back的监听以及基类封装 react native导航navigator react-native 与安卓端通信 android 端集成react native Fetch 网络请求简单封装&#xff0c;支持超时入口 …

Rest 概要

REST RESTful Representational State Transfer。是在因特网上提供计算机系统之间的互操作性的一种方式。基于Http协议的资源传递&#xff0c;包括JSON、XML和文本等。 统一接口 资源识别&#xff08;Identification of resources&#xff09; URI&#xff08;Uniform Resou…

构建一个REST风格的Web服务

你需要什么 大约15分钟IntelliJ IDEA或其他编辑器JDK 1.8或更高版本Maven 3.2 你会建立什么 您将在以下地址构建一个将接受HTTP GET请求的服务&#xff1a; http://localhost:8080/greeting 会返回一个JSON 格式的数据&#xff1a; {"id":1,"content"…

调度任务Scheduling Tasks

你需要什么 大约15分钟IntelliJ IDEA或其他编辑器JDK 1.8或更高版本Maven 3.2 你会建立什么 您将构建一个应用程序&#xff0c;该应用程序使用Spring的 Scheduled 注解&#xff0c;每5秒打印一次当前时间。 构建步骤 1、添加maven依赖 <dependency><groupId>…

消费RESTful Web服务

你需要什么 大约15分钟IntelliJ IDEA或其他编辑器JDK 1.8或更高版本Maven 3.2 你会建立什么 您将构建一个使用Spring RestTemplate的应用程序来获取 http://gturnquist-quoters.cfapps.io/api/random 中的随机Spring Boot数据。 构建步骤 1、添加maven依赖&#xff1a; &l…

上传文件Uploading Files

你需要什么 大约15分钟IntelliJ IDEA或其他编辑器JDK 1.8或更高版本Maven 3.2 你会建立什么 您将创建一个接受文件上传的Spring Boot Web应用程序。您还将构建一个简单的HTML界面来上传测试文件。 构建步骤 1、添加maven依赖。 <dependency><groupId>org.spri…