天天看點

java反射模拟spring依賴注入

首先,這種模拟依賴注入所用的知識是反射。 其次,依賴注入有什麼好處呢?他可以将建立執行個體的工作交給第三方完成,而不用交由調用方完成(這一般依靠配置檔案來實作),這可以降低類與類之間的耦合度。 第三,僅僅将建立執行個體的工作交給第三方完成還不夠。比如A調用B,A中始終包含一個B類的定義(B b),然後B的執行個體化由容器完成,将來如果我們需要用C類的功能替換B的功能,則需要修改A類的引用定義(C c),違反開閉原則。是以,對于一個可擴充、易維護的系統,應該盡量面向接口程式設計,将功能盡量抽象成方法,利用java的多态程式設計。 一般而言,系統更新時,B類和C類的實作功能是一緻的,可能隻是底層接口變動(如mysql資料庫替換成oracle資料庫),或jar包更新,或第三方接口變動,但A類需要的業務功能依然不變。是以系統設計之初,需要定義一個接口,接口中的方法覆寫A類所需的功能。然後B類實作接口I。此時A類調用B類時,可以寫成(I i),然後向上轉型,依靠容器将B的執行個體注入A中,類似:I i = new B();。當以後更新時,我們隻需要定義一個類C實作接口I,然後修改配置檔案,讓C類成為I的引用對象即可。 最後,上面的過程其實就是spring的基本功能,面向接口程式設計,将類之間的執行個體化調用寫在配置檔案applicationContext.xml中,然後所有類的執行個體化由容器完成。

接下來,就開始實作依賴注入: 1.現在又兩類類A、B,A類對B類完成調用,需要在A類中寫:B b = new B(); 我們想像spring一樣隻定義一個B b,然後生成b的get、set方法就能完成B的執行個體化,那麼需要一個方法,傳入A類本身,然後獲得以set開頭的方法,并調用此方法,完成,傳入一個new B()執行個體,完成B的指派。方法如下: 

public static void diObj(Object obj) {
		try {
			// 擷取本類自定義的所有方法
			Method[] ms = obj.getClass().getDeclaredMethods();
			for (Method m : ms) {
				String mn = m.getName();
				// 如果方法名稱以set開頭
				if (mn.startsWith("set")) {
					// 截斷方法名,擷取properties中配置的dao名稱
					mn = mn.substring(3);
					// 将首字母小寫
					mn = mn.substring(0, 1).toLowerCase() + mn.substring(1);
					// 根據peoperties中配置的名稱字元串
					String fullName = PropertiesUtil.getProperty(mn);
					// 通過字元串生成類執行個體
					Object instance = Class.forName(fullName);
					// 調用setXXX()方法完成執行個體的注入
					m.invoke(obj, instance);
				}
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}
           

說明:因為通過字元串生成執行個體(Class.forName(name)),需要的name參數是類的全路徑,是以我們需要将此全路徑配置在properties檔案中。并通過properties檔案操作工具擷取。(spring是寫在xml中) 2.上面方法有了,如何注入呢?那就需要在每個需要注入的類中調用一下此方法,并将類本身傳入。如在A中調用:diObj(this); 3.這種方法有一個問題,就是如果A類中還有其他的set方法,但是我們并不需要注入,就可能抛出異常。是以,我們需要在要自定義一個Annotation類,這個Annotation類标簽,放在屬性前,或放在set方法前都行(下面的栗子,放在set方法前),反正最終要實作的功能都一樣:有Annotation的我們才注入。 1)定義一個Annotation,如下:

<span style="text-indent: 24px; white-space: pre; background-color: rgb(240, 240, 240);">package com.edu.mybatis.common.annotation;</span>
           
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface DaoDi {
	// String abc() default "";
	String value() default "";
}
           

2)修改diObj(Object obj)公共方法:

</pre><pre name="code" class="java">public static void diObj(Object obj) {
		try {
			// 擷取本類自定義的所有方法
			Method[] ms = obj.getClass().getDeclaredMethods();
			for (Method m : ms) {
				String mn = m.getName();
				// 如果此方法上包含@DaoDi标簽,傳回true
				if (m.isAnnotationPresent(DaoDi.class)) {
					DaoDi daoDi = m.getAnnotation(DaoDi.class);
					// 擷取DaoDi的value值
					String value = daoDi.value();
					// 如果annotation中傳入參數(如:@DaoDi("userDao")),則表示注入userDao
					if (value != null && !"".equals(value)) {
						mn = value;
					}
					// 如果annotation不傳參數(如:@DaoDi),則表示将setXXX方法的XXX名稱最為注入對象
					else {
						// 截斷方法名,擷取properties中配置的dao名稱
						mn = mn.substring(3);
						// 将首字母小寫
						mn = mn.substring(0, 1).toLowerCase() + mn.substring(1);
					}
					// 根據peoperties中配置的名稱字元串
					String fullName = PropertiesUtil.getProperty(mn);
					// 通過字元串生成類執行個體
					Object instance = Class.forName(fullName);
					// 調用setXXX()方法完成dao注入
					m.invoke(obj, dao);
				}
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}
           

3)在setXXX方法上加上Annotation,如:

@DaoDi("addressDao")
    public void setAddrDao(IAddressDao addressDao) {
         this.addressDao = addressDao;
    }
           

三部完成,代碼可以正常工作了。 4.有人可能會覺得這種注入,需要在每個類中初始化時都寫上一句diObj(this)是一件非常無趣的事情。那麼我們就需要根據具體業務具體分析了。如果是一個基本B/S項目,我們可能會分層,比如dao層、service層、action層等。以dao層為例: 我們本來就需要為dao抽象出一些公共的操作,并封裝成一個dao的父類(這樣做是必要的,以後更改dao接口也更友善,更靈活)。當業務中需要定義一個dao 類時,我們就繼承此dao父類。此時機會就出現了,我們将注入語句寫在dao父類的構造方法中。那麼凡是在dao需要注入的屬性,在繼承dao父類後,都會通過 父類的構造方法完成。 而至于service層、action層也一樣,我們需要為該層定義一個父類,并在構造方法中調用注入語句。