系列文章
手把手教你實現(xiàn)spring-beans (一)
手把手教你實現(xiàn)spring-beans (二)
手把手教你實現(xiàn)spring-context (TODO)
手把手教你實現(xiàn)spring-aop (TODO)
關(guān)于
??本系列是對tiny-spring項目的詳細(xì)解讀,聚焦spring-beans的基本實現(xiàn),對應(yīng)著(first~sixth)-stage這六個構(gòu)建過程。這部分實現(xiàn)了基礎(chǔ)的IoC容器,DI是它的核心(控制反轉(zhuǎn)和依賴注入的相關(guān)概念可以看這里)。
spring-beans的使用流程
??回想一下在使用BeanFactory.getBean(...)之前,我們要做些什么?首先,定義xml配置文件,告訴Spring我們需要什么樣的對象以及它們之前的關(guān)系,接著初始化BeanFactory讀取配置文件、加載其中的定義信息,最后才是調(diào)用BeanFactory.getBean(),根據(jù)定義信息初始化bean并返回。
??舉個例子,假設(shè)我們有如下兩個類:
public class Car {
private double price;
private String brand;
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
}
public class Person {
private String name;
private Car car;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
??現(xiàn)在有一個叫Saber的妹子有一輛BYD產(chǎn)的價值240000.00的車,如果用Spring來管理的話,大概是這樣:
// 1、定義配置文件,描述需求
<beans>
<bean id="byd" class="test.Car">
<property name="price">
<value>240000.00</value>
</property>
<property name="brand">
<value>BYD</value>
</property>
</bean>
<bean id="saber" class="test.Person">
<property name="name">
<value>Saber</value>
</property>
<property name="car">
<ref bean="byd"/>
</property>
</bean>
</beans>
// 2、讀取配置文件,加載定義信息
Resource resource = new ClassPathResource("test/config.xml");
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);
// 3、調(diào)用getBean()
Person saber = (Person) beanFactory.getBean("saber");
Car byd = saber.getCar();
System.out.println("name = " + saber.getName() + ", car-brand = " + byd.getBrand() + ", car-price = " + byd.getPrice());
// 4、得到打印結(jié)果
name = Saber, car-brand = BYD, car-price = 240000.0
一切從Resource開始
??快速瀏覽完使用流程,我們知道首先是要有xml配置文件來告訴Spring我們需要的對象以及它們之間的關(guān)系。同時,真實的Spring不僅僅支持xml這一種格式,還支持properties文件格式甚至是自定義的格式。Spring是怎么做到的呢?這是因為Spring引入了一層對資源的抽象——Resource接口。Resource接口解決的是配置文件從哪里來、怎么讀取它的問題。
// 在tiny-spring目錄下輸入命令 git checkout first-stage,切換到第一階段,可以看到對Resource接口的描述(這個接口對真實的Resource接口進(jìn)行了大幅精簡)。
// 其中直接定義在Resource接口中的方法抽象了資源從哪里來,定義在父接口中的方法抽象了資源怎么讀。
public interface InputStreamSource {
/**
* 返回代表資源的輸入流。
*/
@Nullable
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
/**
* 從類路徑加載的偽URL協(xié)議前綴。
*/
String CLASSPATH_URL_PREFIX = "classpath:";
/**
* 文件系統(tǒng)中文件的URL協(xié)議名。
*/
String FILESYSTEM_URL_PROTOCOL = "file";
/**
* 檢查資源是否真實存在。
*/
boolean exists();
/**
* 返回指向此資源的URL。
*/
@Nullable
URL getURL() throws IOException;
/**
* 返回表示此資源的文件。
*/
@Nullable
File getFile() throws IOException;
}
??在tiny-spring的實現(xiàn)中,我們采用xml格式來作為配置文件,并且對支持的功能(也就是對應(yīng)的xml標(biāo)簽)進(jìn)行了刪減,因此僅實現(xiàn)了ClassPathResource用來加載classpath下的xml配置文件,作為源碼解析來說應(yīng)該是夠用了。
// 輸入命令git checkout second-stage,切換到第二階段,
// 在DefaultXMLBeanDefinitionParser.java中查看tiny-spring支持的xml標(biāo)簽及屬性。
private static final String TRUE_VALUE = "true";
private static final String BEAN_ELEMENT = "bean";
private static final String CLASS_ATTRIBUTE = "class";
private static final String ID_ATTRIBUTE = "id";
private static final String NAME_ATTRIBUTE = "name";
private static final String SINGLETON_ATTRIBUTE = "singleton";
private static final String DEPENDS_ON_ATTRIBUTE = "depends-on";
private static final String INIT_METHOD_ATTRIBUTE = "init-method";
private static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method";
private static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg";
private static final String INDEX_ATTRIBUTE = "index";
private static final String TYPE_ATTRIBUTE = "type";
private static final String PROPERTY_ELEMENT = "property";
private static final String REF_ELEMENT = "ref";
private static final String BEAN_REF_ATTRIBUTE = "bean";
private static final String LIST_ELEMENT = "list";
private static final String VALUE_ELEMENT = "value";
private static final String NULL_ELEMENT = "null";
private static final String LAZY_INIT_ATTRIBUTE = "lazy-init";
private static final String AUTOWIRE_ATTRIBUTE = "autowire";
private static final String AUTOWIRE_BY_NAME_VALUE = "byName";
private static final String AUTOWIRE_BY_TYPE_VALUE = "byType";
private static final String AUTOWIRE_CONSTRUCTOR_VALUE = "constructor";
private static final String AUTOWIRE_AUTODETECT_VALUE = "autodetect";
??可以看到,tiny-spring復(fù)刻了真實Spring IoC Container的功能子集:每一個<bean>標(biāo)簽都定義了一個容器管理的對象,同時支持setter注入和構(gòu)造函數(shù)注入兩種方式,分別由<property>和<constructor-arg>標(biāo)簽代表,bean之間的引用由<ref>標(biāo)簽代表,注入的值可以是簡單類型,由<value>標(biāo)簽代表,也可以是復(fù)合類型,比如數(shù)組,由<list>標(biāo)簽代表(map/set在tiny-spring中就不做支持了)。其他的諸如自動裝配、懶加載、生命周期回調(diào)等屬性也同樣支持。
XML配置文件到BeanDefinition的轉(zhuǎn)換
??在讀取配置文件之前,思考一下提取出來的信息如何保存?我們說過,每一個<bean>標(biāo)簽都定義了一個容器管理的對象,自然就引出了BeanDefinition,可以說一個<bean>標(biāo)簽就對應(yīng)著一個BeanDefinition實例,singleton、autowire等等都是它的屬性。
// 輸入命令git checkout third-stage,切換到第三階段,查看BeanDefinition的具體定義。
/**
* 保存從xml中解析出來的bean的定義信息。
*/
public class BeanDefinition {
// 不進(jìn)行自動裝配
public static final int AUTOWIRE_NO = 0;
// 通過bean名稱自動裝配
public static final int AUTOWIRE_BY_NAME = 1;
// 通過bean類型自動裝配
public static final int AUTOWIRE_BY_TYPE = 2;
// 自動裝配構(gòu)造函數(shù)
public static final int AUTOWIRE_CONSTRUCTOR = 3;
// 自適應(yīng)裝配模式
public static final int AUTOWIRE_AUTODETECT = 4;
// bean所屬的類, bean的名稱由BeanFactoryRegistry管理
private final Class<?> beanClass;
// 是單實例還是每次獲取都創(chuàng)建,默認(rèn)為true
private boolean singleton = true;
// 對單實例的bean是否需要懶加載,
// 默認(rèn)為false,在BeanFactory初始化時就
// 初始化所有單實例bean
private boolean lazyInit = false;
// 自動裝配的模式
private int autowireMode = AUTOWIRE_NO;
// 所依賴的其他bean的名稱
// dependsOn所代表的bean會在
// 當(dāng)前bean初始化之前得到初始化
private String[] dependsOn;
// 自定義的初始化方法名,要求無參
private String initMethodName;
// 自定義的銷毀方法名,要求無參
private String destroyMethodName;
// setter注入的相關(guān)信息
private MutablePropertyValues propertyValues;
// 構(gòu)造函數(shù)注入的相關(guān)信息
private ConstructorArgumentValues constructorArgumentValues;
// 省略若干
......
}
而對于<property>和<constructor-arg>標(biāo)簽,它們也有著各自的子標(biāo)簽和屬性,因此分別由MutablePropertyValues和ConstructorArgumentValues兩個類來表示。很簡單的兩個類,各位同學(xué)自行查看一下third-stage的代碼即可,這里就不貼了。
BeanDefinition的注冊
??概念上我們知道了一個<bean>標(biāo)簽等于一個BeanDefinition實例,那么tiny-spring怎么實現(xiàn)從XML配置文件到BeanDefinition實例的轉(zhuǎn)換呢?這就引出了XMLBeanDefinitionReader接口,它只有一個方法,從Resource中提取出BeanDefinition(s)。
/**
* 對xml配置文件讀取器的抽象。
* 讀取器最主要的目的是讀取一個個<bean>標(biāo)簽,
* 解析出其中的信息,生成對應(yīng)的BeanDefinition。
*/
public interface XMLBeanDefinitionReader {
/**
* 加載bean的定義信息。
* @param resource 代表一個xml配置文件
*/
void loadBeanDefinition(@NotNull Resource resource);
}
??回想一下BeanFactory.getBean(...)的調(diào)用場景,我們傳入一個bean name,容器根據(jù)bean name找到對應(yīng)的BeanDefinition,通過BeanDefinition描述的信息生成對象并返回。也就是說容器持有著beanName -> BeanDefinition的對應(yīng)關(guān)系,這一層抽象出來也就是BeanDefinitionRegistry。
/**
* 這個接口管理著BeanFactory中BeanDefinition注冊
* 的相關(guān)事宜,因此BeanFactory的實現(xiàn)類也會實現(xiàn)這個接口。
* 單獨抽取出這個接口,是為了讓BeanFactory的職責(zé)更清晰,
* 避免成為上帝接口。BeanFactory就是一個bean工廠,司職于bean的獲取查詢。
*/
public interface BeanDefinitionRegistry {
/**
* 向BeanFactory中注冊bean的定義信息
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
}
顯然,這兩個接口是要組合使用的。XMLBeanDefinitionReader加載出BeanDefinition(s)之后,由BeanDefinitionRegistry來執(zhí)行注冊。Spring在實現(xiàn)時,額外提供了一個策略接口XMLBeanDefinitionParser來進(jìn)行真正的解析,tiny-spring的DefaultXMLBeanDefinitionReader也是這么實現(xiàn)的。
/**
* 對xml配置文件解析器的抽象。
* 這是一個策略接口,XMLBeanDefinitionReader通過
* XMLBeanDefinitionParser來做具體的解析。
*/
public interface XMLBeanDefinitionParser {
/**
* 讀取<bean>標(biāo)簽的定義生成BeanDefinition,再通過
* BeanDefinitionRegistry注冊進(jìn)BeanFactory。
* @param document 代表xml配置文件的Document對象
* @param classLoader 加載<bean>標(biāo)簽對應(yīng)JavaBean的類加載器
* @param registry 用來注冊BeanDefinition的注冊器
*/
void registerBeanDefinitions(@NotNull Document document,
@NotNull ClassLoader classLoader,
@NotNull BeanDefinitionRegistry registry);
}
XMLBeanDefinitionReader將真正的解析行為代理給了XMLBeanDefinitionParser。NOTE:說是策略模式可以,說是代理模式也可以。具體如何解析,只是一個對應(yīng)的算法,從這個層面說是策略模式,ok;XMLBeanDefinitionReader本身不進(jìn)行xml文件的解析,而是將這個行為委托給了XMLBeanDefinitionParser,這么說是代理也沒啥毛病吧。設(shè)計模式吧,大多都是語意上的區(qū)別,理解就好,犯不著鉆牛角尖,Spring中有很多地方用到了這種模式。
XML配置文件的解析
??以上都理解了之后,下面就進(jìn)入DefaultXMLBeanDefinitionParser執(zhí)行真正的配置文件解析了。按照Spring xml配置文件的格式,首先獲取最頂層標(biāo)簽<beans>(這里其實是什么標(biāo)簽都可以),<bean>標(biāo)簽是<beans>的子標(biāo)簽,因此我們逐個遍歷<beans>的子標(biāo)簽找到其中的<bean>標(biāo)簽,因此重心便轉(zhuǎn)到了解析<bean>標(biāo)簽上。
@Override
public void registerBeanDefinitions(Document document, ClassLoader classLoader, BeanDefinitionRegistry registry) {
// 獲取頂層元素(也就是<beans>標(biāo)簽)
Element root = document.getDocumentElement();
// 獲取<beans>下的子標(biāo)簽列表
NodeList nodes = root.getChildNodes();
// 統(tǒng)計<bean>標(biāo)簽的數(shù)量
int numberOfBeans = 0;
// 遍歷子標(biāo)簽列表
for (int i = 0; i < nodes.getLength(); ++i) {
Node node = nodes.item(i);
// 找到<bean>標(biāo)簽
if (node instanceof Element &&
BEAN_ELEMENT.equals(node.getNodeName())) {
// 每一個<bean>標(biāo)簽就對應(yīng)一個BeanDefinition
numberOfBeans++;
// 加載其配置信息
loadBeanDefinition((Element) node, classLoader, registry);
}
}
System.out.println("一共找到" + numberOfBeans + "個<bean>標(biāo)簽");
}
每個<bean>標(biāo)簽對應(yīng)著一個BeanDefinition,因此在解析的過程中我們創(chuàng)建了一個BeanDefinition實例來保存解析的結(jié)果。tiny-spring并不支持bean name alias和inner bean,也不支持BeanFactory的層級結(jié)構(gòu),因此<bean>標(biāo)簽必須指定id屬性和class屬性,解析出來的BeanDefinition就直接交給BeanDefinitionRegistry注冊了。
/**
* 解析并注冊<bean>標(biāo)簽
*/
private void loadBeanDefinition(Element element, ClassLoader classLoader, BeanDefinitionRegistry registry) {
// tiny spring不支持inner bean,也不支持bean的別名,
// 因此獲取到的id就是bean的名稱,也是關(guān)聯(lián)對應(yīng)BeanDefinition的key
String beanName = element.getAttribute(ID_ATTRIBUTE);
if (!StringUtils.hasLength(beanName)) {
throw new BeansException("每個<bean>標(biāo)簽都必須明確指定id屬性");
}
// 解析出對應(yīng)的BeanDefinition
BeanDefinition beanDefinition = parseBeanDefinition(beanName, element, classLoader);
// 檢驗一下是否有效
beanDefinition.validate();
// 并注冊進(jìn)BeanFactory
registry.registerBeanDefinition(beanName, beanDefinition);
System.out.println("已解析出[" + beanName + "]對應(yīng)的bean定義[" + beanDefinition + "]");
}
解析的過程是非常直白的,查看<bean>標(biāo)簽有沒有定義lazy-init、singleton和init-method等屬性,有的話提取出來存儲進(jìn)BeanDefinition。
/**
* 解析<bean>標(biāo)簽
*/
private BeanDefinition parseBeanDefinition(String beanName, Element element, ClassLoader classLoader) {
// tiny spring也沒有支持BeanFactory的層次結(jié)構(gòu),
// 因此每個bean也需要明確指明其所屬的類
String beanClassName = element.getAttribute(CLASS_ATTRIBUTE);
if (!StringUtils.hasLength(beanClassName)) {
throw new BeansException("每個<bean>標(biāo)簽都必須明確指定class屬性");
}
try {
// 加載這個類
Class<?> beanClass = Class.forName(beanClassName, true, classLoader);
// 獲取所有<property>標(biāo)簽的內(nèi)容
MutablePropertyValues propertyValues = parseAllPropertyElements(beanName, element);
// 獲取所有<constructor-arg>標(biāo)簽的內(nèi)容
ConstructorArgumentValues constructorArgumentValues = parseAllConstructorArgElements(beanName, element);
// 生成bean的定義信息
BeanDefinition beanDefinition = new BeanDefinition(beanClass, propertyValues, constructorArgumentValues);
// 獲取依賴信息
if (element.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = element.getAttribute(DEPENDS_ON_ATTRIBUTE);
beanDefinition.setDependsOn(StringUtils.split(dependsOn, ",; ", true, true));
}
// 獲取自動裝配模式
String autowire = element.getAttribute(AUTOWIRE_ATTRIBUTE);
beanDefinition.setAutowireMode(getAutowireMode(autowire));
// 獲取自定義的初始化方法名
String initMethodName = element.getAttribute(INIT_METHOD_ATTRIBUTE);
if (StringUtils.hasLength(initMethodName)) {
beanDefinition.setInitMethodName(initMethodName);
}
// 獲取自定義的銷毀方法名
String destroyMethodName = element.getAttribute(DESTROY_METHOD_ATTRIBUTE);
if (StringUtils.hasLength(destroyMethodName)) {
beanDefinition.setDestroyMethodName(destroyMethodName);
}
// 獲取是否配置成單例
if (element.hasAttribute(SINGLETON_ATTRIBUTE)) {
beanDefinition.setSingleton(TRUE_VALUE.equals(element.getAttribute(SINGLETON_ATTRIBUTE)));
}
// 獲取是否配置成懶加載
String lazyInit = element.getAttribute(LAZY_INIT_ATTRIBUTE);
if (beanDefinition.isSingleton()) { // 此屬性對單例的bean才有效
beanDefinition.setLazyInit(TRUE_VALUE.equals(lazyInit));
}
return beanDefinition;
} catch (ClassNotFoundException e) {
throw new BeansException("找不到[" + beanClassName + "]對應(yīng)的類", e);
}
}
<property>和<constructor-arg>因為是<bean>的子標(biāo)簽而不是屬性,因此需要單獨處理。<property>和<constructor-arg>標(biāo)簽的解析過程基本是一致的,這里就以<property>來作為說明。首先也是要遍歷出<bean>下的所有<property>標(biāo)簽,轉(zhuǎn)換成對應(yīng)的PropertyValue,然后存入MutablePropertyValues,最后歸入BeanDefinition。在tiny-spring中,<property>下可能存在<value>、<list>或<ref>中的一個來表示要注入屬性的值,具體是怎么處理的呢?
/**
* 解析帶有屬性值的標(biāo)簽,提取值
*/
private Object parsePropertySubElement(String beanName, Element element) {
// <property>標(biāo)簽下有<value>/<list>/<ref>三種標(biāo)簽標(biāo)識了屬性值
// <set>/<map>/inner bean這些這里就不做支持了
if (element.getTagName().equals(REF_ELEMENT)) {
// 如果是<ref>,它指向另一個bean的定義
String beanRef = element.getAttribute(BEAN_REF_ATTRIBUTE);
if (!StringUtils.hasLength(beanRef)) {
throw new BeansException("[" + beanName + "] - <ref>標(biāo)簽必須通過bean屬性指明引用的其他bean");
}
// 返回一個包裝引用的對象
return new RuntimeBeanReference(beanRef);
} else if (element.getTagName().equals(LIST_ELEMENT)) {
// 是一個List
return getList(beanName, element);
} else if (element.getTagName().equals(VALUE_ELEMENT)) {
// 是字面值
return getTextValue(beanName, element);
} else if (element.getTagName().equals(NULL_ELEMENT)) {
// 是一個null標(biāo)簽
return null;
}
throw new BeansException("[" + beanName + "] - 發(fā)現(xiàn)一個<property>標(biāo)簽下未知的子標(biāo)簽<" + element.getTagName() + ">");
}
對于<ref>標(biāo)簽,我們用RuntimeBeanReference來標(biāo)識它是一個對其它bean的引用,后續(xù)通過RuntimeBeanReference中保存的bean name,使用BeanFactory的getBean(...)就能獲取到對應(yīng)的bean并設(shè)置進(jìn)去;對于<list>標(biāo)簽,我們同樣用一個標(biāo)記類ManagedList來標(biāo)識它,至于<value>,在xml中只是普通的字符串,直接提取出來保存即可,后續(xù)根據(jù)屬性的實際類型來進(jìn)行轉(zhuǎn)換,當(dāng)然,這是后面的故事了。
總結(jié)
??至此我們完成了配置文件的抽象和讀取過程,接下來就是BeanFactory的戲份了。下一篇將會詳細(xì)介紹BeanFactory如何利用這些配置信息來幫我們管理對象,碼字不易,感覺有幫助的話,點個star吧~~