struts2源码分析之初始化(2)
2013-02-25 15:54 阅读(173)

容器,一般来说就是存放java bean 的,然后再需要的时候从容器中取就可以了,从用户使用角度来说是非常简单了,但是如果要把它的内部构造弄清楚其实还要知道其他的东西,比如容器内的对象是怎样创建的、怎样初始化的、怎样存储的、通过什么方式获取的等等。每个容器都有它自己的规则,但归结起来却又大同小异,接下来我们就看看struts2的容器是如何建立起来的。

struts2的容器是由接口com.opensymphony.xwork2.inject.Container规范的,所有实现该接口的都可成为容器。在struts2中实际使用的实现者是com.opensymphony.xwork2.inject.ContainerImpl类。F5进入:


class ContainerImpl implements Container {

  final Map<Key<?>, InternalFactory<?>> factories;
  final Map<Class<?>,Set<String>> factoryNamesByType;
  .
  .
  .
  //省略
} 

factories属性是实际存放bean的地方,是Map类型,实际使用的是HashMap,key值是struts2单独封装了个com.opensymphony.xwork2.inject.Key类,value值是com.opensymphony.xwork2.inject.InternalFactory类。

factoryNamesByType属性同factories一样也是存放bean的地方,只不过它的key值是Class类型,实际存放的就是配置文件中<bean>元素中的type属性,按照type对容器的元素进行了分组存放。

之所以采用俩个地方存放是为了检索时提供方便。接下来先看下这个类com.opensymphony.xwork2.inject.Key, F5进入:


class Key<T> {

  final Class<T> type;
  final String name;
  final int hashCode;

  private Key(Class<T> type, String name) {
    if (type == null) {
      throw new NullPointerException("Type is null.");
    }
    if (name == null) {
      throw new NullPointerException("Name is null.");
    }

    this.type = type;
    this.name = name;

    hashCode = type.hashCode() * 31 + name.hashCode();
  }

  Class<T> getType() {
    return type;
  }

  String getName() {
    return name;
  }

  @Override
  public int hashCode() {
    return hashCode;
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Key)) {
      return false;
    }
    if (o == this) {
      return true;
    }
    Key other = (Key) o;
    return name.equals(other.name) && type.equals(other.type);
  }

  @Override
  public String toString() {
    return "[type=" + type.getName() + ", name='" + name + "']";
  }

  static <T> Key<T> newInstance(Class<T> type, String name) {
    return new Key<T>(type, name);
  }
}
有三个属性:name、type、hashCode。name、type对应的就是<bean>元素的name、type属性,hashCode是哈希码,从Key的构造方法可以看到hashCode是通过hashCode = type.hashCode() * 31 + name.hashCode()句得到的,所以只要name、type都相同时hashCode也一定相同并且唯一。在看equals方法最后一句return name.equals(other.name) && type.equals(other.type)可知:只要name、type同时相同,则equals方法返回就是true。我们知道HashMap在判断俩个元素是否相等时,要么俩个元素是同一个对象,如果不是同一个对象再通过同时调用hashCode()和equals()方法进行判断,只有当hashCode()返回的值相等,并且equals()返回为true时,才认为俩个元素是相等的。所以经上面的分析,当com.opensymphony.xwork2.inject.Key类作为HashMap的key值时,只有当其中的name、type同时相等时,才认为其对应的元素是相等的。所以容器中的bean是以name、type的组合作为bean的唯一标识的。

接下来在看下容器中的value值,是InternalFactory类型,顾名思义,是个"工厂"类型,通过工厂创建bean。F5进入:


interface InternalFactory<T> extends Serializable {

  /**
   * Creates an object to be injected.
   *
   * @param context of this injection
   * @return instance to be injected
   */
  T create(InternalContext context);
}
是个接口,里边只有一个需要实现的方法"create"。实际上struts2在容器中存放的并不是简单的java bean实例,而是工厂,即实现了InternalFactory的工厂,这样做会带来很大的灵活性,不同的bean的特性可以通过实现不同的工厂来实现,在需要获得bean时,我们只需要调用工厂的create()方法即可,至于create()方法是通过new重新生成对象、还是使用以前生成的、或是从别的地方弄来的,容器是不关心的。比如:我们在配置bean时可以为bean配置个scope(作用域)属性,当scope为singleton时,当前bean创建为单例模式,当scope为request时,当前bean的作用域为request,这样我只需要为这俩种情况创建不同的工厂即可,下面我自己写的一个demo来说明这个问题:


public static void main(String[] args) {
		// TODO Auto-generated method stub
		InternalFactory singleton_factory = new InternalFactory<Object>() {
			Object instance;
	        public Object create(InternalContext context) {
	            if (instance == null) {
	              instance = new Object();
	            }
	            return instance;
	          }
	     };	
	 }
上面我建了个实现了InternalFactory接口的具有单例模式的匿名类singleton_factory,里边加了个属性instance,这样的话当容器想要获得对象调用singleton_factory的create方法时,首先会判断instance是否已经建立,没建立则通过new Object()建立并赋给instance,然后返回instance。这样的话当下次再想要获得这个对象时,就可直接将instance返回去就行。这样就巧妙的实现了个单例的效果。其实struts2中也是这样做的,通过生成不同的匿名工厂类,在将这些匿名工厂类存入容器中,容器在需要获得bean时,只需要调用工厂的create()方法就行,容器不必关心这个工厂管理的是啥样的bean、这个bean有啥特性。有了上面的概念后,我们就来看下struts2是如何将<bean>添加进容器的,F5进入上一篇中说的加载器的register()方法:


public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
        LOG.info("Parsing configuration file [" + configFileName + "]");
        Map<String, Node> loadedBeans = new HashMap<String, Node>();
        for (Document doc : documents) {
            Element rootElement = doc.getDocumentElement();
            NodeList children = rootElement.getChildNodes();
            int childSize = children.getLength();

            for (int i = 0; i < childSize; i++) {
                Node childNode = children.item(i);

                if (childNode instanceof Element) {
                    Element child = (Element) childNode;

                    final String nodeName = child.getNodeName();

                    if ("bean".equals(nodeName)) {
                        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");
                        boolean optional = "true".equals(child.getAttribute("optional"));
                        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;
                        }

                        if (!TextUtils.stringSet(name)) {
                            name = Container.DEFAULT_NAME;
                        }

                        try {
                            Class cimpl = ClassLoaderUtil.loadClass(impl, getClass());
                            Class ctype = cimpl;
                            if (TextUtils.stringSet(type)) {
                                ctype = ClassLoaderUtil.loadClass(type, getClass());
                            }
                            if ("true".equals(onlyStatic)) {
                                // Force loading of class to detect no class def found exceptions
                                cimpl.getDeclaredClasses();
                                containerBuilder.injectStatics(cimpl);
                            } else {
                                if (containerBuilder.contains(ctype, name)) {
                                    Location loc = LocationUtils.getLocation(loadedBeans.get(ctype.getName() + name));
                                    if (throwExceptionOnDuplicateBeans) {
                                        throw new ConfigurationException("Bean type " + ctype + " with the name " +
                                                name + " has already been loaded by " + loc, child);
                                    }
                                }

                                // Force loading of class to detect no class def found exceptions
                                cimpl.getDeclaredConstructors();

                                if (LOG.isDebugEnabled()) {
                                    LOG.debug("Loaded type:" + type + " name:" + name + " impl:" + impl);
                                }
                                containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);
                            }
                            loadedBeans.put(ctype.getName() + name, child);
                        } catch (Throwable ex) {
                            if (!optional) {
                                throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, childNode);
                            } else {
                                LOG.debug("Unable to load optional class: " + ex);
                            }
                        }
                    } else if ("constant".equals(nodeName)) {
                        String name = child.getAttribute("name");
                        String value = child.getAttribute("value");
                        props.setProperty(name, value, childNode);
                    } else if (nodeName.equals("unknown-handler-stack")) {
                        List<UnknownHandlerConfig> unknownHandlerStack = new ArrayList<UnknownHandlerConfig>();
                        NodeList unknownHandlers = child.getElementsByTagName("unknown-handler-ref");
                        int unknownHandlersSize = unknownHandlers.getLength();

                        for (int k = 0; k < unknownHandlersSize; k++) {
                            Element unknownHandler = (Element) unknownHandlers.item(k);
                            unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name")));
                        }

                        if (!unknownHandlerStack.isEmpty())
                            configuration.setUnknownHandlerStack(unknownHandlerStack);
                    }
                }
            }
        }
    }
解析xml的代码就不说了,直接看这句containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope),首先生成个LocatableFactory对象,并将<bean>的所有属性值都做为构造参数传了进去,然后再连同

ctype、name、scope作为参数调用containerBuilder.factory()方法。containerBuilder顾名思义,是个容器建造器,这个建造器和我们上篇说的关于包(package)的各种建造器一样,唯一的区别是它并不是作为内部类,而是单独定义的一个类文件,这俩种

做法区别不大,F5进入containerBuilder.factory():


public <T> ContainerBuilder factory(final Class<T> type, final String name,
      final Factory<? extends T> factory, Scope scope) {
    InternalFactory<T> internalFactory =
        new InternalFactory<T>() {

      public T create(InternalContext context) {
        try {
          Context externalContext = context.getExternalContext();
          return factory.create(externalContext);
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }

      @Override
      public String toString() {
        return new LinkedHashMap<String, Object>() {{
          put("type", type);
          put("name", name);
          put("factory", factory);
        }}.toString();
      }
    };

    return factory(Key.newInstance(type, name), internalFactory, scope);
  }

第1句,首先创建个InternalFactory接口的匿名类,并实现了create()方法,可以看到和我上面demo中的做法是一样的。最后一句factory(Key.newInstance(type, name), internalFactory, scope),首先通过Key.newInstance(type, name)句,利用type、name生成个Key实例,调用了factory的重载方法,F5进入:


private <T> ContainerBuilder factory(final Key<T> key,
      InternalFactory<? extends T> factory, Scope scope) {
    ensureNotCreated();
    checkKey(key);
    final InternalFactory<? extends T> scopedFactory =
        scope.scopeFactory(key.getType(), key.getName(), factory);
    factories.put(key, scopedFactory);
    if (scope == Scope.SINGLETON) {
      singletonFactories.add(new InternalFactory<T>() {
        public T create(InternalContext context) {
          try {
            context.setExternalContext(ExternalContext.newInstance(
                null, key, context.getContainerImpl()));
            return scopedFactory.create(context);
          } finally {
            context.setExternalContext(null);
          }
        }
      });
    }
    return this;
  }
第1句,调用scope.scopeFactory(key.getType(), key.getName(), factory)方法又返回个工厂scopedFactory ,然后通过factories.put(key, scopedFactory)将工厂压入容器,F5进入Scope 类


public enum Scope {

  /**
   * One instance per injection.
   */
  DEFAULT {
    @Override
            <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name,
        InternalFactory<? extends T> factory) {
      return factory;
    }
  },

  /**
   * One instance per container.
   */
  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()) {
            if (instance == null) {
              instance = factory.create(context);
            }
            return instance;
          }
        }

        @Override
        public String toString() {
          return factory.toString();
        }
      };
    }
  },
  .
  .
  .
  //省略

可以看到Scope是个枚举类型,它的每个枚举都是个scopeFactory方法,从上面的代码中可以看到有俩个枚举值(实际代码中还有其他值):DEFAULT、SINGLETON,即默认的(DEFAULT)和单例的(SINGLETON)。DEFAULT的scopFactory()方法中什么也

没做,只是原封不动的将factory返回去。SINGLETON的scopFactory()方法中在原工厂factory的基础上在外面又封装了工厂,并且添加了instance属性来实现单例模式,和文章开头讲的方法一样。最后在将封装后的工厂返回去替换掉了原来的工厂。

回到上一个factory方法,执行完scope.scopeFactory(key.getType(), key.getName(), factory)语句并返回新的工厂后,调用factories.put(key, scopedFactory)将工厂添加到容器中,此处的factories和com.opensymphony.xwork2.inject.ContainerImpl类

中的factories类型是一样的,都是Map类型。这样就成功的将一个<bean>添加到容器中。当所有bean都加载完毕后containerBuilder会调用create()方法生成一个容器,同时将factories传入到该容器中,如下:


public Container create(boolean loadSingletons) {
    ensureNotCreated();
    created = true;
    final ContainerImpl container = new ContainerImpl(
        new HashMap<Key<?>, InternalFactory<?>>(factories));
    if (loadSingletons) {
      container.callInContext(new ContainerImpl.ContextualCallable<Void>() {
        public Void call(InternalContext context) {
          for (InternalFactory<?> factory : singletonFactories) {
            factory.create(context);
          }
          return null;
        }
      });
    }
    container.injectStatics(staticInjections);
    return container;
  }

第1句实例化一个容器对象container,并且将factories作为参数传入到容器中,最后将container返回。其中if语句调用回调函数,将属于单例模式的bean事先调用create方法。singletonFactories变量中存放的是容器中属于单例模式的工厂的引用。这样一个完整的struts2容器就生成了。

需要注意下,在读其他的源码中会发现InternalFactory最后会被封装为n层,即工厂外面又被包了一个工厂......, 不必混乱,最终都只不过是外层的调用内层的create方法,内层的在调用内内层的create()方法而已。

接下来看下容器建造好后,怎样去获得其中的bean,以DefaultConfiguration的reloadContainer()方法中的这句objectFactory = container.getInstance(ObjectFactory.class)为例,ObjectFactory.class表明要找type为ObjectFactory.class的bean,F5进入:


public <T> T getInstance(final Class<T> type) {
    return callInContext(new ContextualCallable<T>() {
      public T call(InternalContext context) {
        return getInstance(type, context);
      }
    });
  }
直接调用callInContext(),参数是个实现ContextualCallable接口的匿名类,其中实现了call()方法,可以看出call其实是个回调方法。call()方法内调用了重载方法getInstance(type, context),content是个上下名,里边存放着包括容器在内的对象。F5在进入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]);
    }
  }
第一句先判断当前线程中是否存在上下文对象InternalContext,没有则通过reference[0] = new InternalContext(this)句创建,同时将容器自身this作为构造参数传入。接着就调用了上面的回调方法callable.call((InternalContext)reference[0]),将上下文对象作为参数传入,由上面的代码可知callable对象的call方法中调用getInstance的重载方法getInstance(type, context),F5进入:


<T> T getInstance(Class<T> type, InternalContext context) {
    return getInstance(type, DEFAULT_NAME, context);
  }
该重载方法getInstance(type, DEFAULT_NAME, context),DEFAULT_NAME是默认的name值,当我们在配置<bean>时,如果没写name属性,则在将该bean加入到容器中时默认将name赋值为DEFAULT_NAME,F5进入:


<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);
    }
  }
这个方法就很清楚了,首先通过type、name生成key,然后通过getFactory(key)从容器的Map集合中检索到工厂,getFactory(key).create(context)句调用工厂的create方法获得bean,在将这个bean往上返回去就是最终我们要获得的。

ContainerImpl容器中对getInstance()方法进行了多种重载,实现用多种不同方式获得bean。


有关容器的东西就基本说完了,希望能对大家理解struts2有所帮助。