Spring MVC 是建立在 IOC 容器基础上的,那么该容器是如何在 Web 环境中被载入并起作用的 ?通常我们需要配置一个监听器(ContextLoaderListener)。
所谓ContextLoaderListener,就是在web部署描述符即web.xml里面经常配置的一个监听器,如下
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
那么配合它一起使用的,经常是context-param,用来指定Spring要加载的配置文件,比如
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/teach-servlet.xml</param-value>
</context-param>
<!-- Spring MVC -->
<servlet>
<servlet-name>teach</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>teach</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
上面两段就是在使用Spring MVC时,常用的配置,DispatcherServlet作为Spring MVC控制器的核心调度器。至于 teach-servlet.xml 就是配置一些Spring MVC需要使用的视图解析器等。
context-param 用来指定 Spring IOC 容器 Bean 定义的XML文件的路径,contextLoaderListener实现了ServletContextListener接口,与 Web 服务器的生命周期相关联,由ContextLoaderListener负责完成 IOC 容器在 Web 环境中的启动工作。
先来看看总体上的时序图:
ContextLoaderListener实现了ServletContextListener接口,该接口是 Servlet API 中定义的,提供了与 Servlet 生命周期结合的回调:contextInitialized 与 contextDestroyed。其中建立 WebApplicationContext 的过程是在 contextInitialized 中完成,具体交给了 ContextLoader , 关系如下
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
关于 WebApplicationContext ,为了方便在 Web 环境中使用 IOC 容器, Spring 为 Web 应用提供了上下文的拓展接口 WebApplicationContext 来满足启动过程中的需求,默认加载 XmlWebApplicationContext。该类继承可追溯到 ApplicationContext , 在其基础上增加了 Web 环境和 XML 配置定义的处理。
ContextLoaderListener 创建的 WebApplicationContext 我们称之为根上下文 ,在 DispatcherServlet 中也会创建一个 WebApplicationContext 我们称之为子上下文,子上下文可以获得父上下文的 bean,对于子上下文 getBean 首先会去父上下文获取,得不到就去子上下文找,父上下文得不到子上下文的 bean,子上下文之间互相也访问不到。
根上下文的配置文件默认是 WEB-INF/appilicationContext.xml ,我们一般将除 Controller 之外的类在该文件中声明;DispatcherServlet 创建的子上下文的配置文件默认在 WEB-INF/xxx-servlet.xml (xxx指的是标签 的值),我们在该配置文件中声明 Controller bean。
根上下文 与 子上下文 都被存储在 ServletContext 中,如根上下文在ServletContext 中的key 为 WebApplicationContext.class.getName() + “.ROOT”,想访问根上下文可以通过 WebApplicationContext.getWebApplicationContext (ServletContext sc) 获取。
ContextLoaderListener
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
initWebApplicationContext
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!");
} else {
......
try {
if (this.context == null) {
//创建WebApplicationContext
this.context = this.createWebApplicationContext(servletContext);
}
......
}
// 存储进servletContext中, key为 WebApplicationContext.class.getName() + ".ROOT
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
//debug发现这里会将WebApplicationContext赋给currentContext变量
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
} else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
......
return this.context;
} catch (Error | RuntimeException var8) {
......
}
}
}
来看看创建WebApplicationContext的逻辑
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 获得上下文WebApplicationContext的实现。默认是XmlWebApplicationContext
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 {
//反射得到WebApplicationContext对象
return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
}
}
determineContextClass
protected Class<?> determineContextClass(ServletContext servletContext) {
// 若你在web.xml中设置的<context-param>里声明了
// contextClass的值,则直接利用反射得到其Class
String contextClassName = servletContext.getInitParameter("contextClass");
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
} catch (ClassNotFoundException var4) {
throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
}
} else {
// 这里返回XmlWebApplicationContext
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
// 反射得到XmlWebApplicationContext.Class
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
} catch (ClassNotFoundException var5) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
}
}
}
这里servletContext.getInitParameter(“contextClass”)获取web.xml中设置的contextClass属性的值,我们可以自己实现一个WebApplicationContext,然后在web.xml中如下配置
<context-param>
<param-name>contextClass</param-name>
<param-value>xxx</param-value>
</context-param>
设置的是全局的信息,通过getInitPatameter获取获取。在每个中还会有自己的,通过该servlet的ServletConfig的getInitParameter方法获取。
如果没有在web.xml中配置,则加载默认实现类XmlWebApplicationContext,逻辑如下
defaultStrategies,在ContextLoader的静态代码块中
static {
try {
ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException var1) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage());
}
currentContextPerThread = new ConcurrentHashMap(1);
}
就是加载ContextLoader.properties文件,来看看该文件的内容
org.springframework.web.context.WebApplicationContext=
org.springframework.web.context.support.XmlWebApplicationContext
XmlWebApplicationContext实例以键值对的形式存储在ServletContext中,其键为WebApplicationContext.class.getName() + “.ROOT”。
上述就是IOC 容器在 Web 容器中的启动过程,在初始化这个上下文后,该上下文会被存储到 ServletContext 中,这样就建立了一个全局的关于整个应用的上下文。之后 DispatcherServlet 在进行自己持有的上下文的初始化时,将该全局上下文设为自己的父上下文。
来源:CSDN
作者:jcsyl_mshot
链接:https://blog.csdn.net/jcsyl_mshot/article/details/104475453