step5
看完了前面的幾步,到現在我們必然要想到的問題就是,資料要是放在xml中怎麼讀?
其實按照正常思維一步一步來,從xml中讀資料和之前手工配進去并沒有什麼大的差別,隻要讀出來就OK了。
先看測試程式,
public void Step5() throws Exception {
// 1.讀取配置
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader();
xmlBeanDefinitionReader.loadBeanDefinitions("bin/resources/tinyioc.xml");
// 2.初始化BeanFactory并注冊bean
BeanFactory beanFactory = new AbstractBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getBeanDefinitionMap().entrySet()) {
((AbstractBeanFactory)beanFactory).registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}
// 3.擷取bean
HelloWorldServiceImpl helloWorldService = (HelloWorldServiceImpl) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld3();
}
關于路徑問題
在java中,擷取檔案(包括xml,jpg等等)有兩種方式
Class類下的getResource(String path)方法與
ClassLoader類下的getResource(String path)方法
先說後面一種,ClassLoader.getResource()參數中不帶"/",預設就是根路徑(在Eclipse中,跟路徑就是工程下的bin檔案夾,在預設情況下,eclipse會把項目中的src下的内容拷貝到bin下,是以也可以了解為根目錄就是src目錄)
第一種Class.getResource()可以帶"/"也可以不帶
一旦帶了/ 就預設從根路徑(就是bin 就是src)下查找了
如果沒有/ 就從這個類本身的那個路徑下查找
在step5這個例子裡
InputStream is = new FileInputStream(local);這裡的local是從項目目錄算的,是以還得加上bin
看了測試代碼大家就會知道,我們的程式結構了吧,XmlBeanDefinitionReader的主要作用就是讀取xml,然後轉換成一個個BeanDefinition,再存儲進BeanDefinitionMap,再建立一個BeanFactory,将BeanDefinitionMap中的記錄一個一個再注冊一邊。
public class XmlBeanDefinitionReader {
//bean清單 就是前面說的學校裡面的學生資訊表
private Map<String, BeanDefinition> beanDefinitionMap;
public XmlBeanDefinitionReader(){
beanDefinitionMap = new HashMap<String, BeanDefinition>();
}
public void loadBeanDefinitions(String local) throws IOException, ParserConfigurationException, SAXException {
InputStream is = new FileInputStream(local);
parseNode(is);
}
看了loadBeanDefinitions,很簡單吧,就是建一個InputStream,連接配接到檔案上,然後從檔案中讀資料。
這裡面的東西不難,但是比較繁雜,牽扯最多的就是對xml的解析
/**
* 通過InputStream 獲得每一個bean
* @param is
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
*/
public void parseNode(InputStream is) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory domfac = DocumentBuilderFactory.newInstance();
DocumentBuilder domBuilder = domfac.newDocumentBuilder();
// 預設是工程目錄
// InputStream is = new FileInputStream("bin/resources/tinyioc.xml");
Document doc = domBuilder.parse(is);
Element root = doc.getDocumentElement();
NodeList beans = root.getChildNodes();
for (int i = 0; i < beans.getLength(); i++)
if (beans.item(i) instanceof Element) {
Element el=(Element)beans.item(i);
parseElement(el);
}
is.close();
}
/**
* 分析每一個bean的id class
* @param el
*/
public void parseElement(Element el){
String id=el.getAttribute("id");
String classPath=el.getAttribute("class");
// System.out.println(id+" "+classPath);
BeanDefinition bd=new BeanDefinition();
bd.setBeanClassName(classPath);
parseProperties(el,bd);
beanDefinitionMap.put(id, bd);
}
/**
* 分析每一個bean的參數 并加入到beandefinition的property裡面
* @param el
* @param bd
*/
public void parseProperties(Element el,BeanDefinition bd){
NodeList bl=el.getElementsByTagName("property");
for (int i = 0; i < bl.getLength(); i++)
if (bl.item(i) instanceof Element) {
Element property=(Element)bl.item(i);
String name=property.getAttribute("name");
// System.out.print(" "+name+" ");
if (property.getAttribute("ref")!="") {
BeanReference br=new BeanReference(property.getAttribute("ref"));
PropertyValue pV=new PropertyValue(name,br);
bd.getPropertyValues().addPropertyValue(pV);
// System.out.println(" "+br.getName()+" ");
}
if (property.getAttribute("value")!="") {
String value=property.getAttribute("value");
PropertyValue pV=new PropertyValue(name, value);
bd.getPropertyValues().addPropertyValue(pV);
// System.out.println(value);
}
}
}
public Map<String, BeanDefinition> getBeanDefinitionMap() {
return beanDefinitionMap;
}
再剩下的代碼,參考step4就ok
step6
如果仔細,比對XmlBeanDefinitionReader與AbstractBeanFactory,就能發現兩個類裡面都有beanDefinitionMap,重寫兩邊,不合适。
另外在step5中
// 2.初始化BeanFactory并注冊bean
BeanFactory beanFactory = new AbstractBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getBeanDefinitionMap().entrySet()) {
((AbstractBeanFactory)beanFactory).registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}
按照使用者與建立者分離的原則,初始化注冊的代碼出現在用戶端也不合适;
怎麼辦?
合起來呗。
還是先寫測試代碼 如下
<pre name="code" class="java">public void Step7() throws Exception {
ApplicationContext ac=new ApplicationContext("bin/resources/tinyioc.xml");
HelloWorldServiceImpl helloWorldService = (HelloWorldServiceImpl) ac.getBean("helloWorldService");
helloWorldService.helloWorld3();
}
漂亮!關鍵就是ApplicationContext,上面已經說了,要把XmlBeanDefinitionReader與AbstractBeanFactory合起來,也就是說要把getBean與loadBeanDefinitions裝到一個類裡面去
package com.myspring.context;
import java.util.Map;
import com.bjsxt.spring.BeanFactory;
import com.myspring.beans.BeanDefinition;
import com.myspring.beans.factory.AbstractBeanFactory;
import com.myspring.beans.xml.XmlBeanDefinitionReader;
public class ApplicationContext implements BeanFactory {
private AbstractBeanFactory abf=new AbstractBeanFactory();
public ApplicationContext(String local) throws Exception {
// InputStream is = new FileInputStream(local);
loadBeanDefinitions(abf,local);
}
protected void loadBeanDefinitions(AbstractBeanFactory beanFactory,String configLocation) throws Exception {
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader();
xmlBeanDefinitionReader.loadBeanDefinitions(configLocation);
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry :
xmlBeanDefinitionReader.getBeanDefinitionMap().entrySet()) {
beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}
}
@Override
public Object getBean(String id) {
// TODO Auto-generated method stub
Object obj=null;
try {
obj = abf.getBean(id);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj;
}
}
AbstractBeanFactory與ApplicationContext都繼承了BeanFactory,然後我們不直接用BeanFactory,而是讓ApplicationContext中裝一個AbstractBeanFactory,這不就是最簡單的代理模式麼?
代碼到這裡,就算是完成了黃億華大神的TinySpring中IoC的大部分代碼
之是以說是大部分,也就是說現在大家看到的代碼還有一些部分與TinySpring不同
主要在這幾個方面
1 Resources部分
在我完成的代碼裡,讀取xml的時候直接就是一個InputStream,TinySpring的方式是有一個Resource類,同時還有一個LoadResource類用來加載資源,當然實作的内部機理都是inputstream;
2 接口問題
我一直認為,良好的代碼是一次一次重構出來的,依我現在的水準,确實不能夠很清晰地說出,分了那麼多層接口,抽象類的實際作用,是以在我的代碼裡各個部分都很"薄弱"(隻有一層)
3 對于類的加載,有兩種方式一種直接加載,一種延遲加載,TinySpring最開始的那幾個step還是在getBean的時候才newInstance的,但是到後面
protected void onRefresh() throws Exception{
beanFactory.preInstantiateSingletons();
}
public void preInstantiateSingletons() throws Exception {
for (Iterator<String> it = this.beanDefinitionNames.iterator(); it.hasNext();) {
String beanName = (String) it.next();
getBean(beanName);
}
}
是以的類都直接加載了;
4 單例模式
TinySpring中一個bean預設隻會加載一次,第二次getBean()的時候會取出之前已經creat的那個;
public Object getBean(String name) throws Exception {
BeanDefinition beanDefinition = beanDefinitionMap.get(name);
if (beanDefinition == null) {
throw new IllegalArgumentException("No bean named " + name + " is defined");
}
Object bean = beanDefinition.getBean();
if (bean == null) { //******************查找beanDefinition
bean = doCreateBean(beanDefinition);
bean = initializeBean(bean, name);
beanDefinition.setBean(bean);
}
return bean;
}
protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
Object bean = createBeanInstance(beanDefinition);
beanDefinition.setBean(bean); //******************寫入beanDefinition
applyPropertyValues(bean, beanDefinition);
return bean;
}