容器,一般来说就是存放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有所帮助。