摘要:后續的文章中,將更進一步的帶領大家逐步深入地了解的的運行流程用于從文件系統中加載指定的文件,來以此作為資源,下面是構造函數初始化基類,主要是的初始化設置資源文件調用的方法,進行容器的刷新是容器的核心方法,我們此文中僅僅探討前兩項內容。
BeanDefinition資源定位
Spring第一步,資源來開路。
鏈接:https://juejin.im/post/5d2945...
Spring資源的加載邏輯比較復雜,我們以相對簡單的FileSystemXmlApplicationContext為例來講解BeanDefinition的定位過程。
后續的文章中,將更進一步的帶領大家逐步深入地了解Spring的的運行流程
FileSystemApplicationContextFileSystemXmlApplicationContext 用于從文件系統中加載指定的Xml文件,來以此作為Spring資源,下面是構造函數
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { //初始化基類,主要是AbstractApplicationContext的初始化 super(parent); //設置資源(xml文件) setConfigLocations(configLocations); if (refresh) { //調用AbstractApplicationContext的refresh方法,進行容器的刷新 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資源的定位、載入、注冊關系不大,不在此處分析,后續文章中進行分析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的實現,
@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資源定位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); }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中對于資源的定位是比較復雜的,我大致梳理一下,大致邏輯如下:
使用PathMatchingResourcePatternResolver來解析Ant表達式路徑,成功則返回,失敗則向下
如果是classpath* 開頭的資源 ,
符合Ant規則的按照Ant路徑解析
不符合Ant規則的,解析成ClasspathResource
不是classpath*開頭的資源
如果 :后面的路徑符合Ant規則,按照Ant路徑解析
:后的路徑不符合Ant規則,調用傳入的ResouceLoader來解析(AbstractApplicaitonContext把這份工作交由DefaultResourceLoader來執行)
使用DefaultResouceLoader加載資源
如果資源以 classpath: 開頭,返回 ClassPathResource
不是 classpath: 開頭
按照Url解析不出錯,返回UrlResource
解析Url出錯了,調用getResourceByPath來解析(這個方法被FileSystemXmlApplicationContext重寫了)
以上就是FileSystemXmlApplicationContext定位資源的基本流程。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75329.html
摘要:前言以下源碼基于版本解析。實現源碼分析對于的實現,總結來說就是定位加載和注冊。定位就是需要定位配置文件的位置,加載就是將配置文件加載進內存注冊就是通過解析配置文件注冊。下面我們從其中的一種使用的方式一步一步的分析的實現源碼。 前言 以下源碼基于Spring 5.0.2版本解析。 什么是IOC容器? 容器,顧名思義可以用來容納一切事物。我們平常所說的Spring IOC容器就是一個可以容...
摘要:使用別名時,容器首先將別名元素所定義的別名注冊到容器中。調用的方法向容器注冊解析的通過對對象的解析和封裝返回一個通過這個來注冊對象當調用向容器注冊解析的時,真正完成注冊功能的是。 文章參考來自:https://www.cnblogs.com/ITtan... 文章代碼來自 spring-boot 1.4.1 Release版本 Spring IoC容器對Bean定義資源的載入是從ref...
摘要:對于開發者來說,無疑是最常用也是最基礎的框架之一。概念上的東西還是要提一嘴的用容器來管理。和是容器的兩種表現形式。定義了簡單容器的基本功能。抽象出一個資源類來表示資源調用了忽略指定接口的自動裝配功能委托解析資源。 對于Java開發者來說,Spring無疑是最常用也是最基礎的框架之一。(此處省略1w字吹Spring)。相信很多同行跟我一樣,只是停留在會用的階段,比如用@Component...
摘要:從層層委托的依賴關系可以看出,的依賴注入給屬性賦值是層層委托的最終給了內省機制,這是框架設計精妙處之一。當然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的若對技術內容感興趣可以加入群交流高工架構師群。 每篇一句 具備了技術深度,遇到問題可以快速定位并從根本上解決。有了技術深度之后,學習其它技術可以更快,再深入其它技術也就不會害怕 相關閱讀 【小家Spring】聊聊Spring中的...
摘要:這個讀取器可以讀取注解標注下的所有定義,并最終添加到的中。處理注解的配置類讀取每一個配置類中定義的,加入到容器中。 IOC的核心就是代碼入口就在AbstractApplictionContext public void refresh() throws BeansException, IllegalStateException { synchronized (t...
閱讀 3065·2023-04-25 18:54
閱讀 2590·2021-11-02 14:40
閱讀 3174·2021-09-23 11:58
閱讀 2424·2019-08-30 13:50
閱讀 1230·2019-08-29 12:46
閱讀 3117·2019-08-28 17:51
閱讀 678·2019-08-26 11:47
閱讀 897·2019-08-23 16:17