Struts2的IoC解析

来源:互联网 发布:搭建视频解析接口源码 编辑:程序博客网 时间:2024/06/05 02:08

原文地址:http://blog.csdn.net/yanlinwang/article/details/8944632

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

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