Spring容器及其初始化

Spring的核心在於其IOC和AOP機制,而IOC機制的關鍵在於Spring中的容器,而Spring中的容器是並非單單指一個獨立的對象.根據Spring中組件的職能,做用的不一樣,咱們將不一樣的組件對象分別注入到兩類Context容器中,而且這兩類Context容器存在着繼承與被繼承的關係.前端

1.Spring中的幾個概念

  • 在閱讀Spring源碼或相關文獻時,會常常遇到這幾個名詞:WebApplicationContext---ApplicationContext---ServletContext---ServletConfig.這些名詞很相近但適用範圍有所不一樣,容易形成spring內部實現的理解混淆,因此首先大體解釋這幾個名詞.java

    • ServletContext:這個是來自Servlet規範裏的概念,它是Servlet用來與容器間進行交互的接口的組合.也就是,這個接口定義了一系列的方法,Servlet經過這些方法能夠很方便地與所在的容器進行一些交互.從它的定義中也能夠看出在一個應用中(一個JVM)只有一個ServletContext.也就是說,容器中全部的Servlet都共享一個ServletContext.
    • ServletConfig:它與ServletContext的區別在於,ServletConfig是針對servlet而言的,每一個servlet都有它獨特的ServletConfig信息,相互之間不共享.
    • ApplicationContext:這個類是Spring容器功能的核心接口,它是Spring實現IOC功能最重要的接口.從它的名字能夠看出,它維護了整個程序運行期所須要的上下文信息,注意這裏的應用程序並不必定是web程序.在Spring中容許存在多個ApplicationContext,這些ApplicationContext相互之間造成父子,繼承與被繼承的關係,這也是一般咱們所說的:在Spring中存在兩個context,一個Root Application Context,一個是Servlet Application Context,這一點在後續會詳細闡述.
    • WebApplicaitonContext:這個接口只是ApplicationContext接口的一個子接口,只不過它的應用形式是web,它在ApplicaitonContext的基礎上,添加了對ServletContext的引用.

2.Spring容器的初始化

SpringMVC配置文件中,咱們一般會配置一個前端控制器 DispatcherServlet和監聽器 ContextLoaderListener來進行 Spring應用上下文的初始化

image.png

  • 在以前的闡述中可知,ServletContext是容器中全部Servlet共享的配置,它是應用於全局的.根據Servlet規範的規定,根據以上監聽器的配置,其中context-param指定了配置文件的未知.在容器啓動後初始化ServletContext時,監聽器會自動加載配置文件,來初始化Spring的根容器Root Application Context.
  • 一樣ServletConfig是針對每一個Servlet進步配置的,所以它的配置是在servlet的配置中,根據以上DispatcherServlet的配置,配置中init-param一樣指定了在Servlet初始化調用#init方法時加載配置信息的xml文件,並初始化Spring應用容器Servlet Application Context.

接下來咱們具體分析Spring容器初始化:web

  • 關於ApplicationContext的配置,首先,在ServletContext中配置context-param參數.經過監聽器會生成所謂的Root Application Context,而每一個DispatcherServlet中指定的init-param參數會生成Servlet Application Context.並且它的parent就是ServletContext中生成的Root Application Context.所以在ServletContext中定義的全部配置都會繼承到DispatcherServlet中,這在以後代碼中會有直觀的提現.

1. Root Application Contextspring

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    //...................
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

}

--------------------------------------------------------------------------------------------------------------------------------------------------------

public class ContextLoader {
    //...................
 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!");
        } else {
          //...................
            try {
                if(this.context == null) {
                    this.context = this.createWebApplicationContext(servletContext);
                }
                //...................
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
                return this.context;
            } 
        }
    }

 protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = this.determineContextClass(sc);
        if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
    }


 protected Class<?> determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter("contextClass");
        if(contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
        } else {
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } 
        }
    }


    static {
        try {
            ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        } catch (IOException var1) {
            throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage());
        }

        currentContextPerThread = new ConcurrentHashMap(1);
    }
}

image.png

  • 爲了方便閱讀,這裏把一些不是核心的代碼過濾segmentfault

    1. 根據配置文件,首先咱們經過ContextLoaderListener對象來監聽ServletContext初始化,在初始化方法中會調用父類ContextLoader#initWebApplicationContext方法來進行
    2. initWebApplication中首先判斷是否存在Root Application Context,若是存在則拋出異常.以後經過#createWebApplicationContext方法來建立容器對象,並會將容器放入ServletContext中.因此對於ApplicationContextServletContext的區別就是ApplicationContext其實就是ServletContext中的一個屬性值而已.這個屬性中存有程序運行的全部上下文信息,因爲這個ApplicationContext是全局的應用上下文,因此在Spring中稱它爲"Root Application Context".
    3. 接下來咱們具體看一下容器是如何建立的:咱們進入到#createWebApplicationContext方法中能夠看到它是經過實例化容器的class類來建立容器的,而在#determineContextClass方法中首先經過初始化參數來獲取全路徑類名,若不存在則經過配置類#defaultStrategies來獲取容器名稱.
    4. 咱們能夠從當前類的靜態代碼找到此配置類,它經過讀取ContextLoader.properties配置文件來獲取當前容器全路徑類名稱

2. Servlet Application Contextapp

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
    public final void init() throws ServletException {
        //遍歷獲取servletConfig的全部參數
        PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        //初始化servlet applicationContext
        this.initServletBean();
    }
}

--------------------------------------------------------------------------------------------------------------------------------------------------------

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
    public FrameworkServlet() {
        this.contextClass = DEFAULT_CONTEXT_CLASS;
        this.contextInitializers = new ArrayList();
        ...
    }

    protected final void initServletBean() throws ServletException {
        //...................
        try {
            this.webApplicationContext = this.initWebApplicationContext();
        } 
    }

    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;

        if(wac == null) {
            wac = this.createWebApplicationContext(rootContext);
        }

        if(this.publishContext) {
            String attrName = this.getServletContextAttributeName();
            this.getServletContext().setAttribute(attrName, wac);
            if(this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }

    protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
        return this.createWebApplicationContext((ApplicationContext)parent);
    }

    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        Class<?> contextClass = this.getContextClass();
       
        if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            wac.setEnvironment(this.getEnvironment());
            wac.setParent(parent);
            wac.setConfigLocation(this.getContextConfigLocation());
            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }
    
    protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
        return this.createWebApplicationContext((ApplicationContext)parent);
    }
  
    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        Class<?> contextClass = this.getContextClass();
        if(this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet with name '" + this.getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "', using parent context [" + parent + "]");
        }
        if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
        } else {
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            wac.setEnvironment(this.getEnvironment());
            wac.setParent(parent);
            wac.setConfigLocation(this.getContextConfigLocation());
            this.configureAndRefreshWebApplicationContext(wac);
            return wac;
        }
    }
}
  • 接下來咱們再看DispatcherServletui

    1. 做爲Servlet,根據規範它的配置信息應該是在#init方法中完成,咱們首先進入到DispatcherServlet#init,其方法是繼承自父類HttpServletBean中.在父類的#init方法中,首先經過遍歷獲取ServletConfig的全部參數,而後進行Servlet Application Context的初始化
    2. 容器初始化方法#initServletBean位於父類FrameworkServlet中,在#initServletBean方法中調用#initWebApplicationContext方法
    3. initWebApplicationContext方法中首先經過ServletContext獲取Root Application Context,而後經過#createWebApplicationContext開始初始化Servlet Application Context,其中獲取的#createWebApplicationContext中獲取的class對象在構造方法中進行初始化的.在建立容器的過程會傳入Root Application Context做爲它的Parent,也就是在這裏二者創建父子關係,造成以前所說的繼承關係,最後一樣將新建立的容器放入ContextServlet中.

QQ截图20191101143626.png

3. 總結

因此在Spring中存在兩類context,一類Root Application Context,一類是Servlet Application Context,前者根容器是惟一的,是其餘全部Servlet應用容器的父類.
以上就是關於在Spring中容器的大體分析,咱們會在項目啓動時將不一樣的組件實例注入到Spring容器中,從而實現Spring IOC機制.this

  1. 若想要了解如何經過Spring註解方式自定義DispatcherServlet相關內容能夠參考:Spring中關於的WebApplicationInitializer及其實現的分析
  2. 若想要了解關於SpringMVC自定義配置化相關知識能夠參考:
相關文章
相關標籤/搜索