Spring第一步,資源來開路。
連接: https://juejin.im/post/5d2945...
Spring資源的加載邏輯比較複雜,咱們以相對簡單的FileSystemXmlApplicationContext爲例來說解BeanDefinition的定位過程。java
後續的文章中,將更進一步的帶領你們逐步深刻地瞭解Spring的的運行流程ide
FileSystemXmlApplicationContext 用於從文件系統中加載指定的Xml文件,來以此做爲Spring資源,下面是構造函數
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { //初始化基類,主要是AbstractApplicationContext的初始化 super(parent); //設置資源(xml文件) setConfigLocations(configLocations); if (refresh) { //調用AbstractApplicationContext的refresh方法,進行容器的刷新 refresh(); } }
refresh是Spring容器的核心方法,咱們此文中僅僅探討前兩項內容。
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的實現,
@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中的校驗DTD文件,下篇文章中會使用到,這裏是一個伏筆 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真正加載資源的邏輯
//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中對於資源的定位是比較複雜的,我大體梳理一下,大體邏輯以下:函數
使用PathMatchingResourcePatternResolver來解析Ant表達式路徑,成功則返回,失敗則向下post
若是是classpath* 開頭的資源 ,ui
- 符合Ant規則的按照Ant路徑解析
- 不符合Ant規則的,解析成ClasspathResource
不是classpath*開頭的資源this
- 若是 :後面的路徑符合Ant規則,按照Ant路徑解析
- :後的路徑不符合Ant規則,調用傳入的ResouceLoader來解析(AbstractApplicaitonContext把這份工做交由DefaultResourceLoader來執行)
使用DefaultResouceLoader加載資源url
- 若是資源以 classpath: 開頭,返回 ClassPathResource
不是 classpath: 開頭spa
- 按照Url解析不出錯,返回UrlResource
- 解析Url出錯了,調用getResourceByPath來解析(這個方法被FileSystemXmlApplicationContext重寫了)
以上就是FileSystemXmlApplicationContext定位資源的基本流程。debug