最近在研讀Hive社區版本的源碼,發現其中多處用到了Java依賴注入,這里簡單總結一下依賴注入的幾種實現方法。在談java依賴注入之前,有必要先回顧一下設計模式中的Proxy模式。
其實每個模式名稱就表明了該模式的作用,代理模式就是多一個代理類出來,替原對象進行一些操作,比如我們在租房子的時候回去找中介,為什么呢?因為你對該地區房屋的信息掌握的不夠全面,希望找一個更熟悉的人去幫你做,此處的代理就是這個意思。再如我們有的時候打官司,我們需要請律師,因為律師在法律方面有專長,可以替我們進行操作,表達我們的想法。先來看看關系圖:
根據上文的闡述,代理模式就比較容易的理解了,我們看下代碼:
public interface Sourceable {
public void method();
}
@Override
public void method() {
System.out.println("the original method!");
}
public class Proxy implements Sourceable {
private Source source;
public Proxy(){
super();
this.source = new Source();
}
@Override
public void method() {
before();
source.method();
atfer();
}
private void atfer() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
}
測試類
public class ProxyTest {
public static void main(String[] args) {
Sourceable source = new Proxy();
source.method();
}
}
輸出:
before proxy!
the original method!
after proxy!
代理模式的應用場景:
如果已有的方法在使用的時候需要對原有的方法進行改進,此時有兩種辦法:
1、修改原有的方法來適應。這樣違反了“對擴展開放,對修改關閉”的原則。
2、就是采用一個代理類調用原有的方法,且對產生的結果進行控制。這種方法就是代理模式。
使用代理模式,可以將功能劃分的更加清晰,有助于后期維護!
下面我們要說的Java依賴注入可以說就是proxy模式的一種應用,這里總結一下常用的三種依賴注入方法
1. 接口注入
我們常常借助接口來將調用者與實現者分離。如:
public class ClassA {
private InterfaceB clzB;
public init() {
Ojbect obj =
Class.forName(Config.BImplementation).newInstance();
clzB = (InterfaceB)obj;
}
……
}
上面的代碼中,ClassA依賴于InterfaceB 的實現,如何獲得InterfaceB 實現類的實例?傳統的方法是在代碼中創建InterfaceB 實現類的實例,并將起賦予clzB。而這樣一來,ClassA在編譯期即依賴于InterfaceB 的實現。為了將調用者與實現者在編譯期分離,于是有了上面的代碼,我們根據預先在配置文件中設定的實現類的類名,動態加載實現類,并通過InterfaceB 強制轉型后為ClassA所用。
這就是接口注入的一個最原始的雛形。而對于一個接口注入型IOC容器而言,加載接口實現并創建其實例的工作由容器完成,如J2EE開發中常用的Context.lookup(ServletContext.getXXX),都是接口注入型IOC的表現形式。
Apache Avalon是一個典型的Type1型IOC容器。
2. 構造子注入
構造子注入,即通過構造函數完成依賴關系的設定,如:
public class DIByConstructor {
private final DataSource dataSource;
private final String message;
public DIByConstructor(DataSource ds, String msg) {
this.dataSource = ds;
this.message = msg;
}
……
}
可以看到,在Type2類型的依賴注入機制中,依賴關系是通過類構造函數建立,容器通過調用類的構
造方法,將其所需的依賴關系注入其中。
PicoContainer(另一種實現了依賴注入模式的輕量級容器)首先實現了Type2類型的依賴注入模式。
3. 設置注入
這種方式廣泛應用在Spring框架的參數配置中,客戶代碼僅僅面向接口編程,而無需知道實現類的具體名稱。同時,我們可以很簡單的通過修改配置文件來切換具體的底層實現類。下面我們拋開Spring框架本身,使用一個簡單的例子來實現類似Spring的依賴注入
public class ActionFactory {
public static Action getAction(String actionName) {
Properties pro = new Properties();
try {
pro.load(new FileInputStream("config.properties"));
String actionImplName = (String) pro.get(actionName);
String actionMessage = (String) pro.get(actionName + "_msg");
Object obj = Class.forName(actionImplName).newInstance();
// BeanUtils是Apache Commons BeanUtils提供的輔助類
BeanUtils.setProperty(obj, "message", actionMessage);
return (Action) obj;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
配置文件則采用最簡單的properties文件形式:
TheAction=net.xiaxin.spring.qs.UpperAction
TheAction_msg=HeLLo
測試類:
public void testFactory(){
Action action = ActionFactory.getAction("TheAction");
System.out.println(action.execute("Rod Johnson"));
}
上面的例子中,我們通過設置注入,在運行期動態將字符串“HeLLo” 注入到Action實現類的Message屬性中。用同樣的方法,我們可以通過修改配置文件讓程序調用不同的實現方法,從而實現靈活的依賴注入。在Hive源碼中,HiveDriverRunHook類也是使用這種方式實現注入的