第13天,昨天講到了代理模式的實現方式,本章會講解兩個重點,利用Java動態載入類別,以及實作簡單的IOC元件,實現不使用 import 的情況下取得依賴元件
Java 的原生類別 Class提供了透過使用類別路徑,載入類別的方式Class.forName("完整類別路徑"),話不多說上程式碼示範使用方式
案例一:先測試載入不存在的類別
try {
// 透過 Class類別加載類別
Class<?> clazz = Class.forName("com.example.NoExistsClass");
Constructor<?> constructor = clazz.getConstructor(); // 无参构造函数
// 執行建構子取得實例
Object instance = constructor.newInstance();
} catch (Exception ex) {
System.out.println(ex.getClass());
System.out.println(ex.getMessage());
}
運行後會打印出找不到類別的錯誤
案例二:載入存在的類別
建立新類別 Sample.java
public class Sample {
private String name = "Default Value";
public void printName() {
System.out.print(name);
}
public String getName() {
return name;
}
}
try {
// 透過 Class類別加載類別,類別路徑可依實際環境調整
Class clazz = Class.forName("app.other.Sample");
//用原生Class類別取得建構子方法
Constructor constructor = clazz.getConstructor();
// 執行建構子取得實例
Object instance = constructor.newInstance();
System.out.printf("Instance Class %s", instance.getClass());
} catch (Exception ex) {
System.out.println(ex.getClass());
System.out.println(ex.getMessage());
}
執行 Main.java 打印出「Instance Class class app.other.Sample」字符串,上面的範例演示了,在程式執行時載入類別的方式,可以將要載入的類別路徑套用到Class.forName方法中,並透過 Constructor類別取得建構器,在執行newInstance方法在運行時建構類別實例,接著將程式碼封裝成一個方法,修改後的結果如下
在 Main.java定義靜態方法 classFactory
static Object classFactory(String classPath) {
try {
// 透過 Class類別加載類別
Class clazz = Class.forName(classPath); Constructor constructor = clazz.getConstructor(); // 无参构造函数
// 執行建構子取得實例
return constructor.newInstance();
} catch (Exception ex) {
System.out.println(ex.getClass());
System.out.println(ex.getMessage());
}
}
將類別載入的程式碼封裝好了以後,在建立一個IOC類別,來實作一個簡易版本的IOC容器,
class IoCBox {
private Map<String, Object> instances = new HashMap<>();
// 新增 key
public void put(String key, Object instance) {
instances.put(key, instance);
}
// 取得實例
public Object get(String key) {
Object instance = instances.get(key);
if (instance == null) {
instance = classFactory(key);
this.put(key, instance);
}
return instance;
}
// 移除容納的實例
public void remove(String classPath) {
instances.remove(classPath);
}
}
在 Main方法調用 IoCBox
public static void main(String[] args) {
IoCBox ioc = new IoCBox();
Object obj = ioc.get("app.other.Sample");
System.out.printf("IoC Container get instance %s", obj.getClass());
}
透過封裝好的IOC容器,可以將使用到的元件交由IoCBox保管,需要調用封裝好的方法,輸入類別路徑透過容器動態取得需要使用的實例,目前只有實現取得實例的業務邏輯,為了完美詮釋依賴性注入原則,還需要一些修改
調整get方法實作邏輯,注意為了方便展示利用IOC修改屬性的實作,此範例針對 Sample的name屬性修改新數值
public Object get(String key) {
Object instance = instances.get(key);
if (instance == null) {
instance = classFactory(key);
// 取得實例內的所有屬性(含私有屬性)
Field[] fields = instance.getClass().getDeclaredFields();
for (Field field : fields) {
try {
// 設定開放修改私有類型
field.setAccessible(true);
field.set(instance, "NewValue");
} catch (Exception ex) {
System.out.printf("Change Field Error %s", ex.getMessage());
}
}
this.put(key, instance);
}
return instance;
}
調整 main方法實作
public static void main(String[] args) {
IocContainer ioc = new IocContainer();
Sample obj1 = new Sample();
System.out.printf("Fist %s\n", obj1.getName());
Sample obj2 = (Sample) ioc.get("app.other.Sample");
System.out.printf("Using Ioc %s\n", obj2.getName());
}
執行程式運行 obj1.getName 那行打印出 Default Value
接著在obje2.getName那行可以發現,Sample的name值在IOC取得實例時,修改了私有屬性,因此實際打印會是不同值