spring啟動入口
(44條消息) spring啟動入口_lieyanhaipo的專欄-CSDN博客_spring 入口類
一、Spring與WEB容器整合
web項目中,Spring啟動是在web.xml配置監聽器,如下所示:
- <!-- 配置Spring上下文監聽器 -->
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
可以看看ContextLoaderListener類,它實現了Tomcat容器的ServletContextListener接口,所以它與普通的Servlet監聽是一樣的。同樣是重寫到兩個方法:contextInitialized()方法在web容器初始化時執行,contextDestroyed()方法在容器銷毀時執行。
WEB容器啟動時會觸發初始化事件,ContextLoaderListener監聽到這個事件,其contextInitialized()方法會被調用,在這個方法中Spring會初始化一個根上下文,即WebApplicationContext。這是一個接口,其實際默認實現類是XmlWebApplicationContext。這個就是Spring IOC的容器,其對應bean定義的配置信息由web.xml中的context-param來指定
- <!-- 配置Spring配置文件路徑 -->
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath*:applicationContext.xmlclasspath*:applicationContext-shiro.xml
- </param-value>
- </context-param>
在Spring IOC 容器啟動初始化完畢之后,會將其儲存到ServletContext中。形式如下:servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, WebApplicationContext context);

在ContextLoaderListener類中,只是實現了ServletContextListener提供的到兩個方法,Spring啟動主要的邏輯在父類ContextLoader的方法initWebApplicationContext實現。ContextLoaderListener的作用就是啟動web容器時自動裝配ApplicationContext的配置信息。更細化一點講,Spring的啟動過程其實就是Spring IOC容器的啟動過程。
- public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
- /**
- * Initialize the root web application context.
- */
- @Override
- public void contextInitialized(ServletContextEvent event) {
- initWebApplicationContext(event.getServletContext());
- }
- /**
- * Close the root web application context.
- */
- @Override
- public void contextDestroyed(ServletContextEvent event) {
- closeWebApplicationContext(event.getServletContext());
- ContextCleanupListener.cleanupAttributes(event.getServletContext());
- }
- }
二、ContextLoader剖析
從上一部分ContextLoaderListener類可以得知,ContextLoader實際執行Spring容器的初始化,Spring整個的配置工作都是在ContextLoader完成的,這里參數ServletContextEvent由web容器提供,不做說明。ContextLoaderListener很好理解,所以我們主要看ContextLoader類。用Maven引入Spring的源碼,打開ContextLoader類,類注釋的第一行就是
- /**
- * Performs the actual initialization work for the root application context.
- ......
- **/
用google翻譯:實際執行根應用上下文的初始化工作。這里的根應用上下文就是上文所寫的WebApplicationContext。我們先看看ContextLoader的時序圖
ContextLoader類initWebApplicationContext()方法
- /**
- * Initialize Spring's web application context for the given servlet context,
- * using the application context provided at construction time, or creating a new one
- * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
- * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
- * @param servletContext current servlet context
- * @return the new WebApplicationContext
- * @see #ContextLoader(WebApplicationContext)
- * @see #CONTEXT_CLASS_PARAM
- * @see #CONFIG_LOCATION_PARAM
- */
- public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
- //判斷ServletContext是否已經存在WebApplication,如果存在則拋出異常
- 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!");
- }
- 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 {
- // Store context in local instance variable, to guarantee that
- // it is available on ServletContext shutdown.
- if (this.context == null) {
- //創建WebApplicationContext(下文有說明)
- 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) {
- // 得到根上下文的父上下文,然后設置到根上下文 。一般的web項目parent為空
- ApplicationContext parent = loadParentContext(servletContext);
- cwac.setParent(parent);
- }
- //從web.xml加載參數,初始化跟上下文WebApplicationContext,創建bean工廠和bean對象。
- //這個過程比較麻煩,下一篇文章專門分析
- configureAndRefreshWebApplicationContext(cwac, servletContext);
- }
- }
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 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.isDebugEnabled()) {
- logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
- WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
- }
- if (logger.isInfoEnabled()) {
- long elapsedTime = System.currentTimeMillis() - startTime;
- logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
- }
- return this.context;
- }
- catch (RuntimeException ex) {
- logger.error("Context initialization failed", ex);
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
- throw ex;
- }
- catch (Error err) {
- logger.error("Context initialization failed", err);
- servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
- throw err;
- }
- }
在這個方法中ServletContext是由web容器監聽器(ContextLoaderListener)提供。首先判斷servlectContext中是否已經存在根上下文,如果存在,則拋出異常;否則通過createWebApplicationContext方法創建新的根上下文。然后通過loadParentContext()方法為其設置父上下文。再通過configureAndRefreshWebApplicationContext為根上下文構建bean工廠和bean對象。 最后把上下文存入servletContext,並且存入currentContextPerThread。至此初始化過程完畢,接下來可以獲取WebApplicationContext,進而用getBean("bean name")得到bean。
createWebApplicationContext()方法用來創建根上下文:
- protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
- //從web.xml配置的contextClass參數中獲取上下文類名,如果contextClass為空,則使用默認的。
- //下文有說明
- Class<?> contextClass = determineContextClass(sc);
- //根上下文必須是ConfigurableWebApplicationContext的子類,否則拋出異常
- if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
- throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
- "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
- }
- //BeanUtils.instantiateClass工具方法,根據類名創建類
- return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
- }
determineContextClass()方法返回根上下文的類名
- protected Class<?> determineContextClass(ServletContext servletContext) {
- //從web.xml獲得參數contextClass,在一般的web項目中,此參數為null
- String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
- if (contextClassName != null) {
- try {
- return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
- }
- catch (ClassNotFoundException ex) {
- throw new ApplicationContextException(
- "Failed to load custom context class [" + contextClassName + "]", ex);
- }
- }
- else {
- //獲得根上下文WebApplicationContext的默認實現類的類名,defaultStrategies是Properties類型,
- //在CotnextLoader類開頭static語句塊中初始化
- contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
- try {
- return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
- }
- catch (ClassNotFoundException ex) {
- throw new ApplicationContextException(
- "Failed to load default context class [" + contextClassName + "]", ex);
- }
- }
- }
WebApplicationContext默認實現類的類名獲取
- static {
- try {
- //獲取當前包下面的ContextLoader.properties文件,文件內容是:
- //org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
- ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
- defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
- }
- catch (IOException ex) {
- throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
- }
- }