深入理解Spring--動手實現一個簡單的SpringIOC容器

   主要思想:

   提到IOC,第一反應就是控制反轉,我以前以爲SpringIOC就是控制反轉,控制反轉就是SpringIOC,當然這種理解是錯誤的,控制反轉是一種思想,一種模式,而Spring的IOC容器是實現了這種思想這種模式的一個載體.

    使用過Spring的人都熟知,SpringIOC容器可以在對象生成或初始化時就直接將數據注入到對象中,如果對象A的屬性是另一個對象B,還可以將這個對象B的引用注入到注入到A的數據域中.

    如果在初始化對象A的時候,對象B還沒有進行初始化,而A又需要對象B作爲自己的屬性,那麼就會用一種遞歸的方式進行注入,這樣就可以把對象的依賴關係清晰有序的建立起來.

    IOC容器解決問題的核心就是把創建和管理對象的控制權從具體的業務對象手中搶過來.由IOC容器來管理對象之間的依賴關係,並由IOC容器完成對象的注入.這樣就把應用從複雜的對象依賴關係的管理中解放出來,簡化了程序的開發過程.

  下圖是這個簡單IOC容器的類圖(原諒我真沒學過UML,湊合看吧):

     

  •  程序中所有的Bean之間的依賴關係我們是放在一個xml文件中進行維護的,就是applicationContext.xml  
  •  ConfigManager類完成的功能是讀取xml,並將所有讀取到有用的信息封裝到我們創建的一個Map<String,Bean>集合中,用來在初始化容器時創建bean對象.
  •  定義一個BeanFactory的接口,接口中有一個getBean(String name)方法,用來返回你想要創建的那個對象.
  •  然後定義一個該接口的實現類ClassPathXmlApplicationContext.就是在這個類的構造方法中,初始化容器,通過調用ConfigManager的方法返回的Map集合,通過反射和內省一一創建bean對象.這裏需要注意,對象的創建有兩個時間點,這取決與bean標籤中scope屬性的值:
    •  如果scope="singleton",那麼對象在容器初始化時就已創建好,用的時候只需要去容器中取即可.
    •  如果scope="prototype",那麼容器中不保存這個bean的實例對象,每次開發者需要使用這個對象時再進行創建.

  使用的主要知識點:

      •     dom4j解析xml文件

      •     xpath表達式(用於解析xml中的標籤)

      •     java反射機制

      •     內省(獲取Bean屬性的set方法進行賦值)

 

  項目結構圖及介紹如下:

          

  項目需要的jar包與項目結構已經在上圖中介紹了,這個項目所能實現的功能如下:

    1.  IOC容器能管理對象的創建以及對象之間的依賴關係.
    2.  能夠實現數據的自動類型轉換(藉助BeanUtils).
    3.  能夠實現scope="singleton"和scope="prototype"的功能,即能夠控制對象是否爲單例.  

下面介紹代碼部分:

application.xml:

複製代碼
<?xml version="1.0" encoding="utf-8"?>
<beans>
    <bean name="student" class="com.wang.entity.Student" >
        <property name="name" value="123"></property>
    </bean>
    
    <bean name="teacher" class="com.wang.entity.Teacher">
        <property name="student" ref="student"></property>
    </bean>
    <bean name="person" class="com.wang.entity.Person" scope="prototype">
        <property name="teacher" ref="teacher"></property>
        <property name="student" ref="student"></property>
    </bean>
    
</beans>
複製代碼

實體類Student,Teacher,Person:

複製代碼
package com.wang.entity;
//Student類
public class Student {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
/************************************/
package com.wang.entity;
//Teacher類
public class Teacher {

    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }
     
}
/************************************/
package com.wang.entity;
//Person類
public class Person {

    private Student student;
    private Teacher teacher;
    
    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
}
複製代碼

用於封裝Bean標籤信息的Bean類:

複製代碼
package com.wang.config;

import java.util.ArrayList;
import java.util.List;

public class Bean {

    
    private String name;
    private String className;
    private String scope="singleton";
    private List<Property> properties=new ArrayList<Property>();

    
    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public List<Property> getProperties() {
        return properties;
    }

    public void setProperties(List<Property> properties) {
        this.properties = properties;
    }

    
}
複製代碼

用與封裝Bean子標籤property內容的Property類:

複製代碼
package com.wang.config;

public class Property {

    private String name;
    private String value;
    private String ref;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
    public String getRef() {
        return ref;
    }
    public void setRef(String ref) {
        this.ref = ref;
    }

    
}
複製代碼

ConfigManager類:

複製代碼
package com.wang.config.parse;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Test;

import com.wang.config.Bean;
import com.wang.config.Property;

public class ConfigManager {
    
    private static Map<String,Bean> map=new HashMap<String,Bean>(); 

    //讀取配置文件並返回讀取結果
    //返回Map集合便於注入,key是每個Bean的name屬性,value是對應的那個Bean對象
    public static Map<String, Bean> getConfig(String path){
        /*dom4j實現
         *  1.創建解析器
         *  2.加載配置文件,得到document對象
         *  3.定義xpath表達式,取出所有Bean元素
         *  4.對Bean元素繼續遍歷
         *      4.1將Bean元素的name/class屬性封裝到bean類屬性中
         *      4.2獲得bean下的所有property子元素
         *      4.3將屬性name/value/ref分裝到類Property類中
         *  5.將property對象封裝到bean對象中
         *  6.將bean對象封裝到Map集合中,返回map 
            */
        //1.創建解析器
        SAXReader reader=new SAXReader();
        //2.加載配置文件,得到document對象
        InputStream is = ConfigManager.class.getResourceAsStream(path);
        Document doc =null;
        try {
             doc = reader.read(is);
        } catch (DocumentException e) {
            e.printStackTrace();
            throw new RuntimeException("請檢查您的xml配置是否正確");
        }
        // 3.定義xpath表達式,取出所有Bean元素
        String xpath="//bean";
        
        //4.對Bean元素繼續遍歷
        List<Element> list = doc.selectNodes(xpath);
        if(list!=null){
            //4.1將Bean元素的name/class屬性封裝到bean類屬性中
        
             // 4.3將屬性name/value/ref分裝到類Property類中
            for (Element bean : list) {
                Bean b=new Bean();
                String name=bean.attributeValue("name");
                String clazz=bean.attributeValue("class");
                String scope=bean.attributeValue("scope");
                b.setName(name);
                b.setClassName(clazz);
                if(scope!=null){
                    b.setScope(scope);
                }
                 //  4.2獲得bean下的所有property子元素
                List<Element> children = bean.elements("property");
                
                 // 4.3將屬性name/value/ref分裝到類Property類中
                if(children!=null){
                    for (Element child : children) {
                        Property prop=new Property();
                        String pName=child.attributeValue("name"); 
                        String pValue=child.attributeValue("value");
                        String pRef=child.attributeValue("ref");
                        prop.setName(pName);
                        prop.setRef(pRef);
                        prop.setValue(pValue);
                        // 5.將property對象封裝到bean對象中
                        b.getProperties().add(prop);
                    }
                }
                //6.將bean對象封裝到Map集合中,返回map 
                map.put(name, b);
            }
        }
        
        return map;
    }

}
複製代碼

BeanFactory接口:

複製代碼
package com.wang.main;

public interface BeanFactory {
    //核心方法getBean
    Object getBean(String name);
}
複製代碼

ClassPathXmlApplicationContext類:

複製代碼
package com.wang.main;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.beanutils.BeanUtils;
import org.junit.Test;

import com.wang.config.Bean;
import com.wang.config.Property;
import com.wang.config.parse.ConfigManager;
import com.wang.entity.Student;
//import com.wang.utils.BeanUtils;
import com.wang.utils.BeanUtil;

public class ClassPathXmlApplicationContext implements BeanFactory {

    // 獲得讀取的配置文件中的Map信息
    private Map<String, Bean> map;
    // 作爲IOC容器使用,放置sring放置的對象
    private Map<String, Object> context = new HashMap<String, Object>();

    public ClassPathXmlApplicationContext(String path) {
        // 1.讀取配置文件得到需要初始化的Bean信息
        map = ConfigManager.getConfig(path);
        // 2.遍歷配置,初始化Bean
        for (Entry<String, Bean> en : map.entrySet()) {
            String beanName = en.getKey();
            Bean bean = en.getValue();

            Object existBean = context.get(beanName);
            // 當容器中爲空並且bean的scope屬性爲singleton時
            if (existBean == null && bean.getScope().equals("singleton")) {
                // 根據字符串創建Bean對象
                Object beanObj = createBean(bean);

                // 把創建好的bean對象放置到map中去
                context.put(beanName, beanObj);
            }
        }

    }

    // 通過反射創建對象
    private Object createBean(Bean bean) {
        // 創建該類對象
        Class clazz = null;
        try {
            clazz = Class.forName(bean.getClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("沒有找到該類" + bean.getClassName());
        }
        Object beanObj = null;
        try {
            beanObj = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("沒有提供無參構造器");
        }
        // 獲得bean的屬性,將其注入
        if (bean.getProperties() != null) {
            for (Property prop : bean.getProperties()) {
                // 注入分兩種情況
                // 獲得要注入的屬性名稱
                String name = prop.getName();
                String value = prop.getValue();
                String ref = prop.getRef();
                // 使用BeanUtils工具類完成屬性注入,可以自動完成類型轉換
                // 如果value不爲null,說明有
                if (value != null) {
                    Map<String, String[]> parmMap = new HashMap<String, String[]>();
                    parmMap.put(name, new String[] { value });
                    try {
                        BeanUtils.populate(beanObj, parmMap);
                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException("請檢查你的" + name + "屬性");
                    }
                }

                if (ref != null) {
                    // 根據屬性名獲得一個注入屬性對應的set方法
                    // Method setMethod = BeanUtil.getWriteMethod(beanObj,
                    // name);

                    // 看一看當前IOC容器中是否已存在該bean,有的話直接設置沒有的話使用遞歸,創建該bean對象
                    Object existBean = context.get(prop.getRef());
                    if (existBean == null) {
                        // 遞歸的創建一個bean
                        existBean = createBean(map.get(prop.getRef()));
                        // 放置到context容器中
                        // 只有當scope="singleton"時才往容器中放
                        if (map.get(prop.getRef()).getScope()
                                .equals("singleton")) {
                            context.put(prop.getRef(), existBean);
                        }
                    }
                    try {
                        // setMethod.invoke(beanObj, existBean);
              //通過BeanUtils爲beanObj設置屬性
BeanUtils.setProperty(beanObj, name, existBean); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("您的bean的屬性" + name + "沒有對應的set方法"); } } } } return beanObj; } @Override public Object getBean(String name) { Object bean = context.get(name); // 如果爲空說明scope不是singleton,那麼容器中是沒有的,這裏現場創建 if (bean == null) { bean = createBean(map.get(name)); } return bean; } }
複製代碼

最後就是一個測試類TestBean:

複製代碼
package com.wang.main;

import org.junit.Test;

import com.wang.entity.Person;
import com.wang.entity.Student;
import com.wang.entity.Teacher;


public class TestBean {

    @Test
    public void func1(){
        
        BeanFactory bf=new ClassPathXmlApplicationContext("/applicationContext.xml");
        Person s=(Person)bf.getBean("person");
        Person s1=(Person)bf.getBean("person");
        System.out.println(s==s1);
        System.out.println(s1);
        Student stu1=(Student) bf.getBean("student");
        Student stu2=(Student) bf.getBean("student");
        String name=stu1.getName();
        System.out.println(name);
        System.out.println(stu1==stu2);
    }
}
複製代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章