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中所有屬性如下:

static String ABSTRACT_KEY
Special key to distinguish owner.(abstract)=true Default is "false".
static String CLASS_KEY
Special key to distinguish owner.(class)=com.myapp.MyClass-
static String CONSTRUCTOR_ARG_PREFIX
Prefix used to denote a constructor argument definition.
static String LAZY_INIT_KEY
Special key to distinguish owner.(lazy-init)=true Default is "false".
static String PARENT_KEY
Special key to distinguish owner.(parent)=parentBeanName.
static String REF_PREFIX
Prefix before values referencing other beans.
static String REF_SUFFIX
Property suffix for references to other beans in the current BeanFactory: e.g.
static String SCOPE_KEY
Special key to distinguish owner.(scope)=prototype.
static String SEPARATOR
Separator between bean name and property name.
static String SINGLETON_KEY
Special key to distinguish owner.(singleton)=false.
static String TRUE_VALUE
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類同,只是方式不同。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM