Struts2的IoC解析

jopen 11年前發布 | 25K 次閱讀 Struts2 Web框架
對于 IoC來說,常見的就是Spring框架的了。并且在目前Java EE開發中,使用SSH框架時,也主要依賴于Spring框架所提供的IoC功能。但Struts2框架本身也提供了IoC的功能。本人對于Spring 框架的IoC功能的實現沒怎么做了解,所以也不對此發表什么見解。這里主要是對Struts2框架的IoC的使用方法和實現原理進行分析。純屬個人學習興趣。。。。
在這其中參照了博文《Struts2源碼分析-IoC容器的實現機制》,鏈接地址為http://blog.csdn.net/fcbayernmunchen/article/details/7686385中的相關內容。
[IoC功能的使用]
首先如何理解IoC的含義呢,現在也常稱為依賴注入(Dependency Injection,DI。各種稱呼,對此略有點糊涂)。核心的意思就是把對象之間的耦合關系,交由外部去管理,一般就是一個容器(Container)對象。那容器,又是如何去獲得關于對象的信息的呢,這就是配置文件的作用了。在Spring中,有applicationContext.xml配置文件。在Struts2中,有struts.xml配置文件。在這些配置文件中配置好對象信息,在加載的時候,這些配置文件會被解析,然后當對象被使用的時候,就通過配置文件找到相應的類,調用相應的方法等。。。。

大致是這么個思路,直接說有點抽象,下面用一個實際的例子來說明這種用法。

struts.xml中的配置項如下所示
< bean name ="entity" type ="lynn.entity.Entity" class ="lynn.entity.Entity" scope="Scope.SINGLETON"/>
配置了一個bean,名稱是entity,類型是lynn.entity.Entity,實例化對象時,具體生成的是lynn.entity.Entity這個類的對象。在這個例子中type和class的值被設置成一樣的。一般來說class描述的類應該是type類的子類,或是type描述的接口的實現類。 scope指出了這個對象的生存范圍,這里這個對象被設置成了一個單例形式的對象。也就是,所有通過Struts2的IoC獲取的對象,都是同一個對象。

下面來看lynn.entity.Entity這個類的代碼,如下所示
package lynn.entity;


public class Entity {
       
        private String name ;
        private int value ;
       
       
        public Entity(){
               name= "Lynn";
               value=23;
       }
       
        public void setName(String name){
               this.name =name;
       }
       
        public void setValue(int value){
               this.value =value;
       }
       
        public String getName(){
               return name ;
       }
        public int getValue(){
               return value ;
       }
       
        @Override
        public String toString(){
               return "Entity info:<" +name +"," +value +">" ;
       }
}
lynn.entity.Entity這個類沒有什么特別之處,就是一個一般的Java Bean,只不過重寫了toString方法,這樣就可以直接將Entity對象的相關信息打印出來。

還有一個測試inject方法的類,叫做InjectTest類,這個類的代碼如下所示
package lynn.inject;

import lynn.entity.Entity;
import com.opensymphony.xwork2.inject.Inject;


public class InjectTest {
        @Inject( "entity")
        private Entity entity ;
       
       
        @Inject
        public void injectTest(@Inject("entity")Entity entity){
              System. out.println(entity);
       }
       
        public void normalMethod(){
              System. out.println("normal method" );
       }
}
主要注意這個類中加入的@Inject注釋。這里指定的名稱是“entity”,也就是前面在struts.xml中配置的那個java bean。

在測試項目中與lynn.entity.Entity和IoC相關的部分的代碼如下所示
Container container=ServletActionContext.getContext().getContainer();
InjectTest inject= new InjectTest();
Entity entity=container.getInstance(Entity. class, "entity" );
System. out.println("first mark: " +entity);
entity.setName( "yanlinwang");
entity.setValue(50);
container.inject(inject);
Entity entity2=container.getInstance(Entity. class,"entity" );
System. out.println("second mark:" +entity2);
在這段代碼中,首先通過ServletActionContext.getContext().getContainer()獲得用來管理對象關系的容器(Container)對象container,然后與IoC功能相關部分的代碼是container.getInstance(Entity. class "entity" );和container.inject(inject); 這里對getInstance對象調用了兩次,并且在中間還對獲取過的對象進行過修改,通過對兩次獲取到的對象的打印結果,來確定是不是像前面所說的那樣,這個對象是一個單例對象。

項目運行后,與此部分相關的顯示結果如下圖所示

從運行結果來看,總共對這個對象打印了三次。出了在上面標出的"first mark"和"second mark"外,還進行了一次打印動作。是在InjectTest類的injectTest函數中。第一次的打印結果是<Lynn,23>,這表明是調用了默認構造函數。在后續對這個entity對象的屬性值進行了設定,然后進行 container.inject(inject);時injectTest函數被調用,使得這個對象又被打印一次。這里顯示的信息就是設置后的新值。然后再次通過容器獲取這個對象,并且打印。這里打印出來的值與設置的值一樣,表明這里獲取的還是之前修改過的對象,所以,從這三次打印的結果來看,三次操作的都是同一個entity對象。

通過上面這個例子,也給出了IoC功能的一個形象說明。下面部分,將對Struts2的IoC的實現,進行解析。學習所致,不保證正確性!

[IoC功能的接口]
前面的例子中給出的對Struts2的IoC功能的使用,是通過一個容器(Container)對象來實現的。Container是一個接口類,它的源代碼如下所示
public interface Container extends Serializable {

  /**
   * Default dependency name.
   */
  String DEFAULT_NAME = "default";

  /**
   * Injects dependencies into the fields and methods of an existing object.
   */
  void inject(Object o);

  /**
   * Creates and injects a new instance of type {@code implementation}.
   */
  <T> T inject(Class<T> implementation);

  /**
   * Gets an instance of the given dependency which was declared in
   * {@link com.opensymphony.xwork2.inject.ContainerBuilder}.
   */
  <T> T getInstance(Class<T> type, String name);

  /**
   * Convenience method.&nbsp;Equivalent to {@code getInstance(type,
   * DEFAULT_NAME)}.
   */
  <T> T getInstance(Class<T> type);
 
  /**
   * Gets a set of all registered names for the given type
   * @param type The instance type
   * @return A set of registered names or empty set if no instances are registered for that type
   */
  Set<String> getInstanceNames(Class<?> type);

  /**
   * Sets the scope strategy for the current thread.
   */
  void setScopeStrategy(Scope.Strategy scopeStrategy);

  /**
   * Removes the scope strategy for the current thread.
   */
  void removeScopeStrategy();
}

由上面的代碼結合前面的例子,可以看出獲取對象和注入相關的接口是getInstance和inject的重載方法。這就是IoC功能的相關外部接口。那么,這個過程是如何實現的呢?

[IoC功能相關的數據結構----容器實現類ContainerImpl的成員變量]
要了解Struts2的IoC的實現,就需要從上面相關容器接口方法的具體實現來看。在Struts2中,實現這些相關方法的類是ContainerImpl,它實現了一個具體的容器的功能。通過對這個容器實現類進行解析,就可以大致了解IoC的實現原理。
在對相關接口的實現函數進行源代碼分析之前,首先說明下ContainerImpl類的一些與此相關的成員變量。
1、factories變量
final Map<Key<?>, InternalFactory<?>> factories;

factories是在生成容器對象時傳遞進來的參數,在之前的博文《Struts2的Builder模式》中介紹了參數的收集過程,并且對參數的作用作了一定的介紹。這里再簡要說明下,構造容器對象時傳遞進來的參數是<Key,InternalFactory>的鍵值對。Key代表的是一個對象的類型、類型的名稱,InternalFactory代表的是創建一個具體對象的工廠對象,在需要的時候,調用factory方法就可以生成這個對象。

factories是在ContainerImpl類的構造函數中被賦值的,就是參數傳遞進來的值。

2、injectors變量
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;
    }
};
injectors 實際上是一個ReferenceCache對象,就是在之前的博文《Struts2緩存解析》中分析的Struts2的一個緩存實現。這里是緩存了類和其注入器之間的關系。對ReferenceCache類的create函數的重寫,主要是調用了ContainImpl類的addInjectors函數。這是實現緩存延遲加載原理中真正加載緩存對象的地方,在緩存中暫時找不到所要的對象時,就通過addInjectors來加載所需的對象。

addInjectors的代碼如下
void addInjectors( Class clazz, List<Injector> injectors ) {
               if (clazz == Object.class) {
                      return;
              }
              addInjectors(clazz.getSuperclass(), injectors);//遞歸向上,為其父類實現注入,直到遇到Object為止
               //變量注入
              addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);
               //方法注入
              addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);
       }
簡單說來addInjectors的作用就是遞歸向上依次實現注入的動作,實現變量注入和方法注入。

這里看不到真正執行注入的動作,繼續分析。來看addInjectorsForFields的代碼
void addInjectorsForFields( Field[] fields, boolean statics,List<Injector> injectors ) {
   addInjectorsForMembers(Arrays. asList(fields), statics, injectors,
                //匿名內部類,注意這個InjectorFactory對象
                new InjectorFactory<Field>() {
                  @Override
                   public Injector create( ContainerImpl container, Field field,String name )
                                     throws MissingDependencyException {
                       return new FieldInjector(container, field, name);//注意這里
                                  }
                  });
}
從代碼上看,addInjectorsForFields也不是執行注入動作的地方,而是通過調用addInjectorsForMembers來實現。與此類似,對addInjectorsForMethods,也是通過調用addInjectorsInjectorsForMembers來執行注入的動作。也就是二者最終都匯集到了對一個方法的調用上,當主要的區別在于所傳遞的參數的不同之處。而影響最終注入動作的就是最后一個參數,InjectorFacotry對象。
區別點:
在addInjectorsForField中,這個InjectorFactory對象的create方法調用,返回的是一個FieldInjector對象;
在addInjectorsForMethods中,傳遞的InjectorFactory對象的create方法調用,返回的是一個MethodInjector對象。

下面來研究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 {
                                          //inject.value()!!
                                         injectors.add(injectorFactory.create( this, member, inject.value()));
                                  } catch ( MissingDependencyException e ) {
                                          if (inject.required()) {
                                                 throw new DependencyException(e);
                                         }
                                  }
                           }
                     }
              }
       }
注意在addInjectors方法中調用addInjectorsForFields和addInjectorsForMethods時,傳遞了一個false參數,傳遞到 addInjectorsForMembers時,就是這個statics參數為false。就是為其中非靜態的實例變量和實例方法來實現注入。對于靜態的類變量和類方法,這里不執行注入動作。關于靜態的情況,這里就不做討論了。。。
在上面的addInjectorForMembers方法中,主要看這兩條語句
Inject inject = member.getAnnotation( Inject. class );首先獲取這個成員(變量、方法)的注釋情況,如果對這個成員有“@Inject”的注釋,那么就執行這條語句injectors.add(injectorFactory.create( this , member, inject.value()));生成這個成員的注入器中,然后保存起來。

保存起來,保存到了何處?看整個調用過程,addInjectors(key, injectors);->
   addInjectorsForFields(clazz.getDeclaredFields(), false injectors);->
      addInjectorsForMembers(...,injectors,...);

其實是保存在了由傳進來的參數指定的List<Injector>中,也就是設置的緩存變量ReferenceCache類型的injectors中。再回過頭來看
這個ReferenceCache類型的injectors緩存,它是ReferenceCache<Class<?>,List<Injector>>,也就是通過類類型,來獲取這個類的所有的注入器。

根據之前對緩存的講解,當找不到緩存值時,就會實現加載,也就是調用create。將調用create的值返回,也就是那些生成的注入器了。具體說來,就是當通過 injectors.get(XXX.class);針對這個類類型加載的注入器會被返回。也就是前面所說的保存的地方了!

[IoC的具體實現]
與Struts2的IoC相關的數據結構介紹完之后,現在來說下接口函數的實現,同時這里以對成員變量的注入為例,通過FieldInjector來看,注入操作到底做了什么。

首先來看 getInstance的實現。
其它的重載函數不予說明,這里給出的是真正執行動作的那個getInstance的實現。其它的重載函數最終都是通過對這個函數的調用來實現的。
<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);
              }
       }
類容不長,真正要注意的地方就是這句話return getFactory(key).create(context);通過制定的類型和名稱,來獲取能夠生成所需實例的工廠對象,其它的語句是對內外上下文的一些設置操作(內外上下文的具體差異,我也比較模糊)。
getFactory方法的方法體中,只有一句話
<T> InternalFactory<? extends T> getFactory( Key<T> key ) {
               return (InternalFactory<T>) factories .get(key);
       }
從上面的代碼來看,對getInstance的實現是非常簡單的,困難的過程是在于構造容器過程中收集參數的過程,在之前的一篇博文《Struts2的Builder模式》中有簡單地介紹。

再來看inject的實現。
也是忽略其它重載函數,給出執行最終動作的inject函數的代碼。inject方法的重載情況稍微復雜一點,有兩條路,這里對與前面的內容相關的那一條路進行介紹。整個的代碼如下
void inject( Object o, InternalContext context ) {
              List<Injector> injectors = this .injectors .get(o.getClass());
               for ( Injector injector : injectors ) {
                     injector.inject(context, o);
              }
       }
就是首先獲取這個對象的所有注入器,然后依次執行注入操作。至于,具體的注入動作是做哪些事情,這里看不出來,要通過對注入器的研究來了解。前面的內容中總共出現過兩類注入器 FieldInjector和MethodInjector。這里以FieldInjector為例來進行分解。對這個類的核心方法進行研究,下面是 FieldInjector類的inject函數的代碼。其它部分的代碼與注入功能的相關性不大,主要是做一些權限上的檢查和設置,這里就不與列出了。
public void inject( InternalContext context, Object o ) {
                      //change the external context
                     ExternalContext<?> previous = context.getExternalContext();
                     context.setExternalContext( externalContext );
                      try {
                            /*
                            * "factory.create()" method will create an instance of the source type,
                            * indicated by the value of the @Inject annotation
                            * set the field value
                            * */
                            field.set(o, factory.create(context));
                     } catch ( IllegalAccessException e ) {
                            throw new AssertionError(e);
                     } finally {
                           context.setExternalContext(previous);
                     }
              }
在FieldInjector的構造函數中,需要傳遞三個參數,
 public FieldInjector( ContainerImpl container, Field field, String name )
在構造函數中,對factory進行初始化
Key<?> key = Key. newInstance(field.getType(), name);
factory = container.getFactory(key);
注意看addInjectorsForMembers中所傳遞的參數,name參數對應的是inject.value(),也就是 @Inject(value="xxxx")或@Inject("")所指定的名稱。一般對應于struts.xml中配置的bean的名稱。在對 FieldInject例子來說,就是這個成員變量所將要真正對應的那個bean。

結合上面的代碼來看,FieldInjector的inject方法的主要動作就是field.set(o, factory.create(context));設置指定對象的某個成員變量的值。這個對象o就是通過inject入口傳進來的對象了。
所以,對FieldInjector的inject函數做個小結,就是設置某一對象的標有“@Inject”注釋的成員變量的值。

類似地,對MethodInjector的inject函數做個小結,就是調用某一對象的表用“@Inject”注釋的成員函數。

那么在進行對象注入,也就是調用inject方法的時候,是設置該對象的加標注的成員變量的值,以及調用加標注的成員函數。

來自:http://blog.csdn.net/yanlinwang/article/details/8944632
 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!