標籤:
程式設計語言中的反射(Refection)指的是可以在程式運行期動態載入一個類。與之相關的是自省(Introspection),這個指的是程式自己可以擷取一個類型的描述資訊,例如擷取一個類的所有介面定義、一個介面的所有形參。當程式設計語言有了這些語言特性之後,可以在很大程度上解決代碼耦合問題,所以在Java的世界裡,可以看到很多庫/架構使用了反射技術。
類似Spring的Bean容器實現就是大量運用了反射機制。Bean容器維護了一些Bean對象,簡單來說就是一些普通對象。Bean容器可以根據配置建立這些對象,建立時如果這些對象依賴了其他對象,Bean容器還會負責將依賴的對象注入到目標對象中,也就是所謂的依賴注入(dependence injection)。放在模組設計中,又衍生出控制反轉(IoC, Inverse of Control)概念,用於描述應用程式在使用一個架構時,不是架構來控制/限制應用程式的架構模式,而是由應用程式來控制架構。
本文就簡單描述下Bean容器是如何使用反射來實現的,最終代碼參考github ioc-sample
類的動態載入
可以簡單地使用Class.forName,傳入某個class的完整名:
public Class<?> loadClass(String fullName) throws ClassNotFoundException { return Class.forName(fullName); }
類的載入涉及到class loader,這塊內容是可以進一步深化的。載入了類之後就可以建立出類的執行個體,但還沒有完成依賴注入的功能:
Class<?> c = loadClass("com.codemacro.bean.test.Test1"); Object o = c.newInstance();通過set介面注入
我們的類可以包含set介面,用於設定某個成員:
public class Test2 { public Test1 test1; public void setTest1(Test1 t) { test1 = t; } }
那麼可以通過setXXX介面將Test1注入到Test2中:
// props指定哪些成員需要注入,例如{"Test1", "test1"},Test1指的是setTest1,test1指的是bean名字 public Object buildWithSetters(String name, Class<?> c, Map<String, String> props) { try { // ClassSetMethods 類擷取Class<?>中所有setXX這種介面 ClassSetMethods setMethods = new ClassSetMethods(c); Object obj = c.newInstance(); for (Map.Entry<String, String> entrys : props.entrySet()) { String pname = entrys.getKey(); String beanName = entrys.getValue(); // 取得setXXX這個Method Method m = setMethods.get(pname); Object val = getBean(beanName); // 調用 m.invoke(obj, val); } beans.put(name, obj); return obj; } catch (Exception e) { throw new RuntimeException("build bean failed", e); } }
ClassSetMethod自省出一個Class中所有的setXXX(xx)介面:
public ClassSetMethods(Class<?> c) { Method[] methods = c.getMethods(); for (Method m : methods) { String mname = m.getName(); Class<?>[] ptypes = m.getParameterTypes(); if (mname.startsWith("set") && ptypes.length == 1 && m.getReturnType() == Void.TYPE) { String name = mname.substring("set".length()); this.methods.put(name, m); } } }
以上就可以看出Java中的自省能力,例如Class<?>.getMethods、Method.getReturnType、Method.getParameterTypes。
通過建構函式注入
類似於Spring中的:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="2001"/> <constructor-arg type="java.lang.String" value="Zara"/>
可以將依賴的Bean通過建構函式參數注入到目標對象中:
List<String> params = new ArrayList<String>(); params.add("test1"); bf.buildWithConstructor("test2", Test2.class, params);
其實現:
public Object buildWithConstructor(String name, Class<?> c, List<String> beanNames) { try { Constructor<?>[] ctors = c.getConstructors(); // 取得Class建構函式列表 assert ctors.length == 1; Constructor<?> cc = ctors[0]; Class<?>[] ptypes = cc.getParameterTypes(); // 取得建構函式參數類型列表 assert ptypes.length == beans.size(); Object[] args = new Object[ptypes.length]; for (int i = 0; i < beanNames.size(); ++i) { args[i] = getBean(beanNames.get(i)); // 構造調用建構函式的實參列表 } Object obj = cc.newInstance(args); // 通過建構函式建立對象 beans.put(name, obj); return obj; } catch (Exception e) { throw new RuntimeException("build bean failed", e); } }
這個介面的使用約定beanNames儲存的是bean名稱,並與建構函式參數一一對應。
通過註解注入
我們可以通過註解標註某個資料成員是需要被自動注入的。我這裡簡單地擷取註解標註的成員類型,找到該類型對應的Bean作為注入對象。當然複雜點還可以指定要注入Bean的名字,或自動尋找類型的衍生類別實現。
一個空的註解即可:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Inject { }
實現:
public Object buildWithInject(String name, Class<?> c) { try { Object obj = c.newInstance(); Field[] fields = c.getDeclaredFields(); // 擷取該類所有定義的成員 for (Field f :fields) { Inject inject = f.getAnnotation(Inject.class); // 擷取資料成員的註解 if (inject != null) { // 如果被Inject註解標註 Object bean = getBeanByType(f.getType()); // 根據成員的類型找到對應的Bean f.set(obj, bean); // 注入 } else { throw new RuntimeException("not found bean " + f.getName()); } } beans.put(name, obj); return obj; } catch (Exception e) { throw new RuntimeException("build bean failed", e); } }
getBeanByType就是根據Class匹配所有的Bean。使用時:
public class Test2 { @Inject public Test1 test1; ... }
完。
原文地址: http://codemacro.com/2015/05/31/java-refect-ioc/
written by Kevin Lynx posted athttp://codemacro.com
Java中的反射及Bean容器的實現