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接口是如何找到实现类的-源码分析

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 连接 连接池产生原因 连接池实现原理 小结 TEMPERANCE:Eat not to dullness;drink not to elevation.节制
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 一个优秀的工程师和一个普通的工程师的区别,不是满天飞的架构图,他的功底体现在所写的每一行代码上。-- 毕玄 1. 命名风格 【书摘】类名用 UpperCamelC
今天犯了个错:“接口变动,伤筋动骨,除非你确定只有你一个人在用”。哪怕只是throw了一个新的Exception。哈哈,这是我犯的错误。一、接口和抽象类类,即一个对象。先抽象类,就是抽象出类的基础部分,即抽象基类(抽象类)。官方定义让人费解,但是记忆方法是也不错的 —包含抽象方法的类叫做抽象类。接口
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket一、引子文件,作为常见的数据源。关于操作文件的字节流就是 —FileInputStream&amp;FileOutputStream。
作者:泥沙砖瓦浆木匠网站:http://blog.csdn.net/jeffli1993个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=FhFAoaWwjP29_Aonqz
本文目录 线程与多线程 线程的运行与创建 线程的状态 1 线程与多线程 线程是什么? 线程(Thread)是一个对象(Object)。用来干什么?Java 线程(也称 JVM 线程)是 Java 进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。 Ja
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket在面向对象编程中,编程人员应该在意“资源”。比如?1String hello = &quot;hello&quot;; 在代码中,我们
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 《程序兵法:Java String 源码的排序算法(一)》 文章工程:* JDK 1.8* 工程名:algorithm-core-le
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 一、父子类变量名相同会咋样? 有个小故事,今天群里面有个人问下面如图输出什么? 我回答:60。但这是错的,答案结果是 40 。我知错能改,然后说了下父子类变
作者:泥瓦匠 出处:https://www.bysocket.com/2021-10-26/mac-create-files-from-the-root-directory.html Mac 操作系统挺适合开发者进行写代码,最近碰到了一个问题,问题是如何在 macOS 根目录创建文件夹。不同的 ma
作者:李强强上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算。这一讲,泥瓦匠带你走进Java中的进制详解。一、引子在Java世界里,99%的工作都是处理这高层。那么二进制,字节码这些会在哪里用到呢?自问自答:在跨平台的时候,就凸显神功了。比如说文件读写,数据通信,还
1 线程中断 1.1 什么是线程中断? 线程中断是线程的标志位属性。而不是真正终止线程,和线程的状态无关。线程中断过程表示一个运行中的线程,通过其他线程调用了该线程的 方法,使得该线程中断标志位属性改变。 深入思考下,线程中断不是去中断了线程,恰恰是用来通知该线程应该被中断了。具体是一个标志位属性,
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want需求 项目在设计表的时候,要处理并发多的一些数据,类似订单号不能重复,要保持唯一。原本以为来个时间戳,精确到毫秒应该不错了。后来觉得是错了,测试环境下很多一
纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 加微信:bysocket01
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want.文章Points:1、介绍RESTful架构风格2、Spring配置CXF3、三层初设计,实现WebService接口层4、撰写HTTPClient 客户
Writer :BYSocket(泥沙砖瓦浆木匠)什么是回调?今天傻傻地截了张图问了下,然后被陈大牛回答道“就一个回调…”。此时千万个草泥马飞奔而过(逃哈哈,看着源码,享受着这种回调在代码上的作用,真是美哉。不妨总结总结。一、什么是回调回调,回调。要先有调用,才有调用者和被调用者之间的回调。所以在百
Writer :BYSocket(泥沙砖瓦浆木匠)一、什么大小端?大小端在计算机业界,Endian表示数据在存储器中的存放顺序。百度百科如下叙述之:大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加
What is a programming language? Before introducing compilation and decompilation, let&#39;s briefly introduce the Programming Language. Programming la
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket泥瓦匠喜欢Java,文章总是扯扯Java。 I/O 基础,就是二进制,也就是Bit。一、Bit与二进制什么是Bit(位)呢?位是CPU
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocket一、前言 泥瓦匠最近被项目搞的天昏地暗。发现有些要给自己一些目标,关于技术的目标:专注很重要。专注Java 基础 + H5(学习) 其他操作系统,算法,数据结构当成课外书博览。有时候,就是那样你越是专注方面越