Java中的反射及Bean容器的實現

來源:互聯網
上載者:User

標籤:

程式設計語言中的反射(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<?>.getMethodsMethod.getReturnTypeMethod.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容器的實現

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.