• Spring MVC 九:Context层级(基于配置)


    Context层级的问题,前面文章应该已经说清楚了。

    只不过,前面文章是以注解方式举例说明的,通过配置方式怎么体现Context层级呢?有必要也说一下,毕竟现在很多项目都是基于xml配置实现的。

    web.xml

    基于配置的Spring MVC的入口就是web.xml文件,毕竟web.xml是基于web的应用的祖先入口…

    
    
      
      
        contextConfigLocation
        classpath:applicationContext.xml
      
    
      
        org.springframework.web.context.ContextLoaderListener
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    一定要搞清楚的是,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初始化的时候会读取。

    
      
      
        dispatcherServlet
        org.springframework.web.servlet.DispatcherServlet
        
          contextConfigLocation
          classpath:springmvc.xml
        
        1
      
      
        dispatcherServlet
        /
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    Spring-mvc.xml

    名字是可以在web.xml文件中指定的,不指定的话默认就是dispatcherServlet名-dispatcher.xml(需要读一下SpringMVC源码确认)。

    如果你想要Spring IoC容器和Spring MvC的web applicationContext容器分开的话,就在Spring-mvc.xml文件中指定包扫描路径仅扫描controller,否则,全扫描即可。反之,Spring Ioc容器存在的话,配置文件的扫描路径也要排除掉Controller的扫描:

        
            
        
    
    • 1
    • 2
    • 3
    • 4
    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());
    	}
    
    • 1
    • 2
    • 3
    • 4

    该方法会调用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行代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    最终会调用到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);
    		}
          ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    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();
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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();
                ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在refresh方法的obtainFreshBeanFactory()方法中:

    	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    		refreshBeanFactory();
    		return getBeanFactory();
    	}
    
    • 1
    • 2
    • 3
    • 4

    继续跟踪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);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    继续跟踪,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);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    最后一个方法调用,loadBeanDefinitions方法:

    	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    		String[] configLocations = getConfigLocations();
    		if (configLocations != null) {
    			for (String configLocation : configLocations) {
    				reader.loadBeanDefinitions(configLocation);
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    终于发现了根据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 八 - 内置过滤器

  • 相关阅读:
    Bootstrap Collapse的使用
    C++中.h和.hpp文件有什么区别?
    【软考软件评测师】第二十六章 计算机安全设计(其他知识)
    杭州公积金修改手机号信息
    网络中的一些概念
    Web应用防火墙的性能优化技术
    实验探究-ExecutorServiceAPI----未完待续!!!!
    ThreadLocal精进篇:子线程类InheritableThreadLocal
    大数据Flink(八十三):SQL语法的DML:With、SELECT & WHERE、SELECT DISTINCT 子句
    CF821 A. Consecutive Sum
  • 原文地址:https://blog.csdn.net/weixin_44612246/article/details/133247969