BeanDefinition資源定位

Spring第一步,資源來開路。java

Spring資源的加載邏輯比較複雜,咱們以相對簡單的FileSystemXmlApplicationContext爲例來說解BeanDefinition的定位過程。ide

後續的文章中,將更進一步的帶領你們逐步深刻地瞭解Spring的的運行流程函數

FileSystemApplicationContext

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

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資源的定位、載入、註冊關係不大,不在此處分析,後續文章中進行分析
複製代碼

prepareRefresh

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

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);
}
複製代碼

PathMatchingResourcePatternResolver的解析

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)};
        }
    }
}
複製代碼

DefaultResourceLoader.getResource

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);
        }
    }
}
複製代碼

FileSystemXmlApplicationContext.getResourceByPath

//能夠看出,把資源看成一個FileSystemResource返回,至此,咱們就找到了真正的資源位置,完成了資源的定位
protected Resource getResourceByPath(String path) {
    if (path != null && path.startsWith("/")) {
        path = path.substring(1);
    }
    return new FileSystemResource(path);
}
複製代碼

總結與回顧

咱們能夠發現Spring中對於資源的定位是比較複雜的,我大體梳理一下,大體邏輯以下:debug

  1. 使用PathMatchingResourcePatternResolver來解析Ant表達式路徑,成功則返回,失敗則向下
    1. 若是是classpath* 開頭的資源 ,
      1. 符合Ant規則的按照Ant路徑解析
      2. 不符合Ant規則的,解析成ClasspathResource
    2. 不是classpath*開頭的資源
      1. 若是 :後面的路徑符合Ant規則,按照Ant路徑解析
      2. :後的路徑不符合Ant規則,調用傳入的ResouceLoader來解析(AbstractApplicaitonContext把這份工做交由DefaultResourceLoader來執行)
  2. 使用DefaultResouceLoader加載資源
    1. 若是資源以 classpath: 開頭,返回 ClassPathResource
    2. 不是 classpath: 開頭
      1. 按照Url解析不出錯,返回UrlResource
      2. 解析Url出錯了,調用getResourceByPath來解析(這個方法被FileSystemXmlApplicationContext重寫了)

以上就是FileSystemXmlApplicationContext定位資源的基本流程。code

相關文章
相關標籤/搜索