读书人

Seasar2 DI流入的底层实现

发布时间: 2012-11-26 11:48:49 作者: rapoo

Seasar2 DI注入的底层实现
Seasar2 DI注入的底层实现

蒋彪 2012-11-21

1. 前言

Searsar2 是一个日本的开源DI容器,类似于spring。

项目组有个同学问我,Searsar2的底层是如何实现DI机制的。于是我调查了一下,调查结果不敢独享。

和Spring一样,Searsar2可以有很多种部署的方式,在web工程中一般以servlet的方式, 在独立jar包部署的情况下,可以硬编码部署。

这边以硬编码举例。按照Searsar2 官方手册,下载一系列jar包,配好一堆配置文件之后(具体搭建方法请自行上网调查),用以下的代码就可以启动该容器

 SingletonS2ContainerFactory.setConfigPath("app.dicon"); SingletonS2ContainerFactory.init(); S2Container container = SingletonS2ContainerFactory.getContainer();


2. 读取配置文件,生成DI容器对象

※本文作者蒋彪 原发于http://blog.csdn.net/nanjingjiangbiao 转载请注明

A. SingletonS2ContainerFactory.init()方法中

container = S2ContainerFactory.create(configPath);

在此处生成单例的(static)的容器对象

B. SingletonS2ContainerFactory.create()方法中

if (!initialized) {// 初期化DI容器工厂,读取DI容器本身的配置文件            configure();        }//用DI容器工厂中关联的特定的provider        return getProvider().create(path);

C. SingletonS2ContainerFactory.configure()方法中

在class根目录下找[s2container.dicon], 实例化DI容器工厂

首先,生成该工厂配套的 provider(用来加载和注入组件)和 Builder(用来读取配置文件)

 if (provider == null) {            provider = new DefaultProvider();        }        if (defaultBuilder == null) {                defaultBuilder = new XmlS2ContainerBuilder();        }

一般说来初期化provider /defaultBuilder 都是null,除非从SingletonS2ContainerInitializer启动DI容器的时候,可以指定

D. 接下来,读取[s2container.dicon],自定义DI容器工厂

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);


这边提一下,这边所谓的自定义DI容器工厂,其实是Searsar2框架提供的一个plugin功能,也就是可以自定义DI容器工厂,比如在配置文件中定义自己的provider,自己的classloader,并且可以指定该容器可否热部署。但是如果不想自定义容器,完全可以不写[s2container.dicon],用系统默认的DI工厂

再多说几句, 和Spring类似,Searsar2也推出了自己容器热部署机制,比如在Web环境下,当某个class或者jar包被替换掉之后,DI容器会重classloader一把,具体的实现,Searsar2是通过提供一个HotDeploy专用的provider。(官网上说对多线程支持不好,要慎用)

http://s2container.seasar.org/2.4/ja/S2.4SmartDeploy.html#HotDeploy

E. 用provider 实例化DI容器实例

SingletonS2ContainerFactory.create()方法中

return getProvider().create(path);

出发,默认到DefaultProvider中的create(path)

F. 如果没有自定义classLoader, 默认用当前jvm环境下的classLoader

if (configurationContainer != null                    && configurationContainer                            .hasComponentDef(ClassLoader.class)) {                classLoader = (ClassLoader) configurationContainer                        .getComponent(ClassLoader.class);            } else {                classLoader = Thread.currentThread().getContextClassLoader();            }


G. 开始实例化DI容器

S2Container container = StringUtil.isEmpty(path) ? new S2ContainerImpl()                    : build(path, classLoader);


一般默认都会有app.dicon,因此会走进build(path, classLoader)

H. 真正的实例化,发生在

final S2Container container = getBuilder(ext).build(realPath,                        classLoader);


在没有自定义的情况下,实际是在XmlS2ContainerBuilder.build中执行

I. 接下来的代码就稍有奇幻色彩

protected S2Container parse(final S2Container parent, final String path) {        final SaxHandlerParser parser = createSaxHandlerParser(parent, path);        final InputStream is = getInputStream(path);        try {            return (S2Container) parser.parse(is, path);        } finally {            InputStreamUtil.close(is);        }    }


读取xml采用的是Sax开源项目,Sax的原理请大家自己上网搜

J. 生成标准的SaxParser , 生成自定义的SaxHandler

final SAXParser saxParser = SAXParserFactoryUtil.newSAXParser(factory);
final SaxHandler handler = new SaxHandler(rule);

这里想提一下,其中的rule

protected S2ContainerTagHandlerRule rule = new S2ContainerTagHandlerRule();

是把DI容器中配置文件可能出现的所有关键字,都作了tagHandler的映射,

在S2ContainerTagHandlerRule中能看到定义如下

        addTagHandler("/components", new ComponentsTagHandler());        addTagHandler("component", new ComponentTagHandler());        addTagHandler("arg", new ArgTagHandler());        addTagHandler("property", new PropertyTagHandler());        addTagHandler("meta", new MetaTagHandler());        addTagHandler("initMethod", new InitMethodTagHandler());        addTagHandler("destroyMethod", new DestroyMethodTagHandler());        addTagHandler("aspect", new AspectTagHandler());        addTagHandler("interType", new InterTypeTagHandler());addTagHandler("/components/include", new IncludeTagHandler());


K. 将SaxHandler和saxParser包装起来,以SaxHandlerParser的面目返回

return new SaxHandlerParser(handler, saxParser);

L. 解析配置文件,生成DI容器实例,就发生在SaxHandlerParser.parse()中

最终的有效代码是

public static void parse(SAXParser parser, InputSource inputSource,            DefaultHandler handler) {        try {            parser.parse(inputSource, handler);        } catch (SAXException e) {            throw new SAXRuntimeException(e);        } catch (IOException e) {            throw new IORuntimeException(e);        }    }

用SAX标准的SAXParser.parser进行配置文件(inputSource)解析,然后把解析成功的对象mapping塞入handler中

下面基本上就是SAX开源框架的事情,根据配置文件的元素命名,向下循环解析,找到命中的tag,就调用对应的TagHandler

比如我们以常见的components举例,一般在配置文件中都配成

<component name="xxxConfig" class="xxxxxConfigImpl"><property name="xxx">"xxx"</property></component>


在ComponentsTagHandler中如下解析该段xml,把情报塞入TagHandlerContext

public void start(TagHandlerContext context, Attributes attributes) {        S2Container container = createContainer();        String path = (String) context.getParameter("path");        container.setPath(path);        String namespace = attributes.getValue("namespace");        if (!StringUtil.isEmpty(namespace)) {            container.setNamespace(namespace);        }        String initializeOnCreate = attributes.getValue("initializeOnCreate");        if (!StringUtil.isEmpty(initializeOnCreate)) {            container.setInitializeOnCreate(Boolean.valueOf(initializeOnCreate)                    .booleanValue());        }        S2Container parent = (S2Container) context.getParameter("parent");        if (parent != null) {            container.setRoot(parent.getRoot());        }        context.push(container);    }


3. 用DI容器注入所有对象

A. S2ContainerImpl.init()方法中

先循环读取子元素,递归init

for (int i = 0; i < getChildSize(); ++i) {                getChild(i).init();            }


再循环,初期化组件

for (int i = 0; i < getComponentDefSize(); ++i) {                getComponentDef(i).init();            }


其中每个Component都是你配置文件中定义好的

B. 组件的初期化最后落地到ComponetDefImpl.init中

public void init() {        getConcreteClass();        getComponentDeployer().init();}


C. getConcreteClass 用来做AOP

concreteClass = AopProxyUtil.getConcreteClass(this);

具体的原理是,根据配置文件中定义的pointcut, 加强和混淆class文件。下面分支很多,不讲

D. getComponentDeployer()的生成

默认情况下是,在InstanceSingletonDef.createComponentDeployer中执行

return ComponentDeployerFactory                .createSingletonComponentDeployer(componentDef);


E. 最后的init发生在SingletonComponentDeployer的assemble()中

private void assemble() {        if (instantiating) {            throw new CyclicReferenceRuntimeException(getComponentDef()                    .getComponentClass());        }        instantiating = true;        try {            component = getConstructorAssembler().assemble();        } finally {            instantiating = false;        }        getPropertyAssembler().assemble(component);        getInitMethodAssembler().assemble(component);}


F. 在默认情况下,对象的instance由以下代码生成

protected Object assembleDefault() {        Class clazz = getComponentDef().getConcreteClass();        Constructor constructor = ClassUtil.getConstructor(clazz, null);        return ConstructorUtil.newInstance(constructor, null);}


以下代码负责把参数注入进去

getPropertyAssembler().assemble(component);


在这里需要注意一点,如果代码中注解,比如

@Resource(name = "JdbcManager")

此时的注入就发生在

AutoPropertyAssembler.assemble()中     PropertyDef propDef = cd.getPropertyDef(i);            AccessTypeDef accessTypeDef = propDef.getAccessTypeDef();            accessTypeDef.bind(cd, propDef, component);


最后底层是调用OGNL达到注入的效果,name一致即注入

#以上

※本文作者蒋彪 原发于http://blog.csdn.net/nanjingjiangbiao 转载请注明


读书人网 >软件架构设计

热点推荐