struts2源碼分析-IOC容器的實現機制(下篇)

經過struts2源碼分析-IOC容器的實現機制(上篇),能夠從中瞭解了不少關於struts2-IOC容器初始化的東西,若是容器託管對象是什麼,<bean>節點中爲何有了type屬性還要有name屬性,ContainerBuilder構建Container原理等。主要是講解了IOC容器的初始化過程,而對從容器中獲取容器託管對象以及注入原理一筆帶過了。該篇文章將詳細講解如何從容器中獲取容器託管對象以及注入原理。java

講解以前有些結論是必需要知道的:緩存

  1. 什麼是容器託管對象?安全

    按struts2/XWork的配置元素節點可分爲兩類,一類是<bean>節點和<constant>節點,這兩個節點爲「容器配置元素」,另外一類是package節點,這個節點下的全部配置定義爲"事件映射關係"。其中<bean>節點普遍用於定義框架級別的內置對象和自定義對象,而constant節點和Properties文件中的配置選項則被用於定義系統級別的運行參數。<bean>節點,<constant>節點加上Propeties文件中的配置選項統稱爲"容器配置元素",就是由於它們所定義的對象的生命週期都是由容器管理的,因此這些對象就是容器託管對象。session

  2. Container中的容器託管對象並非直接存儲在容器中的,實際上容器中存儲的是對象的工廠,有了對象工廠就能夠隨時在運行期 得到對象的實例,存儲工廠有個好處就是能夠控制對象的產生,例如是要返回一個新的對象呢,仍是返回一個單例對象,或者說返 回一個線程安全的對象等。

1、依賴注入

依賴注入也就是調用Container中的inject方法,先看一下該方法聲明框架

void inject(Object o);

  <T> T inject(Class<T> implementation);

方法有兩個,第一個是傳入一個Object,對該對象進行注入,注意,這裏的對象能夠是任意對象,不必定要是容器託管對象。第二個方法傳入一個Class對象,完成注入並返回一個注入好了的對象實例。ide

下面是@Injector註解的源碼:源碼分析

@Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface Inject {

  String value() default DEFAULT_NAME;

  boolean required() default true;
}

從其聲明能夠看到,該註釋可標於方法,構造器,字段,參數列表中,這也對應了進行注入時可能用到的各類類型的注入器。其value屬性爲查找InternalFactory(容器託管對象的工廠)的name屬性值,默認值爲defaultui

Container爲一個接口,其實現類爲ContainerImpl,當咱們要進行注入時,其實調用的就是ContainImpl類的inject方法。以下ContainerImplvoid inject(Object o)的實現:this

public void inject(final Object o) {
    callInContext(new ContextualCallable<Void>() {
      public Void call(InternalContext context) {
        inject(o, context);
        return null;
      }
    });
  }

在該實現方法當中調用的是callInContext方法,這是一個模版方法,在後面能夠看到,從容器中獲取對象的方法getInstance內部調用的也是該方法,其目的是將全部接口操做進行規範化定義,做爲一個統一操做的入口,並將它們歸入一個線程安全的上下文執行執行環境中。而具體的執行邏輯則被封裝於ContextualCallable接口的回調實現以內。對於不一樣的接口實現,它們會擁有不一樣的邏輯,而這些邏輯則由ContextualCallable接口實現類來完成,正由於這樣纔有可能inject方法與getInstance方法內部調用的都是同一個callInContext方法。這就是模版方法的使用,模版方法只是規定了程序的執行流程,而程序執行的具體邏輯能夠由各調用者本身指定。這裏public Void call(InternalContext context)便是那個回調方法。線程

以下是callInContext方法的源碼:

<T> T callInContext(ContextualCallable<T> callable) {
    Object[] reference = localContext.get();
    if (reference[0] == null) {
      reference[0] = new InternalContext(this);
      try {
        return callable.call((InternalContext)reference[0]);
      } finally {
        // Only remove the context if this call created it.
        reference[0] = null;
      }
    } else {
      // Someone else will clean up this context.
      return callable.call((InternalContext)reference[0]);
    }
  }

在該方法當中就是調用了前面註冊的回調方法call,其他的操做都是在獲取一個正確的執行上下文。在回調方法call中調用了void inject(Object o, InternalContext context)方法,咱們進行該方法的內部看看:

void inject(Object o, InternalContext context) {
    List<Injector> injectors = this.injectors.get(o.getClass());
    for (Injector injector : injectors) {
      injector.inject(context, o);
    }
  }

該方法其中的邏輯很簡單,首先獲取該對象所須要的注入器(Injector),再循環迭代第一個注入器,調用注入器的inject方法完成注入。

這裏須要講的是這句List&lt;Injector&gt; injectors = this.injectors.get(o.getClass());這是從一個緩存對象(Map)中獲取所需注入器的操做,這是ContainerImplinjectors的聲明:

final Map<Class<?>, List<Injector>> injectors =
      new ReferenceCache<Class<?>, List<Injector>>() {
        @Override
        protected List<Injector> create(Class<?> key) {
          List<Injector> injectors = new ArrayList<Injector>();
          addInjectors(key, injectors);
          return injectors;
        }
      };

其實你們能夠看到,其實該對象就是個Map,只不過增長了緩存的功能,其key爲一Class對象,就是被注入對象的Class對象,其value爲該對象注入所需注入器,當第一次爲某一對象進行注入時,獲取的注入器確定爲null,這時就會去查找該對象注入所須要的注入器。

你們能夠看到獲取注入器用的是ReferenceCacheget方法,這是get方法的源代碼:

@SuppressWarnings("unchecked")
  @Override public V get(final Object key) {
    V value = super.get(key);
    return (value == null)
      ? internalCreate((K) key)
      : value;
  }

若是說沒有找到指定key的值,該方法則會建立一個新的值放入Map中並返回,這個新的值其實就是注入器列表。

這是internalCreate方法中的兩句源碼:

FutureTask<V> futureTask = new FutureTask<V>(
          new CallableCreate(key));
//中間省略...

futureTask.run();

這裏新建了一個CallableCreate對象封裝到了futureTask對象中,後面又調用了futureTask.run();run方法內容調用的又是一個Sync對象的innerRun()方法,在innerRun方法中調用了CallableCreate對象的call方法,而該call方法中調用的正是injectors聲明中註冊的create(key)回調方法。

好了,如今能夠將注意力集中到該create(key)方法上了來,該中調用了addInjectors(key, injectors);方法,該方法源碼爲:

void addInjectors(Class clazz, List<Injector> injectors) {
    if (clazz == Object.class) {
      return;
    }

    // Add injectors for superclass first.
    addInjectors(clazz.getSuperclass(), injectors);

    // TODO (crazybob): Filter out overridden members.
    addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);
    addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);
  }

該方法是一個遞歸方法,首先查找父類注入器,先爲父類實施注入,其次是本身。這裏addInjectorsForFieldsaddInjectorsForMethods調用的都是一個名爲addInjectorsForMembers的方法,下面是 addInjectorsForMembers源碼:

<M extends Member & AnnotatedElement> void addInjectorsForMembers(
      List<M> members, boolean statics, List<Injector> injectors,
      InjectorFactory<M> injectorFactory) {
    for (M member : members) {
      if (isStatic(member) == statics) {
        Inject inject = member.getAnnotation(Inject.class);
        if (inject != null) {
          try {
            injectors.add(injectorFactory.create(this, member, inject.value()));
          } catch (MissingDependencyException e) {
            if (inject.required()) {
              throw new DependencyException(e);
            }
          }
        }
      }
    }
  }

該方法只是調用InjectorFactorycreate方法返回一個Injector並放入到注入器列表當中,create方法呢又是一個回調方法,這裏以FieldInjector爲例,這是addInjectorsForFields方法的源碼:

void addInjectorsForFields(Field[] fields, boolean statics,
      List<Injector> injectors) {
    addInjectorsForMembers(Arrays.asList(fields), statics, injectors,
        new InjectorFactory<Field>() {
          public Injector create(ContainerImpl container, Field field,
              String name) throws MissingDependencyException {
            return new FieldInjector(container, field, name);
          }
        });
  }

create方法中傳入容器對象,Field, name返回了一個字段注入器,方法注入器原理也是如些,這裏就不說了。

FieldInjector構造方法內問,根據field類型與name參數做爲key在容器中查找對應容器託管對象的InternalFactory,下面是FieldInjectorinject方法的實現:

public void inject(InternalContext context, Object o) {
      ExternalContext<?> previous = context.getExternalContext();
      context.setExternalContext(externalContext);
      try {
        field.set(o, factory.create(context));
      } catch (IllegalAccessException e) {
        throw new AssertionError(e);
      } finally {
        context.setExternalContext(previous);
      }
    }

你們能夠看到最終調用的就是經過反射技術調用field.set()方法,要設置的值就是相應的InternalFactorycreate方法返回的值。到這裏你們對struts2中的注入原理應該有必定的瞭解了吧。

2、獲取容器託管對象

這是在Container接口中的聲明:

<T> T getInstance(Class<T> type, String name);

  <T> T getInstance(Class<T> type);

其實這兩個方法的實質是同樣的,第二個其實就是一個簡單方法,至關於getInstance(type,DEFAULT_NAME);就是以type和name做爲聯合主鍵進行查找。

再看看ContainerImpl的具體實現:

public <T> T getInstance(final Class<T> type, final String name) {
    return callInContext(new ContextualCallable<T>() {
      public T call(InternalContext context) {
        return getInstance(type, name, context);
      }
    });
  }

這裏調用的方法如同上面所說,都是callInContext這個模版方法,其真正邏輯實現是getInstance(type,name,context)方法,其源碼爲:

@SuppressWarnings("unchecked")
  <T> T getInstance(Class<T> type, String name, InternalContext context) {
    ExternalContext<?> previous = context.getExternalContext();
    Key<T> key = Key.newInstance(type, name);
    context.setExternalContext(ExternalContext.newInstance(null, key, this));
    try {
      InternalFactory o = getFactory(key);
      if (o != null) {
          return getFactory(key).create(context);
      } else {
          return null;
      }
    } finally {
      context.setExternalContext(previous);
    }
  }

該方法代碼也很簡單,首先根據typename構造一個Key對象,其實Key就是一個用於存儲typename的一個POJO,用它做爲key去容器中查找相應的InternalFactory對象,而後調用InternalFactory對象的create方法返回該值。
這裏你們可能會問:struts2中bean不是能夠聲明變量範圍嗎,如singleton,request,session,thread的嗎,這裏只是直接調用了InternalFactorycreate方法,這樣能達到目的嗎,前面也已經說了容器中存儲這些工廠其好處就是能夠控制對象的產生,這些功能邏輯都已經封裝在InternalFactory當中的,具體的請參看struts2源碼分析-IOC容器的實現機制(上篇),裏面有很詳細的說明。

struts2中呢大量應用了模版方法,回調方法,剛看可能會不習慣,多看幾遍應該沒有問題的。但願對你們有所幫助。
-------------------------------- END -------------------------------

及時獲取更多精彩文章,請關注公衆號《Java精講》。

相關文章
相關標籤/搜索