seasar 底层DI实现

公司的系统采用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),根据自动注入,就可以把所有的类都实例化了。

    你可能感兴趣的:(Web,框架,seasar)