實現極簡IoC容器
阿新 • • 發佈:2020-09-15
建立Apple類
public class Apple { private String title; private String color; private String origin; public Apple(){ } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getColor() {return color; } public void setColor(String color) { this.color = color; } public String getOrigin() { return origin; } public void setOrigin(String origin) { this.origin = origin; } }
編寫applicationContext.xml檔案public class Apple { private String title; private String color; private String origin; public Apple(){ } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getOrigin() { return origin; } public void setOrigin(String origin) { this.origin = origin; } }
建立ApplicationContext介面,接口裡建立getBean抽象類<?xml version="1.0" encoding="UTF-8" ?> <beans> <bean id="sweetApple" class="com.imooc.spring.ioc.entity.Apple"> <property name="title" value="紅富士"/> <property name="color" value="紅色"/> <property name="origin" value="歐洲"/> </bean> </beans>
public interface ApplicationContext {
public Object getBean(String beanId);
}
建立ClassPathXmlApplicationContext實現類,實現ApplicationContext介面。
public class ClassPathXmlApplicationContext implements ApplicationContext {
private Map iocContainer = new HashMap();
public ClassPathXmlApplicationContext(){
try {
String filePath = this.getClass().getResource("/applicationContext.xml ").getPath();
}catch (Exception e){
}
}
public Object getBean(String beanId){
return null;
}
}
作為java使用Map物件儲存beanId和所對應的物件,Mao是一個鍵值對的結構,作為鍵就對應了beanId,作為值就對應了容器建立過程中所產生的物件。
建立一個HashMap作為IoC容器
private Map iocContainer = new HashMap();
下一步就是在例項化ClassPathXmlApplicationContext物件過程中去載入處理xml檔案。在ClassPathXmlApplicationContext構造方法中,在物件初始化的時候讀取剛才編寫的applicationContext.xml檔案。
getResource方法用於從classpath下獲取指定的檔案資源。之後通過getPath得到這個檔案的路徑。這樣就能得到配置檔案的實體地址了。
public ClassPathXmlApplicationContext(){
try {
String filePath = this.getClass().getResource("/applicationContext.xml ").getPath();
}catch (Exception e){
}
}
但是作為這個地址如果中間包含中文,可能出現路徑找不到的問題,所以還需要url的解碼
filePath = new URLDecoder().decode(filePath,"UTF-8");
接下來作為獲取到的xml路徑如何對xml進行解析?
需要引入dom4j和jaxen,在pom.xml中加入依賴。
Dom4j是java的xml解析元件。Jaxen是Xpath表示式直譯器。Dom4j底層是依賴Jaxen的,所以都要引入
<dependencies>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>
利用Dom4j提供的SAXReader物件去載入解析filePath對應的xml。reader物件中提供了一個read方法用於讀取指定檔案。這裡新建立一個File,將filePath傳入其中,代表了所對應的檔案物件。然後再提供給reader進行讀取解析。這樣就能得到xml對應的文件物件document。所有的文件資料都包含在document物件中。
try {
String filePath = this.getClass().getResource("/applicationContext.xml ").getPath();
filePath = new URLDecoder().decode(filePath,"UTF-8");
SAXReader reader = new SAXReader();
Document document = reader.read(new File(filePath));
}catch (Exception e){
}
之後的工作就是按照xml格式,依次進行讀取。首先文件的根節點是beans,beans下面擁有若干bean標籤,先把它提取出來。document.getRootElement()得到根節點,然後在根節點下用selectNodes(),selectNodes是按照指定的Xpath表示式來得到節點的集合。直接傳入bean就會將當前根節點下所有bean子標籤獲取了。返回的是一個List集合,集合中每個物件都是一個Node節點。
List<Node> beans = document.getRootElement().selectNodes("bean");
接下來for迴圈遍歷。作為Node有多種子型別,作為每一個beans,它的實際型別為Element(元素)。在遍歷過程中,每一個bean標籤都包含了兩個屬性,一個id,一個class,需要讀取出來。使用ele.attributeValue()讀取當前節點對應的屬性。就將xml兩個屬性值提取了出來
for (Node node : beans){
Element ele = (Element) node;
String id = ele.attributeValue("id");
String className = ele.attributeValue("class");
}
但是作為這兩個屬性,用於例項化物件。如何對Apple例項化呢?
就可以使用反射技術了。Class.forName()用於載入指定的類,將className傳入,便可以得到與之對應的類物件了。通過newInstance便可以通過預設構造方法建立Apple類的例項。
Class c = Class.forName(className);
Object obj = c.newInstance();
這時候beanId有了物件也有了,下面只需要利用iocContainer中的put方法將id和obj放入其中,就相當於IoC容器對剛才新建立的物件賦予了beanId進行管理
try {
String filePath = this.getClass().getResource("/applicationContext.xml ").getPath();
filePath = new URLDecoder().decode(filePath,"UTF-8");
SAXReader reader = new SAXReader();
Document document = reader.read(new File(filePath));
List<Node> beans = document.getRootElement().selectNodes("bean");
for (Node node : beans){
Element ele = (Element) node;
String id = ele.attributeValue("id");
String className = ele.attributeValue("class");
Class c = Class.forName(className);
Object obj = c.newInstance();
iocContainer.put(id,obj);
}
System.out.println("IoC容器初始化完畢");
}catch (Exception e){
e.printStackTrace();
}
getBean方法,利用iocContainer.get方法對指定的beanId進行提取,並且進行返回就可以了
public Object getBean(String beanId){
return iocContainer.get(beanId);
}
這樣就完成了一個簡單的IoC容器
驗證。建立測試類Application
在Application中呼叫getBean傳入beanId打印出來地址,在ClassPathXmlApplicationContext中列印iocContainer地址。兩者地址相同
接下來在ioc容器中,通過set方法進行屬性注入。還是通過反射來完成
在獲取到物件以後,通過ele.selectNodes得到每一個bean標籤下的每一個property屬性標籤。且同樣使用List<Node>進行儲存
List<Node> properties = ele.selectNodes("property");
這個List集合中就包含了bean中的三個屬性name和value資料。之後再利用for迴圈對屬性進行遍歷。
for (Node p : properties){
Element property = (Element)p;
}
之後提取每一個屬性中的name和value
for (Node p : properties){
Element property = (Element)p;
String propName = property.attributeValue("name");
String propValue = property.attributeValue("value");
}
屬性名字和對應的值獲取到以後,如何在執行時動態注入呢?
基於property這種形式完成動態注入,是通過屬性的set方法完成的。而set方法命名的規則,前面是set,後面增加屬性名,屬性名首字母大寫。只要按照這個規則組織set方法,就可以利用反射進行動態呼叫。
首先獲取set方法名
String setMethodName = "set" + propName.substring(0,1).toUpperCase() + propName.substring(1);
之後通過方法名完成呼叫。反射中的getMethod方法。
// 獲取Method物件
Method setMethod = c.getMethod(setMethodName, String.class);
// 通過set方法注入資料
setMethod.invoke(obj,propValue);
動態注入就完成了。