曹工說Spring Boot原始碼(5)-- 怎麼從properties檔案讀取bean
寫在前面的話
相關背景及資源:
曹工說Spring Boot原始碼(1)-- Bean Definition到底是什麼,附spring思維導圖分享
曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解
曹工說Spring Boot原始碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,我們來試一下
曹工說Spring Boot原始碼(4)-- 我是怎麼自定義ApplicationContext,從json檔案讀取bean definition的?
工程程式碼地址 思維導圖地址
工程結構圖:
整體思路
bean definition實在太過重要,可以說是基礎中的基礎,所以我們花了很多講在這上面,本講的主題,還是這個。這次,我們是從properties檔案裡讀取bean definition
但是,上次,從json讀取,我們自己實現了org.springframework.beans.factory.support.AbstractBeanDefinitionReader
,使用fastjson從json檔案內讀取。
這次,我們不需要自己實現,是因為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
相關內容。
覺得有幫助的話,大家點個贊