簡化版Spring
Github上有個Spring功能簡化的項目:https://github.com/code4craft/tiny-spring ,簡單包含了Spring的IoC以及AOP功能怜跑。本文簡單介紹了它的IoC實現(xiàn)样勃,AOP以后有空再說。
tiny-spring
是為了學(xué)習(xí)Spring的而開發(fā)的,可以認為是一個Spring的精簡版彤灶。Spring的代碼很多看幼,層次復(fù)雜,閱讀起來費勁幌陕。我嘗試從使用功能的角度出發(fā)诵姜,參考Spring的實現(xiàn),一步一步構(gòu)建搏熄,最終完成一個精簡版的Spring棚唆。有人把程序員與畫家做比較,畫家有門基本功叫臨摹心例,tiny-spring可以算是一個程序的臨摹版本-從自己的需求出發(fā)宵凌,進行程序設(shè)計,同時對著名項目進行參考止后。
下面開始進入tiny-spring的項目構(gòu)建瞎惫。
從XML中讀取Bean信息
從XML中讀取信息自然需要XML文件的輸入流InputStream
,因此我們對InputStream
流的獲取進行約束:
public interface Resource {
InputStream getInputStream() throws IOException;
}
增加一個子接口ResourceLoader
繼承Resource
接口:
public interface ResourceLoader extends Resource {
Resource getResource(String location);
}
最后译株,使用實現(xiàn)類獲取指定位置文件的InputStream
:
public class UrlResourceLoader implements ResourceLoader {
private URL url;
@Override
public InputStream getInputStream() throws IOException{
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
return urlConnection.getInputStream();
}
@Override
public Resource getResource(String location) {
URL resource = this.getClass().getClassLoader().getResource(location);
url = resource;
return this;
}
}
測試看看:
@Test
public void test() throws IOException {
ResourceLoader resourceLoader = new UrlResourceLoader();
Resource resource = resourceLoader.getResource("tinyioc.xml");
InputStream inputStream = resource.getInputStream();
Assert.assertNotNull(inputStream);
}
可以看到瓜喇,獲取指定XML文件的輸入流可以通過測試。
文件讀取沒有問題后歉糜,剩下的就是解析以及保存讀取的內(nèi)容了乘寒。
BeanDefinition
Spring使用BeanDefinition
接口定義作為Bean實例和XML配置文件之間的中間層。
為簡便起見匪补,tiny-spring
將BeanDefinition
定義為一個類:
public class BeanDefinition {
private Object bean;
private Class beanClass;
private String beanClassName;
private PropertyValues propertyValues = new PropertyValues();
public BeanDefinition() {
}
//Getter伞辛、Setter
}
PropertyValues
屬性則封裝了從XML中讀取的屬性:
public class PropertyValues {
private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
public PropertyValues() {
}
public void addPropertyValue(PropertyValue pv) {
//TODO:這里可以對于重復(fù)propertyName進行判斷,直接用list沒法做到
this.propertyValueList.add(pv);
}
public List<PropertyValue> getPropertyValues() {
return this.propertyValueList;
}
}
其實就是key-value:
public class PropertyValue {
private final String name;
private final Object value;
public PropertyValue(String name, Object value) {
this.name = name;
this.value = value;
}
//Getter夯缺、Setter
}
上面說過如何從指定文件獲取InputStream
蚤氏。
UrlResourceLoader
只是提供了獲取InputStream
的方法,保存從XML中讀取轉(zhuǎn)化出的BeanDefinition
還需要一個類進行保存踊兜,tiny-spring
為從XML中加載BeanDefinition
安排了接口BeanDefinitionReader
:
public interface BeanDefinitionReader {
void loadBeanDefinitions(String location) throws Exception;
}
從指定文件中讀取配置信息竿滨。它的實現(xiàn)類AbstractBeanDefinitionReader
則擴充了該接口的功能,同時保存從XML中讀取出的BeanDefinition
:
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader{
private Map<String,BeanDefinition> registry;
private ResourceLoader resourceLoader;
protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
this.registry = new HashMap<String, BeanDefinition>();
this.resourceLoader = resourceLoader;
}
//Getter润文、Setter
}
這里的registry我覺得可以與后續(xù)BeanFactory的beanDefinitionMap整合在一起姐呐,不需要將
BeanDefinition
的集合存兩個地方。
registry屬性將BeanDefinition
的name
作為key典蝌, BeanDefinition
自身作為value保存在Map
中曙砂。
最后,由XmlBeanDefinitionReader
實現(xiàn)具體的方法:
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
super(resourceLoader);
}
@Override
public void loadBeanDefinitions(String location) throws Exception {
InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
doLoadBeanDefinitions(inputStream);
}
}
接下來就很明顯了骏掀,doLoadBeanDefinitions需要對XML文件做詳細的處理鸠澈,包括將屬性名以及屬性值構(gòu)建為PropertyValues
柱告、獲取一個Bean對另一個Bean的引用等。
說到這Bean引用笑陈,當(dāng)前的工具類不能解決一個Bean對另一個Bean的引用际度,所以這里還需要引入一個代表類引用的類BeanReference
:
public class BeanReference {
private String name;
private Object bean;
//Getter、Setter
}
這樣涵妥,tiny-spring
就可以完成XML配置到BeanDefinition的轉(zhuǎn)換了乖菱。
BeanFactory
眾所周知,BeanFactory
是Spring的核心容器接口蓬网。如果是借助BeanFactory
操作Bean的話窒所,用的最多的一個方法就是getBean()
。
比如:
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
即從Spring容器中獲取名為"helloWorldService"的Bean實例帆锋。
如果想要模仿Spring的話吵取,一個BeanFactory
接口必不可少,它代表著Spring對外暴露的接口:
public interface BeanFactory {
Object getBean(String name) throws Exception;
}
有了BeanFactory
自然需要實現(xiàn)類锯厢,比如保存實例化后的Bean集合皮官。考慮到對于XML的處理已經(jīng)由XmlBeanDefinitionReader
等類完成实辑,并已經(jīng)生成了BeanDefinition
集合捺氢。因此,BeanFactory
及其實現(xiàn)類的重點在于完成PropertyValue
的賦值以及Bean的實例化徙菠,包括處理依賴注入等讯沈。
getBean()
方法無疑是最重要的一個方法郁岩,不妨借助抽象類AbstractBeanFactory
對其做一個初步的實現(xiàn):
@Override
public Object getBean(String name) throws Exception {
// 嘗試從緩存中獲取BeanDefinition(BeanDefinition中維護了Bean的實例)
BeanDefinition beanDefinition = beanDefinitionMap.get(name);
if (beanDefinition == null) {
throw new IllegalArgumentException("No bean named " + name + " is defined");
}
Object bean = beanDefinition.getBean();
if (bean == null) {
// 實例化Bean
bean = doCreateBean(beanDefinition);
beanDefinition.setBean(bean);
}
return bean;
}
實例化Bean方法:
protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
Object bean = createBeanInstance(beanDefinition);
beanDefinition.setBean(bean);
applyPropertyValues(bean, beanDefinition);
return bean;
}
protected Object createBeanInstance(BeanDefinition beanDefinition) throws Exception {
return beanDefinition.getBeanClass().newInstance();
}
/**
* 空方法婿奔,留給子類實現(xiàn)
* @param bean
* @param beanDefinition
* @throws Exception
*/
protected void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception {
}
applyPropertyValues類似于模板設(shè)計模式,即確定步驟的執(zhí)行順序问慎,但某些步驟的具體實現(xiàn)還未知萍摊。
來一個子類AutowireCapableBeanFactory
實現(xiàn)該方法:
public class AutowireCapableBeanFactory extends AbstractBeanFactory {
protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
Object value = propertyValue.getValue();
// 處理Bean引用
if (value instanceof BeanReference) {
BeanReference beanReference = (BeanReference) value;
value = getBean(beanReference.getName());
}
try {
// 執(zhí)行類的set方法
Method declaredMethod = bean.getClass().getDeclaredMethod(
"set" + propertyValue.getName().substring(0, 1).toUpperCase()
+ propertyValue.getName().substring(1), value.getClass());
declaredMethod.setAccessible(true);
declaredMethod.invoke(bean, value);
} catch (NoSuchMethodException e) {
Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
declaredField.setAccessible(true);
declaredField.set(bean, value);
}
}
}
}
測試
// 1.讀取配置
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new UrlResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");
// 2.初始化BeanFactory并注冊bean
AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}
// 3.獲取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
Assert.assertNotNull(helloWorldService);
沒問題,詳細代碼可以訪問tiny-spring
項目獲取如叼。
小結(jié)
Spring源碼實在龐大冰木,新手很難一下子適應(yīng),尤其是它錯綜復(fù)雜的繼承以及實現(xiàn)關(guān)系笼恰,看多了頭疼踊沸。
tiny-spring
構(gòu)建了簡單的IoC以及AOP實現(xiàn),像咱這樣的新手看看還挺不錯社证。