1. 程式人生 > >spring beans原始碼解讀之--bean definiton解析器

spring beans原始碼解讀之--bean definiton解析器

spring提供了有兩種方式的bean definition解析器:PropertiesBeanDefinitionReader和XmLBeanDefinitionReader即屬性檔案格式的bean definition解析器和xml檔案格式的bean definition解析器。

我們先從簡單的PropertiesBeanDefinitionReader開始深入挖掘。

1. PropertiesBeanDefinitionReader 屬性檔案bean definition解析器

 1.1  作用:一種簡單的屬性檔案格式的bean definition解析器,提供以Map/Properties型別ResourceBundle型別定義的bean的註冊方法。例如:

 employee.(class)=MyClass       // bean is of class MyClass
 employee.(abstract)=true       // this bean can't be instantiated directly
 employee.group=Insurance       // real property
 employee.usesDialUp=false      // real property (potentially overridden)

 salesrep.(parent)=employee     // derives from "employee" bean definition
salesrep.(lazy-init)=true // lazily initialize this singleton bean salesrep.manager(ref)=tony // reference to another bean salesrep.department=Sales // real property techie.(parent)=employee // derives from "employee" bean definition techie.(scope)=prototype // bean is a prototype (not a shared instance)
techie.manager(ref)=jeff // reference to another bean techie.department=Engineering // real property techie.usesDialUp=true // real property (overriding parent value) ceo.$0(ref)=secretary // inject 'secretary' bean as 0th constructor arg ceo.$1=1000000 // inject value '1000000' at 1st constructor arg

1.2 層次結構

public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader {
}

public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader {
}

其中:

EnvironmentCapable介面是一個包含和暴露Enviroment引用的元件。所有的spring的application context都繼承了EnvironmentCapable介面,這個介面的主要用來在框架中使用instanceof方法檢測,為了和enviroment進行互動,instanceof方法用來檢查beanFactory例項是否是applicationContext例項。
像說明提到到,applicationContext繼承了EnvironmentCapable介面,因此暴露了一個getEnviroment()方法,ConfigurableApplicationContext重寫了getEnviroment方法,返回了一個ConfigurableEnviroment。
BeanDefinitionReader介面定義了bean definition 解析器的基本方法,特別是使用resource來載入bean definition的方法。

1.3 抽象bean definition解析器AbstractBeanDefinitionReader 

1.3.1 EnvironmentCapable介面只有一個方法getEnviroment(),其實現如下:
        // Inherit Environment if possible
        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
        }
        else {
            this.environment = new StandardEnvironment();
        }

當實現了BeanDefinitionRegistry介面的beanFactory同時實現了EnvironmentCapable介面,則直接使用該beanfactory的getEnviroment()方法,否則使用預設的標準Enviroment。

其中,StandardEnvironment類實現了Enviroment介面,適用於標準的應用(例如非web應用)。該介面還通過AbstractEnvironment介面間接繼承ConfigurableEnvironment介面,故具有ConfigurableEnvironment介面的功能:屬性解析和profile相關操作。同時該類還配置了兩個預設屬性源,按照查詢順序如下:

系統屬性,系統環境變數。

這就是說,若鍵"xyz" 即是當前程序的jvm的系統屬性也是系統環境變數,則鍵值則是從系統屬性中獲取(environment.getProperty("xyz")). 這種順序安排是系統預設的,因為系統屬性優先於jvm,而系統環境變數則可以在給定系統的多個jvm中共享。系統屬性優先允許對不同jvm的環境變數進行定製。

 1.3.2 BeanDefinitionReader介面實現方法

BeanDefinitionReader介面提供了標準的解析器方法: 

ClassLoader getBeanClassLoader() 返回載入bean型別(classes)的class loader。
BeanNameGenerator getBeanNameGenerator() 返回匿名bean(沒有指明name的bean)的BeanNameGenerator. 
BeanDefinitionRegistry getRegistry()返回註冊bean definition的beanFactory. 
ResourceLoader getResourceLoader()返回指定資源位置的 resource loader. 
int loadBeanDefinitions(Resource... resources) 根據指定的多個資源載入bean definition. 
int loadBeanDefinitions(Resource resource) 根據指定的一個資源載入bean definition.
int loadBeanDefinitions(String... locations) 根據指定的多個資源位置載入. 
int loadBeanDefinitions(String location) 根據指定的一個資源位置載入bean definition.

其中,BeanDefinitionRegistry是建構函式傳入的,resourceloader獲取:

        // Determine ResourceLoader to use.
        if (this.registry instanceof ResourceLoader) {
            this.resourceLoader = (ResourceLoader) this.registry;
        }
        else {
            this.resourceLoader = new PathMatchingResourcePatternResolver();
        }

其中最重要的方法是根據特定資源的位置來載入bean definiton

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }

        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                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;
        }
    }

這個方法既支援url絕對路徑的單個資源載入,也支援正則表示式的模式匹配資源載入。其中loadBeanDefinitions()放到子類去執行,在PropertiesBeanDefinitionReader中我們可以看到使用屬性檔案中去讀取(具體細節就不贅敘了),請自行參考程式碼:

public int loadBeanDefinitions(EncodedResource encodedResource, String prefix)
            throws BeanDefinitionStoreException {

        Properties props = new Properties();
        try {
            InputStream is = encodedResource.getResource().getInputStream();
            try {
                if (encodedResource.getEncoding() != null) {
                    getPropertiesPersister().load(props, new InputStreamReader(is, encodedResource.getEncoding()));
                }
                else {
                    getPropertiesPersister().load(props, is);
                }
            }
            finally {
                is.close();
            }
            return registerBeanDefinitions(props, prefix, encodedResource.getResource().getDescription());
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException("Could not parse properties from " + encodedResource.getResource(), ex);
        }
    }

XmlBeanDefinitionReader 讀取bean definition屬性通過特定的xml檔案(具體細節就不贅敘了,請自行參考程式碼),如下所示:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

1.4 根據給定字首,讀取所有屬性並根據名稱將bean增加到bean factory之中。

protected void registerBeanDefinition(String beanName, Map<?, ?> map, String prefix, String resourceDescription)
            throws BeansException {

        String className = null;
        String parent = null;
        String scope = GenericBeanDefinition.SCOPE_SINGLETON;
        boolean isAbstract = false;
        boolean lazyInit = false;

        ConstructorArgumentValues cas = new ConstructorArgumentValues();
        MutablePropertyValues pvs = new MutablePropertyValues();

        for (Map.Entry<?, ?> entry : map.entrySet()) {
            String key = StringUtils.trimWhitespace((String) entry.getKey());
            if (key.startsWith(prefix + SEPARATOR)) {
                String property = key.substring(prefix.length() + SEPARATOR.length());
                if (CLASS_KEY.equals(property)) {
                    className = StringUtils.trimWhitespace((String) entry.getValue());
                }
                else if (PARENT_KEY.equals(property)) {
                    parent = StringUtils.trimWhitespace((String) entry.getValue());
                }
                else if (ABSTRACT_KEY.equals(property)) {
                    String val = StringUtils.trimWhitespace((String) entry.getValue());
                    isAbstract = TRUE_VALUE.equals(val);
                }
                else if (SCOPE_KEY.equals(property)) {
                    // Spring 2.0 style
                    scope = StringUtils.trimWhitespace((String) entry.getValue());
                }
                else if (SINGLETON_KEY.equals(property)) {
                    // Spring 1.2 style
                    String val = StringUtils.trimWhitespace((String) entry.getValue());
                    scope = ((val == null || TRUE_VALUE.equals(val) ? GenericBeanDefinition.SCOPE_SINGLETON :
                            GenericBeanDefinition.SCOPE_PROTOTYPE));
                }
                else if (LAZY_INIT_KEY.equals(property)) {
                    String val = StringUtils.trimWhitespace((String) entry.getValue());
                    lazyInit = TRUE_VALUE.equals(val);
                }
                else if (property.startsWith(CONSTRUCTOR_ARG_PREFIX)) {
                    if (property.endsWith(REF_SUFFIX)) {
                        int index = Integer.parseInt(property.substring(1, property.length() - REF_SUFFIX.length()));
                        cas.addIndexedArgumentValue(index, new RuntimeBeanReference(entry.getValue().toString()));
                    }
                    else {
                        int index = Integer.parseInt(property.substring(1));
                        cas.addIndexedArgumentValue(index, readValue(entry));
                    }
                }
                else if (property.endsWith(REF_SUFFIX)) {
                    // This isn't a real property, but a reference to another prototype
                    // Extract property name: property is of form dog(ref)
                    property = property.substring(0, property.length() - REF_SUFFIX.length());
                    String ref = StringUtils.trimWhitespace((String) entry.getValue());

                    // It doesn't matter if the referenced bean hasn't yet been registered:
                    // this will ensure that the reference is resolved at runtime.
                    Object val = new RuntimeBeanReference(ref);
                    pvs.add(property, val);
                }
                else {
                    // It's a normal bean property.
                    pvs.add(property, readValue(entry));
                }
            }
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Registering bean definition for bean name '" + beanName + "' with " + pvs);
        }

        // Just use default parent if we're not dealing with the parent itself,
        // and if there's no class name specified. The latter has to happen for
        // backwards compatibility reasons.
        if (parent == null && className == null && !beanName.equals(this.defaultParentBean)) {
            parent = this.defaultParentBean;
        }

        try {
            AbstractBeanDefinition bd = BeanDefinitionReaderUtils.createBeanDefinition(
                    parent, className, getBeanClassLoader());
            bd.setScope(scope);
            bd.setAbstract(isAbstract);
            bd.setLazyInit(lazyInit);
            bd.setConstructorArgumentValues(cas);
            bd.setPropertyValues(pvs);
            getRegistry().registerBeanDefinition(beanName, bd);
        }
        catch (ClassNotFoundException ex) {
            throw new CannotLoadBeanClassException(resourceDescription, beanName, className, ex);
        }
        catch (LinkageError err) {
            throw new CannotLoadBeanClassException(resourceDescription, beanName, className, err);
        }
    }

其中,bean definition中所有屬性如下:

Special key to distinguish owner.(abstract)=true Default is "false".
Special key to distinguish owner.(class)=com.myapp.MyClass-
Prefix used to denote a constructor argument definition.
Special key to distinguish owner.(lazy-init)=true Default is "false".
Special key to distinguish owner.(parent)=parentBeanName.
Prefix before values referencing other beans.
Property suffix for references to other beans in the current BeanFactory: e.g.
Special key to distinguish owner.(scope)=prototype.
Separator between bean name and property name.
Special key to distinguish owner.(singleton)=false.
Value of a T/F attribute that represents true.

2. XmlBeanDefinitionReader解析器和PropertiesBeanDefinitionReader解析器基本相同,但在獲取bean definition(上面已經論述過)和bean 的註冊時不同的。

其真正實現如下程式碼所示:

/**
     * Register each bean definition within the given root {@code <beans/>} element.
     */
    protected void doRegisterBeanDefinitions(Element root) {
        String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                    profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            if (!getEnvironment().acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }

        // Any nested <beans> elements will cause recursion in this method. In
        // order to propagate and preserve <beans> default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(this.readerContext, root, parent);

        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }

3. 小結

spring提供了有兩種方式的bean definition解析器:PropertiesBeanDefinitionReader和XmLBeanDefinitionReader即屬性檔案格式的bean definition解析器和xml檔案格式的bean definition解析器。

不同有兩點:

1. 根據Resource 載入 bean definition。PropertiesBeanDefinitionReader從屬性檔案中讀取bean definition的屬性,XmLBeanDefinitionReader從xml檔案中讀取bean definition的屬性。

2. 註冊bean definition。和載入bean definitino類同,只是方式不同。

相關推薦

spring beans原始碼解讀--bean definiton解析

spring提供了有兩種方式的bean definition解析器:PropertiesBeanDefinitionReader和XmLBeanDefinitionReader即屬性檔案格式的bean definition解析器和xml檔案格式的bean definition解析器。 我們先從簡單的Prop

spring beans原始碼解讀--Bean的註解(annotation)

隨著spring註解的引入,越來越多的開發者開始使用註解,這篇文章將對註解的機制進行串聯式的講解,不求深入透徹,但求串起spring beans註解的珍珠,展示給大家。 1. spring beans常用的註解:   public @interface Autowired:可以對成員變數、方法和建構函式進

spring beans原始碼解讀--Bean的定義及包裝

  bean的定義,包裝是java bean的基礎。再怎麼強調它的重要性都不為過,因此深入 瞭解這塊的程式碼對以後的程式碼研究可以起到事半功倍的功效。 1. Bean的定義BeanDefinition 1.1 BeanDefinition 作用 一個BeanDefinition描述了一個bean的例項,

spring beans原始碼解讀 ioc容器始祖--DefaultListableBeanFactory

spring Ioc容器的實現,從根源上是beanfactory,但真正可以作為一個可以獨立使用的ioc容器還是DefaultListableBeanFactory,因此可以這麼說, DefaultListableBeanFactory 是整個spring ioc的始祖,研究透它

spring beans原始碼解讀--總結篇

spring beans下面有如下原始檔包: org.springframework.beans, 包含了操作java bean的介面和類。org.springframework.beans.annotation, 支援包,提供對java 5註解處理bean樣式的支援。org.springframework

spring beans原始碼解讀--XmlBeanFactory

導讀: XmlBeanFactory繼承自DefaultListableBeanFactory,擴充套件了從xml文件中讀取bean definition的能力。從本質上講,XmlBeanFactory等同於DefaultListableBeanFactory+XmlBeanDefinitionReader

Spring原始碼解讀——bean的生命週期(隨筆)

bean建立---初始化----銷燬的過程 容器管理bean的生命週期; 我們可以自定義初始化和銷燬方法;容器在bean進行到當前生命週期的時候來呼叫我們自定義的初始化和銷燬方法 構造(物件建立)     單例項:在容器啟動的時候建立物件     多例項:在每次獲取的時

Spring Ioc 原始碼分析Bean的載入和構造

我們都知道,Spring Ioc和Aop是Spring的核心的功能,因此花一點時間去研究還是很有意義的,如果僅僅是知其所以然,也就體會不到大師設計Spring的精華,還記得那句話,Spring為JavaEE開發帶來了春天。IOC就是Inversion of control 也就是控制反轉的意思,另一種稱呼叫做

Spring原始碼解讀核心容器上節

Spring架構圖 說明 Spring的流行程度就不用我來說了,相信大家如果使用JAVA開發就一定知道它。寫這篇文章的初衷在於:1.瞭解Spring底層實現原理,提升對Spring的認識與理解。2.學習優秀框架程式設計實現,學習優秀的設計模式。3.使用Spring三年多,對於底層細節希望知道更多,便於

Spring原始碼解讀核心容器下節

續 上一篇我們通過ClassPathXmlApplicationContext載入xml檔案,通過BeanFactory獲取例項bean的demo程式碼去解讀了Spring Core Container中的spring-beans,spring-core,spring-context三個元件之間的一些具體類

Spring原始碼解讀Spring MVC HandlerMapping元件(二)

一、HandlerMapping HandlerMapping作用是根據request找到相應的處理器Handler和Interceptors,並將Handler和Interceptors封裝成HandlerExecutionChain 物件返回。Handler

Spring原始碼解讀——元件註冊(隨筆)

@ComponentScan  value:指定要掃描的包 excludeFilters = Filter[] :指定掃描的時候按照什麼規則排除那些元件 includeFilters = Filt

Spring Security4.0.3原始碼分析http標籤解析

最近在學習安全框架Spring Security,想弄清楚其中實現的具體步驟,於是下定決心,研究一下Spring Security原始碼,這篇部落格的目的是想把學習過程記錄下來。學習過程中主要參考了http://dead-knight.iteye.com/cat

Flume 原始碼解讀解析HDFS路徑

專案升級需求: 需要通過flume採集過去時間段的日誌,並存儲到HDFS中,保證路徑以 xxx/ymd=%Y-%m-%d/h=%H 來儲存 實踐很簡單: 1)我們通過flume的RPCClient傳送Event事件,給flume服務端,Event header中放入timeStamp&nbs

spring原始碼解讀 JdbcTemplate原始碼

    在Spring中,JdbcTemplate是經常被使用的類來幫助使用者程式操作資料庫,在JdbcTemplate為使用者程式提供了許多便利的資料庫操作方法,比如查詢,更新等,而且在Spring中,有許多類似 JdbcTemplate的模板,比如HibernateT

Spring原始碼解讀——自動裝配(隨筆)

Spring利用依賴注入(DI),完成對IOC容器中中各個元件的依賴關係賦值; 1、@Autowired:自動注入:     1)、預設優先按照型別去容器中找對應的元件:applicationContext.getBean(BookDao.class);找到就賦值   

Spring原始碼閱讀Bean載入(xml)1

先上兩張圖,簡單的畫了一下beanFactory各個類之間的關係,XmlBeanFactory是bean載入的入口和核心。Spring中大量使用了設計模式和UML中的設計原則,比如單一職責原則,從類圖可以看出,BeanFactory派生的各個介面,根據名字的不同,都增加了

Spring原始碼解讀BeanFactoryPostProcessor的處理

前言     前段時間旁聽了某課堂兩節Spring原始碼解析課,剛好最近自己又在重新學習中,便在這裡記錄一下學習所得。我之前寫過一篇博文,是介紹BeanFactoryPostProcessor跟BeanPostProcessor是如何發揮作用的,當時覺得講的還行,但是現在看來,太粗劣了,

Spring Cloud 原始碼解讀 【這也太神奇了,RestTemplate加上一個@LoadBalanced註解就能實現負載均衡!】

前提概要: 前天,有個前端大佬問了我兩個問題:為啥不引入Ribbon依賴就能使用Ribbon?為啥RestTemplate加上@LoadBalanced註解就能負載均衡了?我也表示很疑惑,而我自己其實也真的沒去了解過,所以趁著工作不太忙,趕緊去研究一波。 第一個問題比較簡單,一般都是其他依賴引入了Ribbon

Spring Cloud 原始碼解讀 【如何配置好OpenFeign的各種超時時間!】

關於Feign的超時詳解: 在Spring Cloud微服務架構中,大部分公司都是利用Open Feign進行服務間的呼叫,而比較簡單的業務使用預設配置是不會有多大問題的,但是如果是業務比較複雜,服務要進行比較繁雜的業務計算,那後臺很有可能會出現Read Timeout這個異常。 1、關於hystrix的熔斷