Struts2的IoC解析

个人学习笔记,不保证内容的正确率。转载请声明!!


对于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方法的时候,是设置该对象的加标注的成员变量的值,以及调用加标注的成员函数。

你可能感兴趣的:(struts2,IOC)