struct2源码解读之解析bean标签

  上篇博文,我们大致分析了struct2是如何解析struct2配置文件的,包括default.properties和struct*.xml,但有些细节比较繁琐,如struct2是如何解析bean标签的和struct2是如何解析packag标签的,还没详细分析,这篇博文将详细解析struct2是如何解析bean标签的,package的解析将留在下篇博文进行讲解。

一、bean标签

  我们先来看下bean标签是怎么样的?

代码清单:structs.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
    <!--bean标签-->
    <bean name="" type="" class="" scope="" static="" optional="">
    
    </bean>
<struts>

    bean标签有5个属性,name,type,class,scope,static,optional

    name:它指定的Bean实例的名字,对于有相同type的多个Bean。则它们的name属性不能相同。

    type:这个属性是个可选属性,它指定了Bean实例实现的Struts2的规范,该规范通常是通过某个接口或者在此前定义过的Bean,因此该属性值通常是个接口或者此前定义过的Bean的name属性值。如果需要将Bean的实例作为Strut2组件使用,则应该指定该属性的值。

    class:这个属性是个必填属性,它指定了Bean实例的实现类

    scope:它指定Bean实例的作用域,该属性的值只能是default、singleton、request、session或thread之一

    static:它指定Bean是否使用静态方法注入。通常而言,当指定了type属性时,该属性就不应该指定为true

    optional:该属性是个可选属性,它指定Bean是否是一个可选Bean

二、获得bean标签信息 

上篇博文我讲到,struct2用dom解析struct*.xml文件后,我们会得到一个document对象,然后用这个对象的getDocumentElement()方法获得根节点<struts>,然后再用getChildNodes,得到<struts>的子节点,通过循环遍历子节点,得到bean标签。

  if ("bean".equals(nodeName)) {
                    //获得bean属性
                        String type = child.getAttribute("type");
                        String name = child.getAttribute("name");
                        String impl = child.getAttribute("class");
                        String onlyStatic = child.getAttribute("static");
                        String scopeStr = child.getAttribute("scope");
                         //optional属性默认为true
                        boolean optional = "true".equals(child.getAttribute("optional"));
                         //scope属性默认为singleton,这里可以看到scope的值范围
                        Scope scope = Scope.SINGLETON;
                        if ("default".equals(scopeStr)) {
                            scope = Scope.DEFAULT;
                        } else if ("request".equals(scopeStr)) {
                            scope = Scope.REQUEST;
                        } else if ("session".equals(scopeStr)) {
                            scope = Scope.SESSION;
                        } else if ("singleton".equals(scopeStr)) {
                            scope = Scope.SINGLETON;
                        } else if ("thread".equals(scopeStr)) {
                            scope = Scope.THREAD;
                        }
                        //name属性默认为default
                         if (StringUtils.isEmpty(name)) {
                            name = Container.DEFAULT_NAME;
                        }
                        //省略.对属性的封装,下面详解
       }

   得到bean标签后,获取它的属性值,给某些属性赋予初始值,下面就是对这些属性的封装

三、封装bean属性

代码清单:封装bean属性
     //获取配置的class属性的实现类   
     Class cimpl = ClassLoaderUtil.loadClass(impl, getClass());
     //这个ctype变量会作为这个bean的key值封装到containerBuilder中,这里设计一个变量,是为了区分取class还是type值,默认是class的属性值
     Class ctype = cimpl;
    if (StringUtils.isNotEmpty(type)) {
              //如果type属性不为空,则去type的属性值为key值
             ctype = ClassLoaderUtil.loadClass(type, getClass());
          }
    if ("true".equals(onlyStatic)) {//如果static属性为true
        //通常而言,当指定了type属性时,该属性就不应该指定为true,所以这里用class为key值
        //getDeclaredClasses这个是为了捕获异常
        cimpl.getDeclaredClasses();
        //封装静态类
        containerBuilder.injectStatics(cimpl);
    } else {//如果static属性为false
        if (containerBuilder.contains(ctype, name)) {
                //如果containerBuilder中已经有这个bean 则抛出异常信息.代码略。从这里可以看出封装是以name和ctype为key值的
        }
        //getDeclaredClasses这个是为了捕获异常
       cimpl.getDeclaredConstructors();
       //把这个bean的所有属性以name和class(type)属性值为主键封装到containerBuilder
       containerBuilder
.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);
   }
   //把已解析过的节点放到一个map中缓存,loadBeans是一个map的名称
   loadedBeans.put(ctype.getName() + name, child);

      这个代码就是上面省略的那些代码,前后很简单,无非就是获取下配置class属性的实现类和把解析过的bean以ctype和name为key值放到一个map中缓存。重点是把bean属性封装到containerBuild对象。这里分为封装静态类和封装非静态类。


3.1.封装静态类

     封装静态类也很简单,在injectStatics()这个方法里面,我们来看下这个方法

 final List<Class<?>> staticInjections = new ArrayList<Class<?>>();
 public ContainerBuilder injectStatics(Class<?>... types) {
    staticInjections.addAll(Arrays.asList(types));
    return this;
  }

   从这里可以看到,这里所谓的封装静态类,无非就是把class或者是type属性放到ContainerBuilder的一个类型为list的属性中。到时候我们用get方法,就可以访问到这个属性。


3.2.封装非静态类

    封装非静态类,用到了ContainerBuilder的factory方法

containerBuilder
.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);

     在用这个方法之前,实例化了一个LocatableFactory对象,这个对象封装了bean的所有属性。这里用到了工厂模式。我们先了解总体的过程,后面再讨论这个工厂模式。这里你知道struct2把属性封装到一个LocatableFactory对象就可以了。我们来看看这个factory方法。

  public <T> ContainerBuilder factory(final Class<T> type, final String name,
      final Factory<? extends T> factory, Scope scope) {
      //实例化一个internalFactory对象
    InternalFactory<T> internalFactory =new InternalFactory<T>() { 代码略 };
   //重载factory对象
    return factory(Key.newInstance(type, name), internalFactory, scope);
  }

   上面这个factory重载了这个factory的方法。这个Key是一个静态类

class Key<T> {
  final Class<T> type;
  final String name;
  final int hashCode;
  }

  我们来看看重载后的这个factory方法

  //把封装了属性的factory对象保存到containerBuild的一个map中
  final Map<Key<?>, InternalFactory<?>> factories =
      new HashMap<Key<?>, InternalFactory<?>>();
   //把singleton的factory对象保存到containerBuild的一个list中
  final List<InternalFactory<?>> singletonFactories =
      new ArrayList<InternalFactory<?>>();
      
  private <T> ContainerBuilder factory(final Key<T> key,
      InternalFactory<? extends T> factory, Scope scope) {
      //先判断是否已经存在这个bean
      //有create字段,默认为false,创建后置true,这里就是判断这个字段
    ensureNotCreated();
    //因为factory要保存到一个map中,这里判断是否contain key,如果我true抛出异常
    checkKey(key);
    //进一步封装成scopedFactory
    final InternalFactory<? extends T> scopedFactory =
        scope.scopeFactory(key.getType(), key.getName(), factory);
    //保存到map中。struct2把ctype和name封装到一个Key对象,然后再以这个对象为key值
    factories.put(key, scopedFactory);
    if (scope == Scope.SINGLETON) {
      //如果是SINGLETON,则把重新封装的factory放到一个list集合中
      singletonFactories.add(new InternalFactory<T>() {
        //接口实现类的动态构造方法
        public T create(InternalContext context) {
           
        }
   
      });
    }
    return this;
  }

    由此我们大概知道了struct2封装bean的原理:struct2把bean的所有属性封装到一个实现factory接口的对象中,然后再用name和class(type)为键值把这个对象保存到ContainerBuilder的一个map类型的属性中(singleton的bean保存到list类型的属性中)。现在剩下的问题就是,struct2是如何设计把bean的属性封装到factory接口的实现类中的呢 ?


四、工厂模式

    这里用到了工厂模式。什么是工厂模式?通俗的来说,就是实例化对象的时候不是用new,而是通过集成factory接口的实现类实例化。我们先来看看factory接口

public interface Factory<T> {

  T create(Context context) throws Exception;
}

   这个factory接口只有一个create()方法,这个方法返回的是一个Object T,也就是说通过调用实现了factory接口的对象的create()方法可以返回一个对象实例。我们可以在这个方法中设计自己所要返回的对象.我们以上面的例子做解析。

   上面提到,struct2会把bean的所有属性封装到一个LocatableFactory对象中

new LocatableFactory(name, ctype, cimpl, scope, childNode)

    我们来看看这个LocatableFactory对象

public class LocatableFactory<T> extends Located implements Factory<T> {
    private Class implementation;
    private Class type;
    private String name;
    private Scope scope;
    //构造函数
    public LocatableFactory(String name, Class type, Class implementation, Scope scope, Object location) {
        this.implementation = implementation;
        this.type = type;
        this.name = name;
        this.scope = scope;
        setLocation(LocationUtils.getLocation(location));
    }

    @SuppressWarnings("unchecked")
    public T create(Context context) {
        Object obj = context.getContainer().inject(implementation);
        return (T) obj;
    }
 }

   这个对象实现了factory的接口,并封装了bean的属性,当我们实例化这个对象的时候,就把bean的属性值初始化到了这个对象的属性中。这个对象的create()方法就返回了class(type)属性中配置的对象实例。

  而后面,struct2又把这个对象进一步封装成了InternalFactory对象。为什么要封装成InternalFactory对象呢?我们在实例化bean的时候,是不要是要考虑这个类的作用域?scope.scopeFactory()就是返回InternalFactory对象。scopeFactory()是一个抽象方法

 abstract <T> InternalFactory<? extends T> scopeFactory(
      Class<T> type, String name, InternalFactory<? extends T> factory);

   当我们配置scope的时候,就会调用相应scope的scopeFactory()方法(这是java多态的一个特性)。

比如我们配置了scope=singleton.则会调用

  代码清单:Scope类
  SINGLETON {
    @Override
            <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
        final InternalFactory<? extends T> factory) {
      return new InternalFactory<T>() {
        T instance;
        public T create(InternalContext context) {
          synchronized (context.getContainer()) {
             //singleto的 对象为空的才创建
            if (instance == null) {
              instance = factory.create(context);
            }
            return instance;
          }
        }

        @Override
        public String toString() {
          return factory.toString();
        }
      };
    }
  }

     这里返回了一个InternalFactory对象,把这个对象保存到containerBuild对象的一个map类型的属性中,将来在处理action请求的时候,找到这个对象,然后找到这个InternalFactory对象,然后通过这个对象的create方法,又调用上一个factory的create方法,最后把这个classs实例实例化出来了。

     这里介绍了工厂的模式,同时也介绍了struct2是如何封装bean属性的。对于这个工厂模式,只是相对于new 一个对象的另外一个实例化对象的方法,这个方法的好处还是显而易见的,对于多参数构造一个对象的时候可考虑用工厂模式实例化这个对象,这样能简化代码也便于维护。

五、总结

   现在来总结下上面的内容。struct2解析bean标签的时候,把属性name,class,type,scope,static,optional封装到了一个实现了factory的对象中,这个对象由作用域决定,然后把这个对象以name和class(type)为键值保存到containerBuild的一个map集合中(singleton的保存到list集合)。这个就是struct2解析xml文件时,解析bean 的过程。下篇博文会探讨如何解析package标签。