Context层级的问题,前面文章应该已经说清楚了。
只不过,前面文章是以注解方式举例说明的,通过配置方式怎么体现Context层级呢?有必要也说一下,毕竟现在很多项目都是基于xml配置实现的。
web.xml
基于配置的Spring MVC的入口就是web.xml文件,毕竟web.xml是基于web的应用的祖先入口......
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<!--1、启动Spring的容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
一定要搞清楚的是,context-param配置是用来指定的Spring容器的配置文件所在路径的。必须是和ContextLoaderListener一起配合起作用的。ContextLoaderListener读取context-param的配置来完成Spring IoC容器的初始化的。
Spring IoC容器和Spring MVC的Servlet Web ApplicationContext容器不是必须要分开的,也可以配置为同一个容器。
比如以上配置不指定(不配置Spring IoC容器),也就是去掉contextConfigLocation以及ContextLoaderListener,让Spring MVC的容器担负起Spring IoC容器的职责,也是可以的。
web.xml下面继续配置spring MVC的xml文件所在路径,DispathcerServlet初始化的时候会读取。
<!--2、springmvc的前端控制器,拦截所有请求 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Spring-mvc.xml
名字是可以在web.xml文件中指定的,不指定的话默认就是dispatcherServlet名-dispatcher.xml(需要读一下SpringMVC源码确认)。
如果你想要Spring IoC容器和Spring MvC的web applicationContext容器分开的话,就在Spring-mvc.xml文件中指定包扫描路径仅扫描controller,否则,全扫描即可。反之,Spring Ioc容器存在的话,Ioc容器的配置文件的扫描路径也要排除掉Controller的扫描:
<context:component-scan base-package="org.example.service">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
Servlet和根容器的关系
下图一目了然的说明了两者之间的关系:
Servlet容器存放Controller、VIewResolver、HanderMapping等DispatcherServlet的相关对象,根容器可以存放其他Service、Repositories等对象。
一个DispatcherServlet可以对应的有一个Servlet WebApplicationContext容器,一个Web应用可以有多个DispatcherServlet(这种应用其实比较少见),所以一个应用可以有多个Servlet WebApplicationContext容器。但是一般情况下,即使有多个Servlet容器,一个应用也希望只有一个根容器,以便在不同的Servlet容器之间共享根容器的对象。
根容器初始化过程
xml配置文件的情况下,根容器要依靠ContextLoaderListener来初始化。ContextLoaderListener是Spring MVC实现的ServletContextListener接口的实现类,ServletContextListener是Java Servlet的接口。Servlet标准约定,在Servlet容器初始化的过程中,会回调ServletContextListener接口的contextInitialized方法。
所以如果我们在web.xml文件中配置了ContextLoaderListener,那么,Tomcat在Servlet容器初始化的过程中就会回调ContextLoaderListener的contextInitialized方法:
@Override
public void contextInitialized(ServletContextEvent event) {
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!");
}
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) {
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);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
...省略n行代码
最终会调用到configureAndRefreshWebApplicationContext方法:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
//就是在这儿读取web.xml的contextConfigLocation的
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
...
configureAndRefreshWebApplicationContext方法会读取web.xml中contextConfigLocation配置,Spring IoC容器的初始化配置文件applicationContext.xml文件名、以及所在位置就是在这儿被读入的。
读入配置文件信息之后,调用refresh方法:
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
refresh方法是Spring framework的方法,具体就不在这儿深入研究了,我们只是简单跟踪一下配置文件的加载过程:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
...
在refresh方法的obtainFreshBeanFactory()方法中:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
继续跟踪refreshBeanFactory();方法:
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
继续跟踪,loadBeanDefinitions(beanFactory);方法,会调用到XmlWebApplicationContext类中:
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
最后一个方法调用,loadBeanDefinitions方法:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
终于发现了根据configLocation通过loadBeanDefinitions方法读取配置文件、调用beandefinition的代码逻辑。
可以发现代码是可以支持多个配置文件的!
从代码的角度,我们也完成了xml配置方式下,SpringMVC与spring framework集成,完成spring Ioc容器的初始化过程!
与前面两篇文:Spring MVC 四:Context层级/Spring MVC 五 - Spring MVC的配置和DispatcherServlet初始化过程:Context层级结合,我们就完成了基于注解的、以及基于xml配置文件两种方式下的Spring MVC框架下,Spring IoC容器的初始化代码的分析!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。