Spring第一步,資源來開路。java
Spring資源的加載邏輯比較複雜,咱們以相對簡單的FileSystemXmlApplicationContext爲例來說解BeanDefinition的定位過程。ide
後續的文章中,將更進一步的帶領你們逐步深刻地瞭解Spring的的運行流程函數
FileSystemXmlApplicationContext 用於從文件系統中加載指定的Xml文件,來以此做爲Spring資源,下面是構造函數ui
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
//初始化基類,主要是AbstractApplicationContext的初始化
super(parent);
//設置資源(xml文件)
setConfigLocations(configLocations);
if (refresh) {
//調用AbstractApplicationContext的refresh方法,進行容器的刷新
refresh();
}
}
複製代碼
refresh是Spring容器的核心方法,咱們此文中僅僅探討前兩項內容。this
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//準備刷新容器,通知子類刷新容器
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//獲取BeanFactory,實際是獲取子類配置的BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
.....
//下面代碼與BeanDefinition資源的定位、載入、註冊關係不大,不在此處分析,後續文章中進行分析
複製代碼
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
//獲取激活鎖,設置激活狀態
synchronized (this.activeMonitor) {
this.active = true;
}
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// Initialize any placeholder property sources in the context environment
//初始化屬性源,交由子類配置(FileSystemXmlApplicationContext沒有重寫此方法
initPropertySources();
// Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
//驗證全部標記爲必須的屬性,此處沒有進行任何須須的配置,因此驗證經過
getEnvironment().validateRequiredProperties();
}
複製代碼
obtainFreshBeanFactory實際是調用了子類AbstractRefreshableApplicationContext的實現,url
@Override
protected final void refreshBeanFactory() throws BeansException {
//若是以前有BeanFactory了,就銷燬從新構建一個
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//建立一個BeanFactory,默認實現是DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
//設置id
beanFactory.setSerializationId(getId());
//1.設置一些基本屬性 allowBeanDefinitionOverriding,allowCircularReferences
// 是否容許beanDefinition重載,容許循環引用
//2.設置一個自動注入候選者判斷器QualifierAnnotationAutowireCandidateResolver
// 專用於@Querifiler @Value的條件判斷
customizeBeanFactory(beanFactory);
//定位、加載、註冊beanDefinitiion,交由子類實現,由於不一樣的業務場景下,資源的未知是不一樣的,因此父類不能肯定具體的資源加載形式,因此交由子類實現,對於xml來講是交由子類AbstractXmlApplicationContext實現,
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
//這裏是子類AbstractXmlApplicationContext實現
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
//建立一個XmlBeanDefinitionReader,並初始化
//向XmlBeanDefinitionReader
//設置一個BeanDefinitionRegistry
//設置一個ResourceLoader
//由於DefaultListableBeanFactory不是一個ResoueceLoader,因此這裏用了默認值PathMatchingResourcePatternResolver
//設置環境,用的默認值StandardEnvironment
//可是不要慌,下面的代碼中,就會使用FileSystemXmlApplicationContext來替換這兩個值
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
//使用FileSystemXmlApplicationContext中的環境替換
beanDefinitionReader.setEnvironment(this.getEnvironment());
//使用FileSystemXmlApplicationContext來做爲資源加載器
beanDefinitionReader.setResourceLoader(this);
//設置一個實體解析器,用於解析XML的頭Schema
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 {
//咱們使用的是String配置的資源,不會走這個加載
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//今後處進入
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//使用XmlBeanDefinitionReader定位、加載、註冊指定的configLocations
reader.loadBeanDefinitions(configLocations);
}
}
//這裏傳入的String[]類型,因此調用的是XmlBeanDefinitionReader的父類AbstractBeanDefinitionReader的方法
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
//加載的BeanDefinition的個數統計
int counter = 0;
//迭代,加載全部的location
for (String location : locations) {
//加載並統計數量
counter += loadBeanDefinitions(location);
}
return counter;
}
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
//加載流程的具體實現
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//獲取ResoruceLoader,實際就是上文中傳入的FileSystemXmlApplicaitonContext
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//FileSystemXmlApplicaitonContext實現了這個ResourcePatternResolver接口
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//這行很重要,這裏就是資源定位和加載的核心代碼,這裏是利用FileSystemXmlApplicaitonContext來進行資源的定位和加載,具體分析見下文的資源定位
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//資源的加載和BeanDefintiion的註冊
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
複製代碼
下面是Spring真正加載資源的邏輯spa
//FileSystemXmlApplicationContext自己並無進行資源的加載,而是調用了基類AbstractApplicaiotnContext資源加載的方法,注意此處的方法名是 getResources ,
//內部實際是調用本身內部的resourcePatternResolver,這個resourcePatternResolver是在AbstractApplicationContext實例化是被建立的,是一個PathMatchingResourcePatternResolver
//因此這裏資源的加載是先交給PathMatchingResourcePatternResolver來解析
public Resource[] getResources(String locationPattern) throws IOException {
return this.resourcePatternResolver.getResources(locationPattern);
}
複製代碼
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
//若是以 classpath*: 開頭
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
//若是是Ant表達式,則進入此
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
//不然在classpath中尋找資源
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
//若是不以classpath*:開頭
else {
//查看 : 後面的路徑
int prefixEnd = locationPattern.indexOf(":") + 1;
//若是:後的路徑符合ant表達式
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
//最後 : 後的表達式不是ant表達式的話,就調用本身的ResourceLoader進行資源的加載
//注意 PathMatchingResourcePatternResolver的構造函數中,已經把AbstractApplicationCotexnt做爲了本身的資源加載器,因此此處調用的方法就是AbstractApplicationContext的getResource,注意這個方法的名稱,是getResource,不是getResources
//由於AbstractApplicationContext繼承了DefaultResourceLoader,因此此處調用的getResource,實際調用的DafaultResourceLoader的getResource方法,
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
複製代碼
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//若是 location 以 classpath: 開頭,就返回一個ClassPathResouce
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
//不以classpath: 開頭的話,就嘗試使用url來獲取資源,若是不拋出異常,就返回一個UrlResource資源
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
//異常出現,說明url不能正確解析,只好調用 getResourceByPath 來加載資源
//注意 DefaultResourceLoader中已有getResourceByPath的實現,就是把location看成一個ClassPathContextResource來解析,可是在此處並非,由於FileSystemXmlApplicationContext重寫了這個方法,因此getResourceByPath實際是調用的FileSystemXmlApplicationContext中的實現,
return getResourceByPath(location);
}
}
}
複製代碼
//能夠看出,把資源看成一個FileSystemResource返回,至此,咱們就找到了真正的資源位置,完成了資源的定位
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
複製代碼
咱們能夠發現Spring中對於資源的定位是比較複雜的,我大體梳理一下,大體邏輯以下:debug
- 使用PathMatchingResourcePatternResolver來解析Ant表達式路徑,成功則返回,失敗則向下
- 若是是classpath* 開頭的資源 ,
- 符合Ant規則的按照Ant路徑解析
- 不符合Ant規則的,解析成ClasspathResource
- 不是classpath*開頭的資源
- 若是 :後面的路徑符合Ant規則,按照Ant路徑解析
- :後的路徑不符合Ant規則,調用傳入的ResouceLoader來解析(AbstractApplicaitonContext把這份工做交由DefaultResourceLoader來執行)
- 使用DefaultResouceLoader加載資源
- 若是資源以 classpath: 開頭,返回 ClassPathResource
- 不是 classpath: 開頭
- 按照Url解析不出錯,返回UrlResource
- 解析Url出錯了,調用getResourceByPath來解析(這個方法被FileSystemXmlApplicationContext重寫了)
以上就是FileSystemXmlApplicationContext定位資源的基本流程。code