Spring IOC 容器啟動分為兩大階段:容器啟動階段(載入/解析配置、註冊 BeanDefinition)與 Bean 實例化階段(建立實例、依賴注入、生命週期管理)。本文聚焦啟動階段的第一步——Resource 定位,追蹤從 ApplicationContext 建構到 ResourceLoader 解析配置路徑的完整呼叫鏈。
Spring 以 org.springframework.core.io.Resource 介面統一抽象各類資源(檔案、Classpath、URL、InputStream 等),主要實作:
ClassPathResource:類路徑資源(classpath:xxx.xml)FileSystemResource:檔案系統絕對/相對路徑UrlResource:HTTP、FTP、JAR 等 URL 協定ServletContextResource:Web 應用根目錄下資源InputStreamResource、ByteArrayResource 等public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) {
super(parent);
setConfigLocations(configLocations); // 保存配置路徑陣列
if (refresh) {
refresh(); // 啟動容器初始化核心入口
}
}
AbstractApplicationContext.refresh() 是 IOC 容器初始化的總開關。
protected final void refreshBeanFactory() {
// ... 銷毀舊 BeanFactory ...
DefaultListableBeanFactory beanFactory = createBeanFactory(); // 建立預設 BeanFactory
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory); // 關鍵:載入 BeanDefinition
this.beanFactory = beanFactory;
}
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.setResourceLoader(this); // 關鍵:注入 ResourceLoader(即 ApplicationContext 本身)
reader.setEntityResolver(new ResourceEntityResolver(this));
loadBeanDefinitions(reader); // 模板方法,由子類實作
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) {
Resource[] configResources = getConfigResources(); // 直接取得 Resource 陣列
if (configResources != null) reader.loadBeanDefinitions(configResources);
String[] configLocations = getConfigLocations(); // 取得路徑字串陣列
if (configLocations != null) reader.loadBeanDefinitions(configLocations);
}
XmlBeanDefinitionReader 繼承 AbstractBeanDefinitionReader,持有 ResourceLoader。載入路徑字串時委派給 ResourceLoader.getResource(String location):
public Resource getResource(String location) {
if (location.startsWith("/")) {
return getResourceByPath(location); // 絕對路徑 → 子類實作
} else if (location.startsWith(CLASSPATH_URL_PREFIX)) { // "classpath:"
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
} else {
try {
URL url = new URL(location); // 嘗試解析為 URL
return new UrlResource(url);
} catch (MalformedURLException ex) {
return getResourceByPath(location); // 退回相對路徑處理
}
}
}
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1); // 移除前導斜線
}
return new FileSystemResource(path); // 解析為檔案系統資源
}
因此 FileSystemXmlApplicationContext 專門支援檔案系統絕對/相對路徑(非 classpath)。
若 ResourceLoader 實作 ResourcePatternResolver,getResources(String locationPattern) 可解析 classpath*:/META-INF/*-context.xml 等通配符模式,掃描多路徑。
new FileSystemXmlApplicationContext("beans.xml")refresh() → refreshBeanFactory()createBeanFactory() → DefaultListableBeanFactoryloadBeanDefinitions(beanFactory) → XmlBeanDefinitionReaderreader.loadBeanDefinitions(configLocations)resourceLoader.getResource(location)(委派 DefaultResourceLoader)getResourceByPath() → new FileSystemResource(path)reader.loadBeanDefinitions(resource) → SAX 解析 XML → 註冊 BeanDefinitionAbstractApplicationContext.refresh() 定義骨架,loadBeanDefinitions() 由子類實作。ResourceLoader/ResourcePatternResolver 封裝不同資源解析策略。DefaultListableBeanFactory 為 BeanDefinition 註冊表與 Bean 建立工廠。ApplicationContext 時,覆寫 getResourceByPath() 即可支援新資源類型。classpath:、file:、http: 前綴對應的 Resource 實作,避免路徑解析錯誤。refresh() 可多次呼叫(重新載入),但需注意同步與銷毀舊 Bean。