1. 程式人生 > 實用技巧 >實現極簡IoC容器

實現極簡IoC容器

建立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; } }

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檔案
<?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>
建立ApplicationContext介面,接口裡建立getBean抽象類
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);
動態注入就完成了。

附件列表