SpringIOC分析(Xml配置)01

Web IOC容器

從大家熟悉的DispatcherServlet開始,最先執行的是init()方法。但是經過探索,往上追索在其父類HttpServletBean找到init()方法

/** * Map config parameters onto bean properties of this servlet, and * invoke subclass initialization。 * @throws ServletException if bean properties are invalid (or required * properties are missing), or if subclass initialization fails。 */@Overridepublic final void init() throws ServletException { if (logger。isDebugEnabled()) { logger。debug(“Initializing servlet ‘” + getServletName() + “’”); } // Set bean properties from init parameters。 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this。requiredProperties); if (!pvs。isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory。forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw。registerCustomEditor(Resource。class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw。setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger。isErrorEnabled()) { logger。error(“Failed to set bean properties on servlet ‘” + getServletName() + “’”, ex); } throw ex; } } // Let subclasses do whatever initialization they like。 initServletBean(); if (logger。isDebugEnabled()) { logger。debug(“Servlet ‘” + getServletName() + “’ configured successfully”); }}

在init()方法中,真正完成初始化容器的邏輯在initServletBean()方法中,找到程式碼在FrameworkServlet類中:

/** * Overridden method of {@link HttpServletBean}, invoked after any bean properties * have been set。 Creates this servlet‘s WebApplicationContext。 */@Overrideprotected final void initServletBean() throws ServletException { getServletContext()。log(“Initializing Spring FrameworkServlet ’” + getServletName() + “‘”); if (logger。isInfoEnabled()) { logger。info(“FrameworkServlet ’” + getServletName() + “‘: initialization started”); } long startTime = System。currentTimeMillis(); try { this。webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException | RuntimeException ex) { logger。error(“Context initialization failed”, ex); throw ex; } if (logger。isInfoEnabled()) { long elapsedTime = System。currentTimeMillis() - startTime; logger。info(“FrameworkServlet ’” + getServletName() + “‘: initialization completed in ” + elapsedTime + “ ms”); }}

終於看到了initWebApplicationContext()方法:

/** * Initialize and publish the WebApplicationContext for this servlet。 *

Delegates to {@link #createWebApplicationContext} for actual creation * of the context。 Can be overridden in subclasses。 * @return the WebApplicationContext instance * @see #FrameworkServlet(WebApplicationContext) * @see #setContextClass * @see #setContextConfigLocation */protected WebApplicationContext initWebApplicationContext() { // 先從ServletContext中獲得父容器WebAppliationContext WebApplicationContext rootContext = WebApplicationContextUtils。getWebApplicationContext(getServletContext()); // 宣告子容器 WebApplicationContext wac = null; // 建立父、子容器之間的關聯關係 if (this。webApplicationContext != null) { // A context instance was injected at construction time -> use it wac = this。webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; 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 -> set // the root application context (if any; may be null) as the parent cwac。setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } // 先去ServletContext中查詢Web容器的引用是否存在,並建立好預設的空IOC容器 if (wac == null) { // No context instance was injected at construction time -> see if one // has been registered in the servlet context。 If one exists, it is assumed // that the parent context (if any) has already been set and that the // user has performed any initialization such as setting the context id wac = findWebApplicationContext(); } // 給上一步建立好的IOC容器賦值 if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); } // 觸發onRefresh方法 if (!this。refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here。 synchronized (this。onRefreshMonitor) { onRefresh(wac); } } if (this。publishContext) { // Publish the context as a servlet context attribute。 String attrName = getServletContextAttributeName(); getServletContext()。setAttribute(attrName, wac); if (this。logger。isDebugEnabled()) { this。logger。debug(“Published WebApplicationContext of servlet ’” + getServletName() + “‘ as ServletContext attribute with name [” + attrName + “]”); } } return wac;}/** * Retrieve a {@code WebApplicationContext} from the {@code ServletContext} * attribute with the {@link #setContextAttribute configured name}。 The * {@code WebApplicationContext} must have already been loaded and stored in the * {@code ServletContext} before this servlet gets initialized (or invoked)。 *

Subclasses may override this method to provide a different * {@code WebApplicationContext} retrieval strategy。 * @return the WebApplicationContext for this servlet, or {@code null} if not found * @see #getContextAttribute() */@Nullableprotected WebApplicationContext findWebApplicationContext() { String attrName = getContextAttribute(); if (attrName == null) { return null; } WebApplicationContext wac = WebApplicationContextUtils。getWebApplicationContext(getServletContext(), attrName); if (wac == null) { throw new IllegalStateException(“No WebApplicationContext found: initializer not registered?”); } return wac;}/** * Instantiate the WebApplicationContext for this servlet, either a default * {@link org。springframework。web。context。support。XmlWebApplicationContext} * or a {@link #setContextClass custom context class}, if set。 *

This implementation expects custom contexts to implement the * {@link org。springframework。web。context。ConfigurableWebApplicationContext} * interface。 Can be overridden in subclasses。 *

Do not forget to register this servlet instance as application listener on the * created context (for triggering its {@link #onRefresh callback}, and to call * {@link org。springframework。context。ConfigurableApplicationContext#refresh()} * before returning the context instance。 * @param parent the parent ApplicationContext to use, or {@code null} if none * @return the WebApplicationContext for this servlet * @see org。springframework。web。context。support。XmlWebApplicationContext */protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { Class<?> contextClass = getContextClass(); if (this。logger。isDebugEnabled()) { this。logger。debug(“Servlet with name ’” + 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 ’” + getServletName() + “‘: custom WebApplicationContext class [” + contextClass。getName() + “] is not of type ConfigurableWebApplicationContext”); } ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils。instantiateClass(contextClass); wac。setEnvironment(getEnvironment()); wac。setParent(parent); String configLocation = getContextConfigLocation(); if (configLocation != null) { wac。setConfigLocation(configLocation); } configureAndRefreshWebApplicationContext(wac); return wac;}protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { 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 if (this。contextId != null) { wac。setId(this。contextId); } else { // Generate default id。。。 wac。setId(ConfigurableWebApplicationContext。APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils。getDisplayString(getServletContext()。getContextPath()) + ’/‘ + getServletName()); } } wac。setServletContext(getServletContext()); wac。setServletConfig(getServletConfig()); wac。setNamespace(getNamespace()); wac。addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); // 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(getServletContext(), getServletConfig()); } postProcessWebApplicationContext(wac); applyInitializers(wac); wac。refresh();}

從上面的程式碼中可以看出,在configAndRefreshWebApplicationContext0方法中,呼叫refresh()方法,這個是真正啟動IOC容器的入口,後面會詳細介紹。IOC容器初始化以後,最後呼叫了DispatcherServlet的 onRefresh()方法,在onRefresh()方法中又是直接呼叫initStrategies()方法初始化SpringMVC的九大元件:

/** * This implementation calls {@link #initStrategies}。 */@Overrideprotected void onRefresh(ApplicationContext context) { initStrategies(context);}/** * Initialize the strategy objects that this servlet uses。 *

May be overridden in subclasses in order to initialize further strategy objects。 */protected void initStrategies(ApplicationContext context) { // 多檔案上傳元件 initMultipartResolver(context); // 初始化本地語言環境 initLocaleResolver(context); // 初始化模板處理器 initThemeResolver(context); // handlerMappeing initHandlerMappings(context); // 初始哈引數介面卡 initHandlerAdapters(context); // 初始化異常攔截器 initHandlerExceptionResolvers(context); // 初始化檢視預處理器 initRequestToViewNameTranslator(context); // 初始化檢視轉換器 initViewResolvers(context); initFlashMapManager(context);}

基於XML的IOC容器初始化

IOC容器的初始化包括BeanDefinition的Resource定位、載入和註冊這三個基本的過程。我們以ApplicationContext為例講解,ApplicationContext 系列容器也許是我們最熟悉的,因為Web專案中使用的XmlWebApplicationContext 就屬於這個繼承體系還有ClasspathXmlApplicationContext等,其繼承體系如下圖所示:

SpringIOC分析(Xml配置)01

ApplicationContext 允許上下文巢狀,透過保持父上下文可以維持一個上下文體系。對於Bean的查詢可以在這個上下文體系中發生,首先檢查當前上下文,其次是父上下文,逐級向上,這樣為不同的Spring應用提供了一個共享的Bean定義環境。

1。尋找入口

還有一個我們用的比較多的ClassPathXmlApplicationContext,透過main()方法啟動:

ApplicationContext app = new ClassPathxmlApplicationContext(“application。xml”);

先看其建構函式的呼叫:

public ClassPathxmlApplicationContext(String configlocation)throws BeansException{ this(new String[]{configlocation}, true, (ApplicationContext) nul1); }

其實際呼叫的建構函式為:

public ClassPathxmlApplicationContext(String[] configlocations,boolean refresh,@Nullable ApplicationContext parent) throws BeansException { super(parent); this。setconfigLocations(configlocations); if (refresh) { this。refresh(); }}

還有像AnnotationConfigApplicationContext、FileSystemXmlApplicationContext、 XmlWebApplicationContext等都繼承自父容器AbstractApplicationContext主要用到了裝飾器模式 和策略模式,最終都是呼叫refresh()方法。

2。獲得配置路徑

透過分析ClassPathXmlApplicationContext的原始碼可以知道,在建立ClassPathXmlApplicationContext 容器時,構造方法做以下兩項重要工作:

首先,呼叫父類容器的構造方法(super(parent)方法)為容器設定好Bean 資源載入器。然後,再呼叫父類AbstractRefreshableConfigApplicationContext 的setConfigLocations(configLocations)方法設定Bean配置資訊的定位路徑。 透過追蹤ClassPathXmlApplicationContext的繼承體系,發現其父類的父類AbstractApplicationContext中初始化IOC容器所做的主要原始碼如下:

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { static { // Eagerly load the ContextClosedEvent class to avoid weird classloader issues // on application shutdown in WebLogic 8。1。 (Reported by Dustin Woods。) // 為了避免應用程式在Weblogic8。1關閉是出現類載入異常載入問題,載入IOC容器關閉事件(ContextClosedEvent)類 ContextClosedEvent。class。getName(); } /** * Create a new AbstractApplicationContext with no parent。 */ public AbstractApplicationContext() { this。resourcePatternResolver = getResourcePatternResolver(); } /** * Create a new AbstractApplicationContext with the given parent context。 * @param parent the parent context */ public AbstractApplicationContext(@Nullable ApplicationContext parent) { this(); setParent(parent); } // 獲取一個Spring Source的載入器用於讀入SpringBean配置資訊 // AbstractApplicationContext預設構造方法中有呼叫 PathMatchingResourcePatternResolver 構造方法 // 建立Spring資源載入器 protected ResourcePatternResolver getResourcePatternResolver() { return new PathMatchingResourcePatternResolver(this); }}

在設定容器的資源載入器之後,接下來ClassPathXmlApplicationContext 執行 setConfigLocations()方法透過呼叫其父類AbstractRefreshableConfigApplicationContext的方法進行對Bean配置資訊的定位,該方法的原始碼如下:

/** * Set the config locations for this application context in init-param style, * i。e。 with distinct locations separated by commas, semicolons or whitespace。 *

If not set, the implementation may use a default as appropriate。 */// 處理單個資原始檔路徑為一個字串情況public void setConfigLocation(String location) { // 多個資原始檔路徑之間可以用 “,; \t\n” 分割 setConfigLocations(StringUtils。tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));}/** * 儲存需要載入的本地資源 例如:application。xml * Set the config locations for this application context。 *

If not set, the implementation may use a default as appropriate。 */public void setConfigLocations(@Nullable String。。。 locations) { if (locations != null) { Assert。noNullElements(locations, “Config locations must not be null”); this。configLocations = new String[locations。length]; for (int i = 0; i < locations。length; i++) { // resolvePath 為同一個類中將字串解析為路徑的方法 this。configLocations[i] = resolvePath(locations[i])。trim(); } } else { this。configLocations = null; }}

透過這兩個方法的原始碼我們可以看出,我們既可以使用一個字串來配置多個Spring Bean配置資訊,也可以使用字串陣列,即下面兩種方式都是可以的:

ClassPathResource res=new ClassPathResource(“a。xml,b。xml”);

多個資原始檔路徑之間可以是用“,;\t\r”等分隔。

ClassPathResource res =new ClassPathResource(new String[]{“a。xml”,“b。xml”});

至此,SpringlOC容器在初始化時將配置的Bean 配置資訊定位為Spring封裝的Resource。