公司的系统采用Seasar框架。Seasar是一套日本人开发的类似Spring的开源web框架。关于Seasar的中文教程比较少,只有日文和英文的(
http://www.seasar.org/en/)。这几天研究了一下seasar的代码,做点记录怕以后忘记了。也希望能对路人朋友有点帮助。
(注:建议在阅读本文前先去阅读下官方提供的文档
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。关于第二句,需要先理解以下概念:
S2Container类:就是Seasar容器类,用来管理dicon文件生成的对象。在官方文档中的示例代码中关于普通JAVA项目如何使用seasar,有:
引用
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();
}
}
可以看到调用S2container类getComponent函数即可获得容器中生成的GreetingClient类对象。
值得注意的是,每一个dicon文件都会有自己的container实例来管理。如果dicon文件中include其他dicon, 则可以通过调用getChild()来获得子dicon的container实例。
S2containerFactory类:工厂模式,负责生成s2container。
S2ComponentDef类,封装了dicon中的<Component>标签声明的component,S2Container.getComponent函数实际上就是调用S2ComponentDef的getComponent函数来返回真正的对象(如上例中的GreetingClient对象)。
S2ContainerBehavior类:工厂模式用来生成S2ComponentDef类实例。
ComponentDeployer类:每一个ComponentDef实例中都有一个ComponentDeployer,它的作用是真正的去生成指定的类对象(如上例中的GreetingClient类型,该实例是通过Deployer通过反射生成的)。
ComponentDeployerFactory类:工厂模式用来生成ComponentDeployer对象。
Assember类:主要包括ConstructorAssember, PropertyAssembler和MethodAssembler。每个componentDeployer都包括如下assembler
...
private ConstructorAssembler constructorAssembler;
private PropertyAssembler propertyAssembler;
private MethodAssembler initMethodAssembler;
private MethodAssembler destroyMethodAssembler;
...
在componentDeployer生成类对象过程中,通过策略模式委托assembler完成相应的生成工作。
AssemblerFactory类:工厂模式用来生成assembler实例。
Provider类:seasar代码中每一个XXXFactory类都包含一个provider对象,Provider是策略类,用来执行真正的XXX类对象的生成。注意不同的factory之间的provider虽然都叫做"provider",但彼此间并没有联系,都是因为策略模式而分离出来专门用于“生成对象”的。
HotdeployBehavior类:这个命名很容易根S2ContainerBehavior类混淆,其实它是一个用于S2ContainerBehavior的Provider的子类,执行的也是Provider的功能。
回到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>
在实际运行中,是调用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>
这四行存在依次依赖关系。熟悉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),根据自动注入,就可以把所有的类都实例化了。