• Spring 源码分析-1-启动


    Spring 源码分析-1-启动

    在web项目中使用spring的时候,我们会在web.xml中加入如下配置:

    	<listener>
    		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    	</listener>
    

    这个配置增加了一个listener,这个ContextLoaderListener 实现了ServletContextListener 。我们在日常工作中也会定义一些listener。用于在应用启动的时候做些什么。因为我们知道servelt容器在启动的时候,Listener 类中的contextInitialized()方法将会被调用。spring中ContextLoaderListener也把这个作为起始点来初始化,contextInitialized()方法的实现如下:

    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
      	private ContextLoader contextLoader;
    	public void contextInitialized(ServletContextEvent event) {
    		this.contextLoader = createContextLoader();//此方法直接return null了
    		if (this.contextLoader == null) {
    			this.contextLoader = this;
    		}
    		this.contextLoader.initWebApplicationContext(event.getServletContext());
    	}
    	@Deprecated
    	protected ContextLoader createContextLoader() {
    		return null;
    	}
    }
    

    根据上面的代码,在调用contextInitialized方法里边的代码很少,先是给contextLoader赋值了,然后调用了initWebApplicationContext方法。这个方法就是我们窥探的入口,它是在ContextLoader类中的,代码如下,可以先不要读这个代码,大概扫一眼,然后继续根据后面的文字描述跟踪逻辑:

    //为了方便初步的阅读,我删除了一些占用篇幅的地方
    public class ContextLoader {
      public static final String CONTEXT_CLASS_PARAM = "contextClass";
      public static final String CONTEXT_ID_PARAM = "contextId";
      public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
      public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";//web.xml里有,熟悉吧
      public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
      public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
      private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
      private static final Properties defaultStrategies;
      
      	static {
          //这个静态代码块读取了ContextLoader.properties这个配置文件。在spring源码中可以找到这个配置文件
          //内容只有一行 指定WebApplicationContext的实现类为XmlWebApplicationContext
    		try {
    			ClassPathResource resource = new ClassPathResource("ContextLoader.properties",                                                                                 ContextLoader.class);
    			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    		}
    		catch (IOException ex) {
    			throw new IllegalStateException("Could not load 'ContextLoader.properties': "
                                                + ex.getMessage());
    		}
    	}
      	private static volatile WebApplicationContext currentContext;
      	private WebApplicationContext context;
      	private BeanFactoryReference parentContextRef;
      
    	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    
    		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) {
                  // 这个create方法创建了一个ConfigurableWebApplicationContext实例
    				this.context = createWebApplicationContext(servletContext);
    			}
    			if (this.context instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = 
                                 (ConfigurableWebApplicationContext) this.context;
    				if (!cwac.isActive()) {//isActive默认为false,所以会进入if,执行下面的代码
    					if (cwac.getParent() == null) {
    						ApplicationContext parent = loadParentContext(servletContext);
    						cwac.setParent(parent);
    					}
                      //
    					configureAndRefreshWebApplicationContext(cwac, servletContext);
    				}
    			}
    			servletContext.setAttribute("一个变量名", 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.isInfoEnabled()) {
    				long t = System.currentTimeMillis() - startTime;
    				logger.info("Root WebApplicationContext: initialization completed in"+t+" ms");
    			}
    			return this.context;
    		}
    		catch (RuntimeException ex) {
    			logger.error("Context initialization failed", ex);
    			servletContext.setAttribute("一个很长变量名", ex);
    			throw ex;
    		}
    		catch (Error err) {
    			logger.error("Context initialization failed", err);
    			servletContext.setAttribute("一个很长变量名", err);
    			throw err;
    		}
    	}  
    }
    

    在上面的贴出的的代码中,我们可以看到这个ContextLoader类有一个静态代码块,静态代码块会在累加在的时候就执行了,这个代码块执行的内容很简单,就是找到一个名为“ContextLoader.properties”的配置文件,并将这个Properties赋给defaultStrategies变量。ContextLoader.properties的内容如下:

    org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
    

    我们继续看initWebApplicationContext方法。这个方法很长,但是实际上它主要做了两件事情:

    1.创建一个ConfigurableWebApplicationContext实例。

    2.根据创建的ConfigurableWebApplicationContext实例,来配置并刷新WebApplicationContext。

    先看第一步,创建ConfigurableWebApplicationContext,方法是createWebApplicationContext()。代码如下:

    	protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    		Class<?> contextClass = determineContextClass(sc);
    		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    			throw new ApplicationContextException("日志描述");
    		}
    		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    	}
    

    这个方法先调用determineContextClass来找到一个类,然后return语句中通过BeanUtils反射来创建这个类的实例并返回。

    determineContextClass类中,其实就是利用刚才读到的配置文件“ContextLoader.properties”,从这个文件中得到配置的类名,根据类名返回Class对象。代码简单就不贴了。

    在看第二步,刷新WebApplicationContext。即调用了configureAndRefreshWebApplicationContext(...)方法。这个方法里边做的事情很重要。先看看代码:

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
                // 这里我删除了一段无关紧要的代码,起逻辑和下面的两句一样。
                String xxx= ...;
    			wac.setId(xxx)
    		}
    
    		wac.setServletContext(sc);
           // 这里得到了我们在web.xml中配置的一个值,即:contextConfigLocation的值,也就是我们的spring文件
    		String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    		if (initParameter != null) {
              // 设置spring的配置文件
    			wac.setConfigLocation(initParameter);
    		}
    		customizeContext(sc, wac);//后面分析
    		wac.refresh();//后面分析
    	}
    

    方法中,最后两行的方法调用。先看customizeContext(sc,wac)方法,方法的里边通过serveltContext 的getInitParameter方法得到contextInitializerClasses。如果没配置,就什么也不做,进入之后,可以看到头两行的内容如下:

    List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
    				determineContextInitializerClasses(servletContext);
    if (initializerClasses.size() == 0) {
    	// no ApplicationContextInitializers have been declared -> nothing to do
        return;
    }
    

    第一行,通过determineContextInitializerClasser方法来获取配置的contextInitializerClasses变量值,这个值是一个class类名,多个的话用逗号隔开。如果没有配置的话,代码就会执行if size=0这一句,然后return了。

    第二行wac.refresh()方法调用非常重要。这个方法实际上完成了spring 容器的初始化,代码如下:

    	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();
    			// Prepare the bean factory for use in this context.
    			prepareBeanFactory(beanFactory);
    			try {
    				// Allows post-processing of the bean factory in context subclasses.
    				postProcessBeanFactory(beanFactory);
    				// Invoke factory processors registered as beans in the context.
    				invokeBeanFactoryPostProcessors(beanFactory);
    				// Register bean processors that intercept bean creation.
    				registerBeanPostProcessors(beanFactory);
    				// Initialize message source for this context.
    				initMessageSource();
    				// Initialize event multicaster for this context.
    				initApplicationEventMulticaster();
    				// Initialize other special beans in specific context subclasses.
    				onRefresh();
    				// Check for listener beans and register them.
    				registerListeners();
    				// Instantiate all remaining (non-lazy-init) singletons.
    				finishBeanFactoryInitialization(beanFactory);
    				// Last step: publish corresponding event.
    				finishRefresh();
    			}
    			catch (BeansException ex) {
    				destroyBeans();cancelRefresh(ex);
    				throw ex;
    			}
    		}
    	}
    

    至此spring容器初始化完成了,这个wac.refresh()代码暂时不做深究,本篇主要讨论的是web.xml中的一行配置如何导致了spring的启动过程。本文代码是基于spring3.2.5RELASE。

    SpringMVC是怎么工作的,SpringMVC的工作原理
    spring 异常处理。结合spring源码分析400异常处理流程及解决方法
    Netty系列
    Mybatis Mapper接口是如何找到实现类的-源码分析

  • 相关阅读:
    函数/方法的活动对象
    Function 详解(一)
    两栏自适应布局延展到多栏自适应布局
    height百分比以及高度自适应问题
    clearfix为什么用display:table,而不用display:block
    Nodejs写的搬家工具知识分享
    终于把自己多年前的百度文章搬到博客园了
    用Razor来生成模板 using razor for template
    一个request,但是controller被执行了多次的问题
    33个好用的图片轮显 jquery图片轮显
  • 原文地址:https://www.cnblogs.com/demingblog/p/7443714.html
Copyright © 2020-2023  润新知