(注:建议在阅读本文前先去阅读下官方提供的文档 http://s2container.seasar.org/2.4/en/DIContainer.html)
察看Seasar web项目下的web.xml。默认的servlet是TeedaServlet。 TeedaServelet实际上是继承S2containerServlet。在初始化servlet的过程中,调用了S2containerServlet的init函数,其中采用策略模式,调用了策略类SingletonS2ContainerInitializer的initialize()函数。该函数内实现了对 app.dicon文件中所有包含的component的初始化和注入工作。
(注:如果不理解component和dicon的概念,请先浏览官方文档再继续阅读)
SingletonS2ContainerInitializer:initialize函数如下:
if (!StringUtil.isEmpty(configPath)) { SingletonS2ContainerFactory.setConfigPath(configPath); } if (ComponentDeployerFactory.getProvider() instanceof ComponentDeployerFactory.DefaultProvider) { ComponentDeployerFactory .setProvider(new ExternalComponentDeployerProvider()); } HttpServletExternalContext extCtx = new HttpServletExternalContext(); extCtx.setApplication(application); SingletonS2ContainerFactory.setExternalContext(extCtx); SingletonS2ContainerFactory .setExternalContextComponentDefRegister(new HttpServletExternalContextComponentDefRegister()); SingletonS2ContainerFactory.init();
configPath的值就是"app.dicon",是在web.xml中定义的。代码的第一句话就是设定ConfigPath。关于第二句,需要先理解以下概念:
引用
public class GreetingMain2 {
private static final String PATH =
"examples/di/dicon/GreetingMain2.dicon";
public static void main(String[] args) {
S2Container container =
S2ContainerFactory.create(PATH);
GreetingClient greetingClient = (GreetingClient)
container.getComponent("greetingClient");
greetingClient.execute();
}
}
private static final String PATH =
"examples/di/dicon/GreetingMain2.dicon";
public static void main(String[] args) {
S2Container container =
S2ContainerFactory.create(PATH);
GreetingClient greetingClient = (GreetingClient)
container.getComponent("greetingClient");
greetingClient.execute();
}
}
可以看到调用S2container类getComponent函数即可获得容器中生成的GreetingClient类对象。
值得注意的是,每一个dicon文件都会有自己的container实例来管理。如果dicon文件中include其他dicon, 则可以通过调用getChild()来获得子dicon的container实例。
... private ConstructorAssembler constructorAssembler; private PropertyAssembler propertyAssembler; private MethodAssembler initMethodAssembler; private MethodAssembler destroyMethodAssembler; ...
在componentDeployer生成类对象过程中,通过策略模式委托assembler完成相应的生成工作。
回到initialize()函数上来。第二句要给ComponentDeployerFactory中的provider赋值为ExternalComponentDeployerProvider,是因为seasar的web项目和seasar的普通Java项目不同,普通Java项目中component只需要支持PROTOTYPE,SINGLETON两种实例类型,而web还需要支持APPLICATION,SESSION,REQUEST等实例类型,所以要使用ExternalComponentDeployerProvider。
接下来最核心的代码是最后一句SingletonS2ContainerFactory.init()。
container = S2ContainerFactory.create(configPath); if (container.getExternalContext() == null) { if (externalContext != null) { container.setExternalContext(externalContext); } } else if (container.getExternalContext().getApplication() == null && externalContext != null) { container.getExternalContext().setApplication( externalContext.getApplication()); } if (container.getExternalContextComponentDefRegister() == null && externalContextComponentDefRegister != null) { container .setExternalContextComponentDefRegister(externalContextComponentDefRegister); } container.init();
container = S2ContainerFactory.create(configPath)用来生成app.dicon的s2container容器并把dicon文件中的component注册到容器中,然后container.init()执行dicon文件中component的生成。
首先是create函数中:
public static synchronized S2Container create(final String path) { if (StringUtil.isEmpty(path)) { throw new EmptyRuntimeException("path"); } if (!initialized) { configure(); } return getProvider().create(path); }
调用了configure()函数(其实在S2containerFactory类载入时运行的static代码段就调用了 ):
public static void configure() { final String configFile = System.getProperty(FACTORY_CONFIG_KEY, FACTORY_CONFIG_PATH); configure(configFile); }
这里的configFile是"s2container.dicon"。这个dicon文件及其子dicon文件里声明的组件都是用来为app.dicon中的组件服务的,主要是提供一些AOP的设定。(关于Seasar的AOP,请查阅官方文档 http://s2container.seasar.org/2.4/en/aop.html)
在进行下一步之前,先来查看一下s2container.dicon的结构。s2container.dicon是Seasar web项目默认提供的。其内容如下:
引用
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
"http://www.seasar.org/dtd/components24.dtd">
<components>
<include condition="#ENV == 'ut'" path="warmdeploy.dicon"/>
<include condition="#ENV == 'ct'" path="hotdeploy.dicon"/>
<include condition="#ENV != 'ut' and #ENV != 'ct'" path="cooldeploy.dicon"/>
</components>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
"http://www.seasar.org/dtd/components24.dtd">
<components>
<include condition="#ENV == 'ut'" path="warmdeploy.dicon"/>
<include condition="#ENV == 'ct'" path="hotdeploy.dicon"/>
<include condition="#ENV != 'ut' and #ENV != 'ct'" path="cooldeploy.dicon"/>
</components>
在实际运行中,是调用hotdeploy.dicon文件,这个dicon隐藏在s2-framework-XX.jar中。内容如下:
引用
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
"http://www.seasar.org/dtd/components24.dtd">
<components>
<include path="convention.dicon"/>
<include path="customizer.dicon"/>
<include path="creator.dicon"/>
<component class="org.seasar.framework.container.hotdeploy.HotdeployBehavior"/>
</components>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
"http://www.seasar.org/dtd/components24.dtd">
<components>
<include path="convention.dicon"/>
<include path="customizer.dicon"/>
<include path="creator.dicon"/>
<component class="org.seasar.framework.container.hotdeploy.HotdeployBehavior"/>
</components>
这四行存在依次依赖关系。熟悉seasar注入机制的就知道,这意味着每一个component的生成都会注入之前所依赖的component。在hotdeploy.dicon声明的组件是对以后用户自定义的app.dicon中的component作一些设定,主要是AOP的设定。比如以后用户自己定义的Page类,就会对Page类中的do*, initialize, prerender函数添加j2ee.requireTx的transaction AOP。(关于j2ee.requireTx及更多的Seasar transaction机制,请查阅官方文档 http://s2container.seasar.org/2.4/en/tx.html)
回到configure(configureFile)函数:
public static synchronized void configure(final String configFile) { if (configuring) { return; } configuring = true; if (provider == null) { provider = new DefaultProvider(); } if (defaultBuilder == null) { defaultBuilder = new XmlS2ContainerBuilder(); } if (ResourceUtil.isExist(configFile)) { final S2ContainerBuilder builder = new XmlS2ContainerBuilder(); configurationContainer = builder.build(configFile); configurationContainer.init(); Configurator configurator; if (configurationContainer.hasComponentDef(Configurator.class)) { configurator = (Configurator) configurationContainer .getComponent(Configurator.class); } else { configurator = new DefaultConfigurator(); } configurator.configure(configurationContainer); } DisposableUtil.add(new Disposable() { public void dispose() { S2ContainerFactory.destroy(); } }); configuring = false; initialized = true; }
这个函数就是负责s2container.dicon文件的container的生成和初始化。builder.build(configFile)用来解析dicon文件(本质上是xml),将其中的<include>标签封装为子container,将其中<component>标签之间的内容封装为ComponentDef实例,并注册进该container中(在这里就是 configurationContainer)。然后comfigurationContainer.init()函数的作用实际上和之前的container.init()函数是差不多的。只不过这个是s2container.dicon的容器初始化,而后者是app.dicon容器的初始化。该init()函数核心代码片断如下:
for (int i = 0; i < getChildSize(); ++i) { getChild(i).init(); } for (int i = 0; i < getComponentDefSize(); ++i) { getComponentDef(i).init(); }
首先是递归地调用子container的init函数(每个container对应一个include的子dicon) ,其次是对本dicon中注册的component进行实例生成。在第二步 getComponentDef(i).init()的代码如下:
public void init() { getConcreteClass(); getComponentDeployer().init(); }
其中getConreteClass函数是把AOP的行为封装在要生成的实际类对象(例如GreetingClient对象)的相关函数上(在dicon中AOP绑定的函数)。而getComponentDeployer().init()则是采用策略模式(前文已经介绍)由componentDef中的componentDeployer来实现实际类对象的生成,其中依次调用它的构造函数、属性(如果属性是私有则调用setter)注入和@initMethod函数的调用。
多说一句,在对dicon文件解析的build过程中,根据每个component定义的 "instance"来定义相应componentDef的componentDeployer(如instance=singleton则componentDeployer=new SingletonComponentDeployer() )。
重新回到S2ContainerFactory的init函数。configurationContainer.init()之后,又设定了一个DefaultConfigurator,并运行configurator.configure(configurationFactory)。代码如下:
public void configure(final S2Container configurationContainer) { provider = createProvider(configurationContainer); defaultBuilder = createDefaultBuilder(configurationContainer); setupBehavior(configurationContainer); setupDeployer(configurationContainer); setupAssembler(configurationContainer); }
这里是为下一步app.dicon的container容器初始化作准备工作。将S2ContainerBehavior(这个类的功能类似于ComponentDefFactory)、ComponentDeployerFactory和AssemblerFactory的provider设定好。事实上在实际过程中,只有S2ContainerBehavior的provider设定为org.seasar.framework.container.hotdeploy.HotdeployBehavior。这个HotdeployBehavior实例是在s2container.dicon下的子dicon文件hotdeploy.dicon中定义的(此时s2container.dicon相关的组件已经初始化完成)。其余两个provider仍是默认提供的provider。
configurationContainer容器初始化完成以后,跳回S2ContainerFactory的create函数,执行最后一句 return getProvider().create(path)。这个是初始化app.dicon的container,相关代码如下:
public S2Container create(final String path) { ClassLoader classLoader; if (configurationContainer != null && configurationContainer .hasComponentDef(ClassLoader.class)) { classLoader = (ClassLoader) configurationContainer .getComponent(ClassLoader.class); } else { classLoader = Thread.currentThread().getContextClassLoader(); } S2Container container = StringUtil.isEmpty(path) ? new S2ContainerImpl() : build(path, classLoader); if (container.isInitializeOnCreate()) { container.init(); } return container; }
初始化过程和configurationContainer的初始化过程是一样的。都是先解析文件(build函数)然后初始化(init函数),不过在实际运行中,init并不在这一步进行。
回到SingletonS2Factory的init函数。app.dicon的container的init函数是在这里调用。这样就完成了应用程序容器的初始化。
而在之后的web交互中,每一个request都会被过滤器HotdeployFilter和S2containerFilter拦截(感觉也没有做什么实际性的过滤工作,读者可自行研究)。
对组件的调用也是通过container.getComponent函数完成的。相关代码如下:
public Object getComponent(Object componentKey) { assertParameterIsNotNull(componentKey, "componentKey"); ComponentDef cd = S2ContainerBehavior.acquireFromGetComponent(this, componentKey); if (cd == null) { return null; } return cd.getComponent(); }
进入S2ContainerBehavior.acquireFromGetComponent(this,
componentKey),代码如下:
public static ComponentDef acquireFromGetComponent(S2Container container, final Object key) { return getProvider().acquireFromGetComponent(container, key); }
注意此时的provider是HotdeployBehavior类型。这里的aquireFromGetComponent函数会调用HotdeployBehavior的getComponentDef函数,完成获取ComponentDef的工作,。在HotdeployBehavior的acquireFromGetComponent函数中,就会把在前文中介绍的hotdeploy.dicon中声明的AOP设定添加进componentDef中。
多说一句,所谓HotDeploy,实际上他的本意是类的实例不是以<component>在app.dicon声明,而是预先设置好相应的AOP(这一部分是在customer.dicon中定义)和Creator(这一部分是在creator.dicon定义)。然后通过将一个HotdeployBehavior实例赋值到S2ContainerBehavior的一个静态成员变量,通过这个静态变量的穿针引线,在需要调用container.getComponent (XXClass)函数的时候利用这个HotdeployBehavior察看是否XXClass是否符合一定的命名规则(主要是看类的路径是否有相应的creator处理以及类的名字后缀是否合法,比如jp.co.worksap.cim.service这个路径下的类就归ServiceCreator生成,而该目录下RunSericeImpl是合法命名,RunServiceImpl1就不是合法命名,具体规则可察看ServiceCreator的定义),如果一切符合则生成实例。这样实际上在app.dicon中并没有定义该类的<component>却可以最终生成该类的实例,这就是HotDeploy。
这里可能有人会有疑问,如果我在app.dicon或者其子dicon下定义了某个component,而又符合Creator可以自动构造的命名规则的话,究竟是这个component的ComponentDef是谁来生成呢?查看HotdeployBehavior类的getCompoonentDef类,如下:
protected ComponentDef getComponentDef(S2Container container, Object key) { ComponentDef cd = super.getComponentDef(container, key); if (cd != null) { return cd; } if (container != container.getRoot()) { return null; } cd = getComponentDefFromCache(key); if (cd != null) { return cd; } if (key instanceof Class) { cd = createComponentDef((Class) key); } else if (key instanceof String) { cd = createComponentDef((String) key); if (cd != null && !key.equals(cd.getComponentName())) { logger.log("WSSR0011", new Object[] { key, cd.getComponentClass().getName(), cd.getComponentName() }); cd = null; } } else { throw new IllegalArgumentException("key"); } if (cd != null) { register(cd); S2ContainerUtil.putRegisterLog(cd); cd.init(); } return cd; }
重点看第一行,进入这个函数,有
protected ComponentDef getComponentDef(final S2Container container, final Object key) { return ((S2ContainerImpl) container).internalGetComponentDef(key); }
再进入S2ContainerImpl的internalGetComponentDef函数, 有
protected ComponentDef internalGetComponentDef(Object key) { ComponentDefHolder holder = (ComponentDefHolder) componentDefMap .get(key); if (holder != null) { return holder.getComponentDef(); } if (key instanceof String) { String name = (String) key; int index = name.indexOf(NS_SEP); if (index > 0) { String ns = name.substring(0, index); name = name.substring(index + 1); if (ns.equals(namespace)) { return internalGetComponentDef(name); } } } return null; }
这里有个Map叫做componentDefMap, 在解析app.dicon及其子dicon的xml的时候就会将新生成的componentDef添加到这个Map。所以说如果你在app.dicon中自己定义了component,那么seasar会优先取你自己的定义的这个component,而不再用creator来生成。
返回到container.getComponent函数中。在真实的Seasar项目运行过程中,一般首先会调用getComponent(XXPage.class)获得某个相关html的后台Page类。由之前的代码可以知道,我们首先获得XXPage的相关的ComponentDef,然后调用cd.getComponent()获得XXPage的实例。在cd.getComponent()中调用deploy()函数完成Page实例的生成。而deploy()中又分别调用ConstructorAssembler, PropertyAssembler,InitMethodAssmebler等(根据不同的instance决定,比如Prototype和Singleton在生成时就有差异)完成对构造函数的调用,对类的内部属性的自动注入以及initMethod的调用。在对类的内部属性的自动注入中,就把后台的什么Service,Logic,Dao等等也会一层一层迭代地调用container.getComponent(XXClass)。所以说实际上,只需要container.getComponent(XXPageClass),根据自动注入,就可以把所有的类都实例化了。