曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取bean

寫在前面的話

相關背景及資源:html

曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java

曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解git

曹工說Spring Boot源碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,咱們來試一下spring

曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?json

工程代碼地址 思惟導圖地址app

工程結構圖:ide

總體思路

bean definition實在太太重要,能夠說是基礎中的基礎,因此咱們花了不少講在這上面,本講的主題,仍是這個。此次,咱們是從properties文件裏讀取bean definition函數

可是,上次,從json讀取,咱們本身實現了org.springframework.beans.factory.support.AbstractBeanDefinitionReader,使用fastjson從json文件內讀取。spring-boot

此次,咱們不須要本身實現,是由於spring-beans包內,竟然自帶了從properties文件讀取bean的實現類。測試

因此,這樣就變得很簡單了,咱們只須要定義一個applicationContext,讓它使用這個開箱即用的reader便可。

閒言少敘,let's code!

本場大佬簡介--PropertiesBeanDefinitionReader

本類的javadoc如是說:

* Bean definition reader for a simple properties format.
*
* <p>Provides bean definition registration methods for Map/Properties and
* ResourceBundle. Typically applied to a DefaultListableBeanFactory.

這裏說,就是一個從properties格式的文件讀取bean definition的,那麼,是否是properties能夠隨便怎麼寫呢?嗯,按理說,是能夠隨便寫,你別管我怎麼寫,形式重要嗎,重要的是,有這個數據。

bean definition的核心數據有哪些?再回憶一下,beanClassName、scope、lazy-init、parent、abstract等。

parent和abstract,是個新概念,前面我也沒有提,你們看下面的例子可能就懂了(來自於PropertiesBeanDefinitionReader的註釋,我本身梳理了一下)。

這個reader,對properties的格式是有要求的,參考下面這份:

//定義一個抽象bean,名稱爲employee(句號前面爲bean的名稱),表示爲員工,類型爲Employee,兩個屬性:組名:Insurance;useDialUp(我理解爲工位是否配電話):true
employee.(class)=org.springframework.simple.Employee
employee.(abstract)=true
employee.group=Insurance
employee.usesDialUp=false

//定義一個非抽象bean,parent爲抽象的employee,department屬性爲CEOdepartment,usesDialUp爲true,覆蓋了parent的false
ceo.(parent)=employee
ceo.department=ceo department
ceo.usesDialUp=true

//定義另外一個非抽象bean,表示銷售人員,lazy-init,經理字段:引用了另外一個bean,name爲ceo;部門爲Sales
salesrep.(parent)=employee
salesrep.(lazy-init)=true
salesrep.manager(ref)=ceo
salesrep.department=Sales

//這個相似
techie.(parent)=employee
techie.(scope)=prototype
techie.department=Engineering
techie.usesDialUp=true
techie.manager(ref)=ceo

貼心的我給你們花了個圖:

詳細剖析

接下來,咱們仍是先看看這個類:

實現接口

看一個類,其實主要看接口,才能快速瞭解一個類的用途,這裏,它實現了org.springframework.beans.factory.support.BeanDefinitionReader接口。

這個接口的方法以下:

public interface BeanDefinitionReader {

    //獲取bean definition 註冊中心,老朋友DefaultListableBeanFactory實現了該接口
    BeanDefinitionRegistry getRegistry();

    // 獲取資源加載器
    ResourceLoader getResourceLoader();
    
    //獲取classloader
    ClassLoader getBeanClassLoader();

    //獲取bean名稱生成器
    BeanNameGenerator getBeanNameGenerator();

    //從指定資源充,加載bean definition
    int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
    
    //重載
    int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
    
    //重載
    int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
    
    //重載
    int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}

大致,能夠看出來,這個bean definition reader接口,就是使用指定的classloader,從指定的resource,去加載bean definition。

加載bean definition

咱們先看看PropertiesBeanDefinitionReader怎麼構造:

//調用父類,參數傳入了bean definition 註冊表
    public PropertiesBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

    //構造默認的資源加載器、environment
    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this.registry = registry;

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

        // Inherit Environment if possible
        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable)this.registry).getEnvironment();
        }
        else {
            this.environment = new StandardEnvironment();
        }
    }

再看主要的loadBeanDefinition方法,是怎麼實現的:

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource), null);
    }

調用了內部的:

//加載bean definition
public int loadBeanDefinitions(EncodedResource encodedResource, String prefix)
            throws BeanDefinitionStoreException {
    //讀取properties文件內容到props變量
    Properties props = new Properties();
    InputStream is = encodedResource.getResource().getInputStream();
    if (encodedResource.getEncoding() != null) {
        InputStreamReader reader = new InputStreamReader(is, encodedResource.getEncoding());
        props.load(reader);
    }
    else {
        props.load(is);
    }
    //註冊bean definition
    return registerBeanDefinitions(props, prefix, null);
}

繼續深刻上面的倒數第二行的函數:

public int registerBeanDefinitions(Map map, String prefix, String resourceDescription)
            throws BeansException {

        if (prefix == null) {
            prefix = "";
        }
        int beanCount = 0;

        for (Object key : map.keySet()) {
            String keyString = (String) key;
            if (keyString.startsWith(prefix)) {
                // Key is of form: prefix<name>.property
                String nameAndProperty = keyString.substring(prefix.length());
                // Find dot before property name, ignoring dots in property keys.
                int sepIdx = nameAndProperty.lastIndexOf(SEPARATOR);
                
                if (sepIdx != -1) {
                    String beanName = nameAndProperty.substring(0, sepIdx);
                    if (!getRegistry().containsBeanDefinition(beanName)) {
                        // 若是以前沒註冊這個bean,則註冊之,這裏的prefix:prefix+beanName,其實就是從properties文件中篩選出beanName一致的key-value
                        registerBeanDefinition(beanName, map, prefix + beanName, resourceDescription);
                        ++beanCount;
                    }
                }
            }
        }

        return beanCount;
    }

主要就是遍歷map,將property的key用.分割,前面的就是beanName,用beanName做爲前綴,而後調用下一層函數:

public static final String CLASS_KEY = "(class)";
public static final String PARENT_KEY = "(parent)";
public static final String SCOPE_KEY = "(scope)";
public static final String SINGLETON_KEY = "(singleton)";
public static final String ABSTRACT_KEY = "(abstract)";
public static final String LAZY_INIT_KEY = "(lazy-init)";
public static final String REF_SUFFIX = "(ref)";

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());
                  //核心屬性,bean的ClassName
                if (CLASS_KEY.equals(property)) {
                    className = StringUtils.trimWhitespace((String) entry.getValue());
                }//parent屬性
                else if (PARENT_KEY.equals(property)) {
                    parent = StringUtils.trimWhitespace((String) entry.getValue());
                }//是否抽象bean definition
                else if (ABSTRACT_KEY.equals(property)) {
                    String val = StringUtils.trimWhitespace((String) entry.getValue());
                    isAbstract = TRUE_VALUE.equals(val);
                }//scope
                  ...此處不重要的屬性代碼進行省略
                //經過構造器注入其餘bean,咱們例子裏沒涉及
                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));
                    }
                }
                // 這裏引用其餘bean,語法是咱們例子用到的,(ref)
                else if (property.endsWith(REF_SUFFIX)) {
                    property = property.substring(0, property.length() - REF_SUFFIX.length());
                    String ref = StringUtils.trimWhitespace((String) entry.getValue());
                    Object val = new RuntimeBeanReference(ref);
                    pvs.add(property, val);
                }
                else {
                    // It's a normal bean property.
                    pvs.add(property, readValue(entry));
                }
            }
        }
        //構造一個bean definition
        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);
    }

本類的主要代碼就這些,刪減了部分,主要是避免太冗餘,代碼有刪減就會使用...表示。

定義applicationContext

package org.springframework.beans.extend.properties.applicationcontext;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractRefreshableConfigApplicationContext;
import java.io.IOException;


public class ClassPathPropertyFileApplicationContext extends AbstractRefreshableConfigApplicationContext {



    /**
     * Loads the bean definitions via an XmlBeanDefinitionReader.
     * @see XmlBeanDefinitionReader
     * @see #loadBeanDefinitions
     */
    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 構造一個propertiesBeanDefinitionReader,就是前面咱們的主角
        PropertiesBeanDefinitionReader beanDefinitionReader = new PropertiesBeanDefinitionReader(beanFactory);

        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        loadBeanDefinitions(beanDefinitionReader);
    }

    //使用reader,加載bean definition
    protected void loadBeanDefinitions(PropertiesBeanDefinitionReader reader) throws BeansException, IOException {
        String[] configResources = getConfigLocations();
        if (configResources != null) {
            //看這,兄弟
            reader.loadBeanDefinitions(configResources);
        }
    }

    public ClassPathPropertyFileApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }


}

測試代碼

@Slf4j
public class BootStrap {
    public static void main(String[] args) {
        ClassPathPropertyFileApplicationContext context = new ClassPathPropertyFileApplicationContext("beanDefinition.properties");
        Map<String, Employee> beansOfType = context.getBeansOfType(Employee.class);
        for (Map.Entry<String, Employee> entry : beansOfType.entrySet()) {
            log.info("bean name:{},bean:{}",entry.getKey(),entry.getValue());
        }

    }
}

output:

22:17:26.083 [main] INFO  o.s.b.extend.properties.BootStrap - bean name:techie,bean:Employee(group=Insurance, usesDialUp=true, department=Engineering, manager=Employee(group=Insurance, usesDialUp=true, department=ceo department, manager=null))
    
22:17:26.083 [main] INFO  o.s.b.extend.properties.BootStrap - bean name:salesrep,bean:Employee(group=Insurance, usesDialUp=false, department=Sales, manager=Employee(group=Insurance, usesDialUp=true, department=ceo department, manager=null))
    
22:17:26.083 [main] INFO  o.s.b.extend.properties.BootStrap - bean name:ceo,bean:Employee(group=Insurance, usesDialUp=true, department=ceo department, manager=null)

這裏能夠看出來,子bean是繼承了父bean的bean definition,並override了父bean中已經存在的屬性。

總結

工程源碼:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-beans-load-bean-definition-from-properties

這一講,主要是講解了另外一種讀取bean definition的方式,其實就是告訴咱們要打破思想束縛,bean的來源能夠用不少,不必定只有xml和註解。另外,也是培養咱們的抽象思惟,至少bean definition reader這個接口,給咱們的感受就是如此,我無論你resource來自哪裏,只要能讀取bean definition便可,正所謂:英雄不問出處!

咱們做爲技術從業人員也是如此,只要技術夠ok,到哪都能混得走。

下一講,咱們將繼續講解bean definition,主要是bean definition的繼承和override相關內容。

以爲有幫助的話,你們點個贊哈

相關文章
相關標籤/搜索