Spring容器初始化整理

Spring

最近做IT測試時,每次Spring 加載配置等都很慢,抽空把Spring 容器的啓動整理一下。

容器

Spring中聽的最多的就是依賴注入、控制反轉。一般情況下,A對象使用B對象,都是A先創建B,然後調用B的方法。這樣兩個對象間有依賴的。控制反轉簡單來說就是對象-對象的依賴關係變成對象-容器-對象。這樣每次A只需要調用傳給它的B即可。所以容器的管理對象的作用就顯而易見了。聯繫它的功能,容器其實就是創建、維護與管理對象的工廠類,只不過這個類的附加功能比較強大而已。

在這裏先說下兩個概念:BeanFactoryApplicationContext,簡單來說它倆都是Spring 提供的容器。不過稍有區別,

BeanFactory

BeanFactory 是容器的最基本的接口,所有的factory和context都繼承或實現它。提供getBean() 供獲取容器中受管理的Bean,這裏是通過Bean的名字獲取的。

實際上,在Spring 把 DefaultListableBeanFactory 作爲默認的容器來使用的,然後容器加載配置來創建Bean。

一般情況下,從Spring配置文件beans.xml 中加載Bean,這個主要是加載BeanDefinitions的過程。

看個代碼(我用的版本是5.0.4.RELEASE):

public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {
    // ...省略其他

    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;
        if (this.registry instanceof ResourceLoader) {
            this.resourceLoader = (ResourceLoader)this.registry;
        } else {
            this.resourceLoader = new PathMatchingResourcePatternResolver();
        }

        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable)this.registry).getEnvironment();
        } else {
            this.environment = new StandardEnvironment();
        }

    }
    // ...省略其他

    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        Assert.notNull(resources, "Resource array must not be null");
        int counter = 0;
        Resource[] var3 = resources;
        int var4 = resources.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            Resource resource = var3[var5];
            counter += this.loadBeanDefinitions((Resource)resource);
        }
    }
    // ...省略其他
}

簡單來說:
1. 創建容器配置文件的抽象資源,它包含BeanDefinition 的定義信息。
2. 創建一個BeanFactory,Spring默認使用 DefaultListableBeanFactory。
3. 創建一個BeanDefinition 的讀取器。
4. 讀取配置信息(loadBeanDefinitions函數),解析並註冊。

然後容器就可以使用了。


ApplicationContext

看到名字就知道它是上下文。實際上ApplicationContext 是一個高級點的容器。另外,因爲繼承了六個interface,因此它還有語言國際化、訪問資源、發佈應用事件等功能。下面看下類圖:



從圖中看,ApplicationContext 其實是BeanFactory的子接口。另外還繼承了其他功能接口。簡單來說,它主要提供資源的定位和加載。

容器初始化

在上邊圖中,從ApplicationContext 接口往下看是 ConfigurableApplicationContext。

public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {

    // ... 省略
    void refresh() throws BeansException, IllegalStateException;
    // ... 省略
}

其實,容器初始化就是由refresh() 函數來啓動的,這個方法標誌着容器的正式啓動。簡單來說,啓動分爲三個基本過程:BeanDefinition的Resource定位、載入和註冊。

現在配置一個beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="springHelloWorld" class="com.oocl.spring.SpringHello"></bean>
</beans>

新建一個bean,並給一個方法:

public class SpringHello {

    public void prints() {
        System.out.println("hello spring");
    }
}

有了bean的配置,一般測試時,我們會採用手動的方式初始化:

public class TestSpring {

    public static void main(String[] args) {
        TestSpring ts = new TestSpring();
        ts.startSpring();
    }

    private void startSpring() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        SpringHello bean = (SpringHello) context.getBean("springHelloWorld");
        bean.prints();
    }
}

運行上邊代碼,你會看到如下輸出:

Mar 29, 2018 10:31:48 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@442d9b6e: startup date [Thu Mar 29 22:31:48 CST 2018]; root of context hierarchy
Mar 29, 2018 10:31:49 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [beans.xml]
hello spring

從輸出看,先是執行了 ClassPathXmlApplicationContext 的 prepareRefresh() 方法。一路看代碼發現該方法定義在AbstractApplicationContext.java 中,整個方法設計上使用模板方法

	@Override
	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();// 若已存在BeanFactory,則銷燬,並新建。調用子類的refreshBeanFactory()。

			// 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);//反射調用BeanFactory的後處理器(Bean定義的)

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);// 註冊Bean的後處理器,在Bean創建過程中調用

				// Initialize message source for this context.
				initMessageSource();// 初始化上下文中的消息源

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();// 初始化上下文中的事件機制

				// Initialize other special beans in specific context subclasses.
				onRefresh();// 如有需要,初始化其他特殊Bean,

				// Check for listener beans and register them.
				registerListeners();// 檢查監聽Bean並且將這些Bean向容器註冊

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);// 實例化所有 non-lazy-init 的bean

				// Last step: publish corresponding event.
				finishRefresh();// 發佈容器事件,結束refresh 過程
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();// f防止Bean資源佔用,遇到異常,銷燬已經創建的 singletons 的bean

				// Reset 'active' flag.
				cancelRefresh(ex);// 重置 'active' 標誌

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();// 重置 spring core 中內置緩存
			}
		}
	}

在上邊方法中重置BeanFactory時,

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();// 在子類中實現
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (logger.isDebugEnabled()) {
			logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
		}
		return beanFactory;
	}

子類AbstractRefreshableApplicationContext中:

	@Override
	protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {// 如果已經有 BeanFactory,則銷燬、關閉
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();// 新建一個默認容器
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			loadBeanDefinitions(beanFactory);// 重頭戲,啓動對BeanDefinition的載入。具體由子類實現。又是模板方法!
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}
由於是模板方法,在上邊的類圖中繼續向下找,即 AbstractXmlApplicationContext.java
	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		// 創建XmlBeanDefinitionReader,並通過回調方式設置到Bean中去
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);// 爲reader 配置 ResourceLoader(因爲從圖上看該類本身繼承了DefaultResourceLoader類)
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}
	// ...

	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		// 以Resource 的方式獲取配置文件的資源位置
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		// 以String 的形式獲取配置文件的位置。
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}
此時就完成了第二個過程:載入。

綜合上述代碼,其實就new ClassPathXmlApplicationContext("beans.xml") 等效於將代碼拆分如下:

public class TestSpring {

    public static void main(String[] args) {
        TestSpring ts = new TestSpring();
        ts.startSpringByStep();
    }

    private void startSpringByStep() {
        ClassPathResource res = new ClassPathResource("beans.xml");
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        reader.loadBeanDefinitions(res);

        SpringHello bean = (SpringHello) factory.getBean("springHelloWorld");
        bean.prints();
    }
}

註冊

Spring中對接口BeanFactory的繼承結構,分兩部分:ApplicationContext*Factory. 下邊看下第一個繼承結構。


從上邊的類圖看有三個接口設計路線:

紅色:AbstractBeanFactory -> ConfigurableBeanFactory -> HierarchicalBeanFactory -> BeanFactory。提供一個具有別名管理、單例Bean創建與註冊、工廠方法等功能的容器。

綠色:AbstractAutowireCapableBeanFactory -> AutowireCapableBeanFactory -> BeanFactory。提供一個具有自動裝配功能的容器。

藍色:DefaultListableBeanFactory -> ConfigurableListableBeanFactory。提供一個集所有功能於一體的功能強大的容器。

同時,DefaultListableBeanFactory 依賴SimpleAutowireCandidateResolver.java 這樣就能對一些配置有autowiring屬性的Bean自動完成依賴注入。


待續







發佈了96 篇原創文章 · 獲贊 29 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章