什麼是IoC
控制反轉(Inversion of Control,縮寫為IoC),是一種設計模式,用來解耦元件之間的耦合度。
IoC容器系列的設計與實現
在Spring IoC容器的設計中,有兩種主要的容器系列:BeanFactory與ApplicationContext。
BeanFactory,IoC容器的介面定義,該系列容器提供的是最基本的IoC容器的功能。ApplicationContext,容器的高階形態,它通過繼承 MessageSource,ResourceLoader,ApplicationEventPublisher介面,在簡單容器的基礎上,增加了許多對高階容器的支援。
在Spring提供的最基本的IoC容器的介面定義和實現的基礎上,Spring通過定義BeanDefinition來管理基於Spring的應用中的各種物件以及他們直接的相互依賴關係。BeanDefinition 抽象了我們對 Bean的定義,是讓容器起作用的主要資料型別。對 IOC 容器來說,BeanDefinition 就是對依賴反轉模式中管理的物件依賴關係的資料抽象。也是容器實現依賴反轉功能的核心資料結構。
BeanFactory介面定義的方法:
BeanFactory容器的設計原理
BeanFactory介面提供了使用IoC容器的規範,我們以XmlBeanFactory的實現為例來說明簡單IoC容器的設計原理。XmlBeanFactory類繼承關係圖:
XmlBeanFactory繼承自DefaultListableBeanFactory類,DefaultListableBeanFactory包含了基本IoC容器所具有的重要功能,XmlBeanFactory在繼承了DefaultListableBeanFactory容器的功能的同時,增加了新的功能,它是一個可以讀取以XML檔案定義的BeanDefinition的IoC容器,來看下具體實現:
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
複製程式碼
在XmlBeanFactory中,初始化了一個XmlBeanDefinitionReader物件,該物件用於對XML檔案定義資訊的處理。XmlBeanFactory建構函式需要傳入一個Resource引數,該引數封裝了BeanDefinition的來源資訊。在構造方法中,XmlBeanDefinitionReader會呼叫loadBeanDefinitions方法來完成BeanDefinition的載入。
ApplicationContext容器的設計原理
我們以常用的FileSystemXmlApplicationContext為例,debug原始碼來看下ApplicationContext容器的設計原理。
測試程式碼如下:
@Test
public void testApplicationContext() {
ApplicationContext context = new FileSystemXmlApplicationContext("src/main/resources/bean.xml");
context.getBean("helloWorld",HelloWorld.class).sayHello();
}
複製程式碼
進入 FileSystemXmlApplicationContext 的構造方法:
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
複製程式碼
該方法呼叫過載方法,傳入三個引數:陣列型別的configLocation,預設屬性為true,父容器為null:
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
複製程式碼
該方法首先會呼叫父類的構造方法,引數為null,在跟進去之前,我們先看下FileSystemXmlApplicationContext類的繼承結構:
super(parent)方法會一直往上呼叫其父類構造方法,直到AbstractApplicationContext:
public AbstractApplicationContext(ApplicationContext parent) {
this();
setParent(parent);
}
複製程式碼
this方法呼叫預設建構函式,為屬性resourcePatternResolver賦值:
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
複製程式碼
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
複製程式碼
回到setConfigLocations(configLocations)方法,看下該方法的具體實現:
public void setConfigLocations(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++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
複製程式碼
先畫出方法時序圖,跟著圖一步步跟下去:
該方法會呼叫resolvePath方法,解析路徑,跟進去看下:
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
複製程式碼
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
複製程式碼
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
複製程式碼
getEnvironment方法返回的是StandardEnvironment物件,該物件是標準環境,會自動註冊System.getProperties() 和 System.getenv()到環境。
接下來,回到resolvePath方法,該方法會呼叫AbstractEnvironment類的resolveRequiredPlaceholders方法:
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
複製程式碼
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
複製程式碼
此時propertyResolver屬性已經有值了,緊接著會呼叫propertyResolver屬性的resolveRequiredPlaceholders方法,我們先看下ConfigurablePropertyResolver繼承結構:
進入resolveRequiredPlaceholders方法看下,該方法在AbstractPropertyResolver中:
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}
複製程式碼
該方法做了兩件事:1.呼叫createPlaceholderHelper方法為屬性strictHelper賦值;2.呼叫doResolvePlaceholders方法,該方法是具體解析方法。
其中屬性strictHelper,型別為PropertyPlaceholderHelper,持有要解析的字首字尾,我們看下該類屬性
繼續跟進,會呼叫doResolvePlaceholders方法:
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
@Override
public String resolvePlaceholder(String placeholderName) {
return getPropertyAsRawString(placeholderName);
}
});
}
複製程式碼
具體不在深入,講了半天,我們來總結下setConfigLocations方法幹了什麼事情:解析FileSystemXmlApplicationContext建構函式中引數存在的佔位符,並替換為真實值。如:
@Test
public void testApplicationContext() {
Properties properties = System.getProperties();
properties.setProperty("aaaa", "bbbb");
properties.setProperty("bbbb", "src/main/resources/bean.xml");
ApplicationContext context = new FileSystemXmlApplicationContext("${${aaaa}}");
context.getBean("helloWorld",HelloWorld.class).sayHello();
}
複製程式碼
setConfigLocations方法會將${${aaaa}}
,遞迴解析為bbbb,然後查詢對應的系統屬性存在aaaa為key的value bbbb,然後再根據bbbb查詢系統屬性,替換為applicationContext.xml。
如果讀完覺得有收穫的話,歡迎點贊、關注、加公眾號【Java線上】,查閱更多精彩歷史!!!