`
dhongwu
  • 浏览: 8103 次
社区版块
存档分类
最新评论

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),根据自动注入,就可以把所有的类都实例化了。
    分享到:
    评论

    相关推荐

      seasar2.PPT

      在Seasar2中,DI通过配置文件或注解来定义对象之间的依赖关系,使得在运行时可以动态地改变对象的配置,从而实现不同环境下的灵活部署。 例如,一个简单的DI示例包括一个接口`Greeting`,它的实现类`GreetingImpl`...

      seasar

      数据访问对象(DAO)模式是数据库操作的标准设计模式,Seasar的S2DAO模块实现了这一模式,提供了一种统一的方式来处理各种数据库操作。它封装了SQL的编写和执行,减轻了开发者的工作负担。S2DAO支持多种数据库,包括...

      seasar2中文文档

      为了理解Seasar2框架的基本概念及如何快速启动一个项目,我们首先介绍一个简单的示例:实现一个问候功能。在这个过程中,我们将学习Seasar2中的核心组件之一——`S2Container`。 ##### 登场人物 - **问候语类** (`...

      Seasar2最新版

      Seasar2还提供了AOP(面向切面编程)支持,通过定义切面和通知,可以在不修改原有代码的情况下,实现如日志记录、权限检查等功能。此外,Seasar2还集成了测试框架,使得单元测试和集成测试变得更加方便。 总的来说...

      seasar2构建工程

      7. **S2Aspect**:Seasar2的AOP功能允许开发者定义切面,以实现如日志记录、性能监控等跨切面关注点。 8. **配置文件**:Seasar2项目中通常包含多个配置文件,如`s2-tight coupling.xml`、`s2-component.xml`等,...

      小日本的开源框架 Seasar2

      Seasar2是一个源自日本的开源框架,其设计目标是为Java开发者提供轻量级的容器服务,以提高开发效率和代码质量。与许多其他轻量级容器不同,Seasar2强调了无需编写配置文件的特性,它采用了Convention over ...

      seasar2

      1. **S2Container**:这是Seasar2的核心,它负责管理应用中的对象,实现了依赖注入(Dependency Injection,DI)和控制反转(Inversion of Control,IoC)。通过S2Container,开发者可以方便地配置和管理对象的生命...

      seasar实例教程

      总的来说,Seasar2是一个强大的Java开发框架,它通过减少配置文件、实现热部署以及支持多种扩展功能,为开发者提供了便捷、高效的开发环境。对于希望简化开发流程并提升生产力的Java开发者,Seasar2无疑是一个值得...

      Seasar2的英文介绍

      Seasar2的核心设计理念是组件化和容器化,通过提供一个统一的框架来管理对象的生命周期和依赖关系,使得开发者能够更加专注于业务逻辑的实现,而不是基础设施的搭建。 Seasar2框架包含了多个子项目,其中Teeda...

      seasar for .net

      Seasar for .NET 的核心组件 S2Container.NET 是一个类似于 IoC(Inversion of Control)或 DI(Dependency Injection)容器,它负责管理对象的生命周期和依赖关系。S2Container.NET 1.3.14 版本可能包含以下功能和...

      seasar2 官方例子代码

      Seasar2是一个已退役的Java应用框架,它旨在简化企业级Java开发,提供了一系列的工具和组件,包括数据库访问、事务管理、IOC(控制反转)和AOP(面向切面编程)等功能。Seasar2的核心组件包括S2Container、S2DAO、S2...

      基本Seasar2 Web工程项目源码

      Seasar2其实就是类似于spring的一个提供DI功能的开源框架,但比Sping轻量级。 并且同“其它轻量级容器”不同的是,“完全不需要书写设定文件”,“就算是应用程序发生改动也无需再次起动即可直接识别变更,因此具有...

      seasar教程,java开发框架之一

      seasar教程,最流行的java开发框架之一,再过几年可能成为主流. struts与Spring与Hibernate的简化版本,国外很多大公司己经用了几年了,NEC,LG的软件开发都是用这个的。兄弟们快学吧

      Seasar2其实就是类似于Spring的一个开源框架

      3. **轻量级容器**:Seasar2作为轻量级容器,专注于提供最小化的依赖管理和组件管理,使得开发者可以更加专注于业务逻辑的实现而不是框架本身的复杂性。 #### 三、安装与配置 ##### 安装步骤: - **JDK版本要求**...

      seasar 的一个action处理

      日本框架seasar 的一个action处理

      seasar s2dao

      Seasar S2DAO 是一个Java ORM(对象关系映射)框架,它简化了数据库操作,使得开发者无需深入了解JDBC即可进行高效的数据访问。S2DAO的主要特点和功能如下: 1. **无需XML进行O/R Mapping**: S2DAO区别于其他框架...

      seasar框架开发常用数据关系组件

      Seasar框架是一个源自日本的开源Java Web框架,它旨在简化Web应用程序的开发并提高开发效率。Seasar提供了多个组件,其中一个是DOMA(Database Object Mapping Accessor),这是一个强大的数据访问层,它允许开发者...

      seasar2_pj

      Seasar2虽然现在已经不太活跃,但它在当时对Java社区的贡献是显著的,它的设计理念和实现方式对后来的框架如Spring有着深远的影响。对于学习和理解Java企业级应用开发,尤其是容器管理和面向切面编程,Seasar2是一个...

      Seasar2_3_en.pdf

      该框架的主要目标是简化Java应用程序的开发过程,减少开发过程中对配置文件(尤其是XML配置文件)的依赖,并提供一种更简单的方式来实现依赖注入(Dependency Injection, DI)。Seasar2.3版本作为该系列的一个重要...

    Global site tag (gtag.js) - Google Analytics