實現IOC功能的簡單Spring框架

需求分析

設計一個含有IOC的簡單Spring,要求含有對象註冊、對象管理以及暴露給外部的獲取對象功能。java

項目設計

  1. 對於註冊的對象用一個類BeanInfo來描述其信息,包括對象標識、全類名以及屬性名與值的Map。
  2. 對於IOC容器設定一個頂層接口BeanFactory,定義經過對象標識獲取對象示例的方法getBean(String id)AbstractBeanFactory實現該接口,在該類中實現解析生成目標對象,以及獲取對象方法,並在該類中添加註冊器接口,以便能從註冊器中讀取註冊的對象。
  3. 對於註冊器,提供一個頂層接口SourceReader,並在其中添加加載用戶註冊的對象的方法loadBeans(String filePath)。本項目中使用讀取XML的方式,從XML中讀取出註冊的對象,並把它們封裝成BeanInfo放入Map中。
  4. 設定一個上下文XMLContext,繼承AbstractBeanFactory,負責選擇使用哪一種註冊方式,並決定什麼時候加載註冊的對象。

代碼實現

BeanInfo類

package ex1;

import java.util.HashMap;
import java.util.Map;

/**
* 該類用於描述註冊在容器中的對象
*/
public class BeanInfo {
    private String id; //對象ID,名字
    private String type; //全類名
    private Map<String, Object> properties = new HashMap<>(); //屬性名與值的映射集合

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Map<String, Object> getProperties() {
        return properties;
    }

    public void setProperties(Map<String, Object> properties) {
        this.properties = properties;
    }

    public void addProperty(String key,Object value) {
        properties.put(key,value);
    }
}

BeanFactory接口

package ex1;

public interface BeanFactory {
    /**
     * 根據對象的名稱標識來獲取對象實例
     * @param id 對象名稱,即對象描述信息中的對象標識
     * @return 指定名稱的對象實例
     */
    Object getBean(String id);
}

AbstractBeanFactory類

package ex1;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
/**
 * 最頂層的IOC實現
 * 該類負責從註冊器中取出註冊對象
 * 實現從對象描述信息轉換爲對象實例的過程
 * 實現根據名稱獲取對象的方法
 */
public abstract class AbstractBeanFactory implements BeanFactory {
    private String filePath;  //註冊文件路徑
    private Map<String, BeanInfo> container; //註冊對象信息Map(IOC容器)
    protected SourceReader reader; //對象註冊讀取器


    public AbstractBeanFactory(String filePath) {
        this.filePath = filePath;
    }

    /**
     * 抽象方法,需由子類實現,用於指定使用什麼樣的註冊讀取器
     * @param reader 指定的註冊讀取器
     */
    protected abstract void setReader(SourceReader reader);

    //從註冊讀取器中讀取註冊對象的信息Map
    public void registerBeans() {
        this.container = this.reader.loadBeans(this.filePath);
    }

    //實現BeanFactory定義的根據名稱獲取指定對象的方法
    @Override
    public Object getBean(String id) {
        BeanInfo beanInfo = this.container.get(id); //根據對象名稱獲取該對象的描述信息
        if (beanInfo == null) {
            return null;
        } else {
            //根據對象信息,解析並生存指定對象實例,返回給用戶
            return this.parseBean(beanInfo);
        }
    }

    /**
     * 解析並生成對象實例
     * 該方法主要經過反射完成,步驟以下:
     * 1.根據類名,加載指定類,並獲取該類的Class對象clazz
     * 2.使用clazz實例化該類,獲取一個對象,注意,這裏採用無參構造方法
     * 3.逐個設置對象子段的值,這裏採用setter Method方式,而不是直接使用Field對象
     * 4.返回對象實例
     * @param beanInfo 指定對象的描述信息
     * @return
     */
    protected Object parseBean(BeanInfo beanInfo) {
        Class clazz;
        Object bean = null;
        try {
            clazz = Class.forName(beanInfo.getType()); //根據對象的全類名,指定類
            bean = clazz.newInstance(); //使用註冊對象的無參構造函數,實例化對象
            Method[] methods = clazz.getMethods(); //獲取全部公共方法(其實Spring獲取的是全部方法,包括非公有是)
            for (String property : beanInfo.getProperties().keySet()) {
                //首字母大寫
                String name = property.toUpperCase().charAt(0) + property.toLowerCase().substring(1);
                //獲取屬性的setter方法名稱(命名規範)
                String setter = "set" + name;
                for (Method method : methods) {
                    if (method.getName().equals(setter)) {
                        Object value = beanInfo.getProperties().get(property);
                        method.invoke(bean, value); //經過反射對屬性賦值
                        break;
                    }
                }
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return bean;
    }
}

SourceReader接口

package ex1;

import java.util.Map;
/**
 * 註冊讀取器接口
 * 複製讀取用戶註冊的對象
 * 繼承該接口的類能夠實現多種讀取方式,如從配置文件中讀取,根據標註讀取,從網絡中讀取等
 */
public interface SourceReader {
    /**
     * 讀取用戶註冊的對象信息
     * @param filePath 註冊路徑
     * @return 註冊對象信息Map
     */
    Map<String, BeanInfo> loadBeans(String filePath);
}

XMLSourceReader類

這裏實現從xml配置文件中讀取。須要用到dom4j包,用來解析XML文件。本項目中XML文件放置在根目錄下,內容以下:bootstrap

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="Person" class="ex1.Person">
        <property name="name" value="fang"/>
    </bean>
</beans>

實現代碼網絡

package ex1;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
 * XML註冊讀取器
 * 該類繼承了註冊讀取器接口,並模擬實現了讀取註冊對象信息的方法
 */
public class XMLSourceReader implements SourceReader {
    /**
     * 實現讀取註冊對象信息方法
     * 本身編寫經過配置文件讀取的實現
     */
    @Override
    public Map<String, BeanInfo> loadBeans(String filePath) {
        BeanInfo info = new BeanInfo(); //註冊對象的info
        InputStream is = XMLContext.class.getClassLoader().getResourceAsStream(filePath);//獲取xml文件
        SAXReader reader = new SAXReader();
        Map<String,BeanInfo> beanMap = new HashMap<>();
        try {
            Document document = reader.read(is);
            Element root = document.getRootElement(); //獲取根標籤,這裏是beans
            //遍歷全部bean
            for(Iterator iterator = root.elementIterator("bean");iterator.hasNext();){
                Element element = (Element)iterator.next();
                //獲取id和class
                Attribute id = element.attribute("id");
                Attribute clazzName = element.attribute("class");
                info.setId(id.getText());
                info.setType(clazzName.getText());
                //遍歷該bean的property
                for(Iterator it=element.elementIterator("property");it.hasNext();){
                    Element tmp = (Element)it.next();
                    //獲取name和value
                    Attribute name = tmp.attribute("name");
                    Attribute value = tmp.attribute("value");
                    info.addProperty(name.getText(),value.getText());
                }
                beanMap.put(id.getText(),info);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return beanMap;
    }
}

XMLContext類

package ex1;

public class XMLContext extends AbstractBeanFactory {
    /**
     * 上下文的構造方法
     * 該方法中指明註冊讀取器
     * 並在構造該方法時一次性加載註冊的對象
     * @param filePath
     */
    public XMLContext(String filePath) {
        super(filePath);
        this.setReader(new XMLSourceReader());//添加註冊讀取器
        this.registerBeans(); //加載註冊的對象信息
    }

    //設置註冊讀取器
    @Override
    protected void setReader(SourceReader reader) {
        this.reader = reader;
    }
}

測試類

Speakable接口

package ex1;

public interface Speakable {
    void speak(String message);
}

Person類

package ex1;

public class Person implements Speakable {
    private String name;

    public String getName() {
        return name;
    }

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

    @Override
    public void speak(String message) {
        System.out.println(this.name + " say: " + message);
    }
}

Bootstrap類

package ex1;

public class Bootstrap {
    public static void main(String[] args) {
        BeanFactory beanFactory = new XMLContext("bean.xml");
        Speakable speakable = (Speakable) beanFactory.getBean("Person");
        speakable.speak("Experience One!");
    }
}

運行結果

fang say: Experience One!
相關文章
相關標籤/搜索