天天看點

手寫Spring之IOC基于xml動态建立對象

 Spring作為Java Web最為流行的架構之一,其功能之強大,封裝細節之全面不用過多贅述。使用Spring的方式很簡單,不需要關注細節,把對象的建立和對象之間的關系都交給架構來管理,僅僅做好配置檔案和實作具體的業務邏輯即可。可以說Spring為我們在編寫Java Web應用時省去了大量重複的代碼,并且可以降低對象與對象之間的耦合度。但若隻是知其然,而不知其是以然,在程式設計時也難免會遇到各種問題,個人的水準也難以有所長進。

 是以這篇文章的目的是分享本人對于SpringIOC如何實作控制反轉,以及如何在運作過程中動态建立對象的了解,算是在漫長的學習過程中的一個小小的标的。廢話不多說,直接上幹貨!

 在手寫Spring容器之前,需要做一些前期的準備工作:

 首先是建立項目,在這裡我為了後期下載下傳jar包友善,建立的是maven工程,是在JDK1.7的環境下。當然你也可以建立普通的Java工程,在需要使用第三方的jar包時手動導入。

手寫Spring之IOC基于xml動态建立對象

 在pom.xml檔案中添加jar包依賴路徑,下載下傳所需要的第三方API,本次需要使用dom4j去解析xml配置檔案。

<dependencies>
  	<dependency>
	  <groupId>dom4j</groupId>
	  <artifactId>dom4j</artifactId>
	  <version>1.6.1</version>
	</dependency>
</dependencies>
           

 建立xml配置檔案,為了後面使用友善,在這裡我起名為:user.xml,放在根目錄下:

手寫Spring之IOC基于xml動态建立對象

 配置檔案内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans> 
    <bean id="user1" class="entity.User" scope="singleton">
        <property name="id" value="1"></property>
        <property name="name" value="張三"></property>
        <property name="password" value="12456"></property>
    </bean>
    <bean id="user2" class="entity.User" scope="prototype">
        <property name="id" value="2"></property>
        <property name="name" value="李四"></property>
        <property name="password" value="654321"></property>
    </bean>
</beans>
           

 然後根據xml配置檔案中的class路徑建立對應的POJO實體類:User

手寫Spring之IOC基于xml動态建立對象

 User類中内容如下(為了節省篇幅,省略setter和getter方法):

public class User {
	private Integer id;
	private String name;
	private String password;
	public User() {
		System.out.println("無參構造方法執行");
	}
	//setters和getters...
}
           

 主角登場...

 建立ClassPathXmlApplicationContext類:

手寫Spring之IOC基于xml動态建立對象

 先定義幾個後面要用到的容器,這裡我使用的是Map來存儲對象:

package applicationContext;

public class ClassPathXmlApplicationContext {
	/**存儲單例對象容器*/
	private Map<String, Object> singletonBeanFactory;
	/**存儲建立類定義對象的容器*/
	private Map<String, Class<?>> beanDefinationFactory;
	/**存儲beanElement對象容器*/
	private Map<String, Element> beanEleMap;
	/**存儲bean的scope屬性容器*/
	private Map<String, String> beanScopeMap;
}
           

 定義有參的構造方法,在構造方法中初始化容器,并調用初始化方法:

/**有參的構造方法,在建立此類執行個體時需要指定xml檔案路徑*/
public ClassPathXmlApplicationContext(String xmlPath) {
	//初始化容器
	singletonBeanFactory = new ConcurrentHashMap<String, Object>();
	beanDefinationFactory = new ConcurrentHashMap<String, Class<?>>();
	beanEleMap = new ConcurrentHashMap<String, Element>();
	beanScopeMap = new ConcurrentHashMap<String, String>();
	//調用初始化方法
	init(xmlPath);
}
           

 init初始化方法内容如下,每一行我都加了詳細的注釋,請直接看代碼:

/**
 * 初始化方法,在建立ClassPathXmlApplicationContext對象時初始化容器,
 * 并解析xml配置檔案,擷取bean元素,在運作時動态建立對象,并為對象的屬性指派,
 * 最後把對象存放在容器中以供擷取
 * @param xmlPath 配置檔案路徑
 */
private void init(String xmlPath) {
	/*
	 * 使用dom4j技術讀取xml文檔
	 * 首先建立SAXReader對象
	 */
	SAXReader reader = new SAXReader();
	try {
		//擷取讀取xml配置檔案的輸入流
		InputStream is = getClass().getClassLoader().getResourceAsStream(xmlPath);
		//讀取xml,該操作會傳回一個Document對象
		Document document = reader.read(is);
		//擷取文檔的根元素
		Element rootElement = document.getRootElement();
		//擷取根元素下所有的bean元素,elements方法會傳回元素的集合
		List<Element> beanElements = rootElement.elements("bean");
		//周遊元素集合
		for (Element beanEle : beanElements) {
			//擷取bean的id值,該值用于作為key存儲于Map集合中
			String beanId = beanEle.attributeValue("id");
			//将beanElement對象存入map中,為對象設定屬性值時使用
			beanEleMap.put(beanId, beanEle);
			//擷取bean的scope值
			String beanScope = beanEle.attributeValue("scope");
			//如果beanScope不等于null,将bean的scope值存入map中友善後續使用
			if(beanScope!=null){
				beanScopeMap.put(beanId, beanScope);
			}
			//擷取bean的class路徑
			String beanClassPath = beanEle.attributeValue("class");
			//利用反射技術根據獲得的beanClass路徑得到類定義對象
			Class<?> cls = Class.forName(beanClassPath);
			//如果反射擷取的類定義對象不為null,則放入工廠中友善建立其執行個體對象
			if(cls!=null){
				beanDefinationFactory.put(beanId, cls);
			}
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}
           

 以上為建立ClassPathXmlApplicationContext對象時自動啟用的初始化方法,要想擷取對象則需要使用getBean方法,代碼如下:

/**
 * 根據傳入的bean的id值擷取容器中的對象,類型為Object
 */
public Object getBean(String beanId){
	//根據傳入beanId擷取類對象
	Class<?> cls = beanDefinationFactory.get(beanId);
	//根據id擷取該bean對象的element元素對象
	Element beanEle = beanEleMap.get(beanId);
	//擷取存在map中的bean元素的scope屬性值
	String scope = beanScopeMap.get(beanId);
	Object obj = null;
	try {
		//如果scope等于singleton,建立單例對象
		if("singleton".equals(scope) || null == scope){
			//判斷容器中是否已有該對象的執行個體,如果沒有,建立一個執行個體對象放到容器中
			if(singletonBeanFactory.get(beanId)==null){
				Object instance = cls.newInstance();
				singletonBeanFactory.put(beanId,instance);
			}
			//根據beanId擷取對象
			obj = singletonBeanFactory.get(beanId);
		}
		//如果scope等于prototype,則建立并傳回多例對象
		if("prototype".equals(scope)){
			obj = cls.newInstance();
		}
		setFieldValues(beanId, beanEle, scope, cls, obj);
		return obj;
	} catch (InstantiationException e) {
		e.printStackTrace();
	} catch (IllegalAccessException e) {
		e.printStackTrace();
	}
	//暫不支援其它類型,若不是以上兩種類型或遭遇異常,傳回null
	return null;
}
/**
 * 此為重載方法,在根據傳入的bean的id值擷取容器中的對象的同時,還可以自動轉換類型,
 * 傳回指定的類型,在調用該方法時省去強轉的步驟,傳入時第二個參數為指定的類型,
 * 方法實作同上一個方法,隻是在傳回對象前加了類型強轉
 */
public <T>T getBean(String beanId,Class<T> c){
	return (T)getBean(beanId);
	
}
           

 在以上的getBean方法中,調用了setFieldValues方法,該方法代碼如下:

/**
 * 該方法用于為對象設定成員屬性值
 * @param beanEle bean所對應的element對象
 * @param beanId bean元素的id屬性
 * @param beanScope bean元素的scope屬性
 * @param cls 類對象
 * @param obj 要為其成員屬性指派的執行個體對象
 */
private void setFieldValues(String beanId,Element beanEle,String beanScope,Class<?> cls,Object obj) {
	try {
		//擷取每個bean元素下的所有property元素,該元素用于給屬性指派
		List<Element> propEles = beanEle.elements("property");
		//如果property元素集合為null,調用putInMap方法将對象放進Map中
		if(propEles==null){
			return;
		}
		//周遊property元素集合
		for (Element propEle : propEles) {
			//擷取每個元素的name屬性值和value屬性值
			String fieldName = propEle.attributeValue("name");
			String fieldValue = propEle.attributeValue("value");
			//利用反射技術根據name屬性值獲得類的成員屬性
			Field field = cls.getDeclaredField(fieldName);
			//将該屬性設定為可通路(防止成員屬性被私有化導緻通路失敗)
			field.setAccessible(true);
			//擷取成員屬性的類型名稱,若非字元串類型,則需要做相應轉換
			String fieldTypeName = field.getType().getName();
			//判斷該成員屬性是否為int或Integer類型
			if("int".equals(fieldTypeName) || "java.lang.Integer".equals(fieldTypeName)){
				//轉換為int類型并為該成員屬性指派
				int intFieldValue = Integer.parseInt(fieldValue);
				field.set(obj, intFieldValue);
			}
			//判斷該成員屬性是否為String類型
			if("java.lang.String".equals(fieldTypeName)){
				//為該成員屬性指派
				field.set(obj, fieldValue);
			}
			//此處省略其它類型的判斷......道理相同!
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}
           

 以上是擷取單例或多例對象時需要調用的getBean方法的全部内容。當調用者使用完容器之後,自然還需要關閉容器釋放資源,是以還需要有一個destroy方法:

/**
 * 銷毀方法,用于釋放資源
 */
public void destroy(){
	singletonBeanFactory.clear();
	singletonBeanFactory = null;
	
	beanDefinationFactory.clear();
	beanDefinationFactory = null;
	
	beanEleMap.clear();
	beanEleMap = null;
	
	beanScopeMap.clear();
	beanScopeMap = null;
}
           

 至此,ClassPathXmlApplicationContext類中的内容全部完成,可以寫測試類進行測試:

手寫Spring之IOC基于xml動态建立對象

 測試類内容如下,這裡我就簡單寫main方法進行測試:

public class springIocTest {
	public static void main(String[] args) {
		//建立ClassPathXmlApplicationContext對象
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("user.xml");
		//使用手動強轉的方式擷取單例的User對象
		User user1_1 = (User) ctx.getBean("user1");
		System.out.println("單例user1_1:"+user1_1);
		//使用傳入類對象的方式擷取單例的User對象
		User user1_2 = ctx.getBean("user1",User.class);
		System.out.println("單例user1_2:"+user1_2);
		//使用手動強轉的方式擷取多例的User對象
		User user2_1 = (User)ctx.getBean("user2");
		System.out.println("多例user2_1:"+user2_1);
		//使用傳入類對象的方式擷取多例的User對象
		User user2_2 = ctx.getBean("user2",User.class);
		System.out.println("多例user2_2:"+user2_2);
	}
}
           

 控制台列印輸出結果:

手寫Spring之IOC基于xml動态建立對象

 從控制台輸出的結果中可以看到,擷取到了4個對象,其中前兩個為單例對象,後兩個為多例對象,兩個單例對象在預設調用Object類中的toString方法是其位址值的hashCode十六進制的映射,其映射值完全一緻,可以說明是同一個對象。而且建立了4個對象,其無參的構造方法隻執行了三次。

 如果在User類型加入toString方法:

@Override
public String toString() {
	return "User [id=" + id + ", name=" + name + ", password=" + password + "]";
}
           

 再次運作程式,控制台輸出結果如下: 

手寫Spring之IOC基于xml動态建立對象

 可以看到對象所定義的屬性值也在建立時成功指派了。

 以上是我近期學習Spring所總結的 内容,關于建立多例對象的源碼其實我也沒有找到,目前所寫的隻是基于我的思路寫出來的方案,與大家一起分享。由于個人水準有限,難免會有寫錯或者遺漏的地方,甚至可能會有以偏概全。但這并不重要,正如開篇所說,這隻是我學習Java在程式設計成長路上的一個小小的标的。如果有大牛看到,歡迎留言指正,不勝感激~