Spring
最近做IT測試時,每次Spring 加載配置等都很慢,抽空把Spring 容器的啓動整理一下。
容器
Spring中聽的最多的就是依賴注入、控制反轉。一般情況下,A對象使用B對象,都是A先創建B,然後調用B的方法。這樣兩個對象間有依賴的。控制反轉簡單來說就是對象-對象的依賴關係變成對象-容器-對象。這樣每次A只需要調用傳給它的B即可。所以容器的管理對象的作用就顯而易見了。聯繫它的功能,容器其實就是創建、維護與管理對象的工廠類,只不過這個類的附加功能比較強大而已。
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自動完成依賴注入。
待續