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容器存在的话,配置文件的扫描门路也要排除掉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); 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容器的初始化代码的剖析!
上一篇 Spring MVC 八 - 内置过滤器