Seasar DI Container with AOP

DIContainer

DIContainer is a light weight container which does Dependency Injection, and it is occasionally said the IoC Container. Dependency Injection is plainly explained by “Inversion of Control Containers and the Dependency Injection pattern" of Martin Fowler.

Then, let's make the component on the S2Container. The specification of the component is as follows.

  • The function of an automatic numbering is offered.
  • When the numbering key is received as an argument, the value by which the number of counter is increased is returned.
  • When the numbering key which does not exist is specified, the exception is generated.
package examples.dicon.service;

public interface AutoNumber {

    public int next(int numberKey) throws NumberKeyNotFoundRuntimeException;
}

The component of DAO becomes the following specifications.

  • The counter is updateed based on the numbering key.1 is returned when succeeding in the update, 0 is returned when a numbering key does not exist.(Statement.executeUpdate() of JDBC API is assumed.)
  • The value of the latest counter is returned based on the numbering key.
package examples.dicon.dao;

public interface AutoNumberDao {

    public int inclement(int numberKey);

    public int getCurrentNumber(int numberKey);
}

The AutoNumber component needs the reference to the AutoNumberDao component. The constructor is handled this time though there is a method of using the constructor and property to acquire the reference.

AutoNumberImpl

package examples.dicon.service;

import examples.dicon.dao.AutoNumberDao;

public class AutoNumberImpl implements AutoNumber {

    private AutoNumberDao autoNumberDao_;

    public AutoNumberImpl(AutoNumberDao autoNumberDao) {
        autoNumberDao_ = autoNumberDao;
    }

    public int next(int numberKey) throws NumberKeyNotFoundRuntimeException {
        int updateCount = autoNumberDao_.inclement(numberKey);
        if (updateCount == 1) {
            return autoNumberDao_.getCurrentNumber(numberKey);
        } else {
            throw new NumberKeyNotFoundRuntimeException(numberKey);
        }
    }
}

Who will set AutoNumberDao which is constructor's argument? When S2Container is used, S2Container is solved to such a dependency.

Then, let's test AutoNumberImpl. The class which implements AutoNumberDao is necessary to test. Mock is made so that you may not actually connect with the database. S2ではモックを簡単に作成できるようにMockInterceptorが用意されています。

S2Container is made.

S2Container container = new S2ContainerImpl();

S2Container#register(Class componentClass) is used to register the component.

container.register(AutoNumberImpl.class);

AutoNumberDaoのモックを作成して、S2Containerに登録します。コンポーネントのクラスのかわりに直接オブジェクトを登録することもできます。

MockInterceptor mi = new MockInterceptor();
mi.setReturnValue("increment", new Integer(1));
mi.setReturnValue("getCurrentNumber", new Integer(1));
AutoNumberDao dao = (AutoNumberDao) mi.createMock(AutoNumberDao.class);
container.register(dao);

S2Container#getComponent(Class componentClass) is used to acquire the component.

AutoNumber autoNumber = (AutoNumber) container.getComponent(AutoNumber.class);

Being necessary to know with S2Container at least is only this. When the type is an interface, the dependency of the component is automatically solved by the type. Even when property is used, the dependence is automatically similarly solved by the type.

The test case is as follows.

package test.examples.dicon.service;

import org.seasar.framework.aop.interceptors.MockInterceptor;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.impl.S2ContainerImpl;

import examples.dicon.dao.AutoNumberDao;
import examples.dicon.service.AutoNumber;
import examples.dicon.service.AutoNumberImpl;
import examples.dicon.service.NumberKeyNotFoundRuntimeException;
import junit.framework.TestCase;

public class AutoNumberImplTest extends TestCase {

    public AutoNumberImplTest(String arg0) {
        super(arg0);
    }

    public static void main(String[] args) {
        junit.textui.TestRunner.run(AutoNumberImplTest.class);
    }

    public void testNext() {
        S2Container container = new S2ContainerImpl();
        container.register(AutoNumberImpl.class);
        MockInterceptor mi = new MockInterceptor();
        mi.setReturnValue("increment", new Integer(1));
        mi.setReturnValue("getCurrentNumber", new Integer(1));
        AutoNumberDao dao = (AutoNumberDao) mi.createMock(AutoNumberDao.class);
        container.register(dao);
        AutoNumber autoNumber = (AutoNumber) container.getComponent(AutoNumber.class);
        assertEquals("1", 1, autoNumber.next(1));
    }

    public void testNextForNumberKeyNotFound() {
        S2Container container = new S2ContainerImpl();
        container.register(AutoNumberImpl.class);
        MockInterceptor mi = new MockInterceptor();
        mi.setReturnValue(new Integer(0));
        AutoNumberDao dao = (AutoNumberDao) mi.createMock(AutoNumberDao.class);
        container.register(dao);
        AutoNumber autoNumber = (AutoNumber) container.getComponent(AutoNumber.class);
        try {
            autoNumber.next(-1);
            fail("1");
        } catch (NumberKeyNotFoundRuntimeException ex) {
            assertEquals("2", -1, ex.getNumberKey());
        }
    }
}

Type of Dependency Injection

In Dependency Injection, there are Interface Injection, Setter Injection, Constructor Injection, and Method Injection. Method Injection is original of S2.S2 supports all types and the hybrids.

Constructor Injection

The example of passing 0 of int to the constructor of Integer is as follows.

examples/dicon/xml/ConstructorInjection.dicon

<components>
<component name="foo" class="java.lang.Integer">
<arg>0</arg>
</component> <component class="java.util.Date>
<arg>foo</arg>
</component>
</components>

The value is written in the body of the arg tag. Please refer to the manual of OGNL for details. Two or more arg tags are defined when there are two or more arguments. The component tag can be used for the child tag of the arg tag.

examples.dicon.xml.ConstructorInjectionClient

private static final String PATH =
"examples/dicon/xml/ConstructorInjection.dicon";
S2Container container = S2ContainerFactory.create(PATH); System.out.println(container.getComponent("foo")); System.out.println(container.getComponent(Date.class));

To make S2Container, passing XML is specified for the first argument of S2ContainerFactory.create(). It is necessary to include XML in CLASSPATH. The component can be acquired even in the name and the type.

Setter Injection

The example of setting 0 to property time of Date is as follows.

examples/dicon/xml/SetterInjection.dicon

<components>
<component class="java.util.Date">
<property name="time">0</property>
</component>
</components>

Two or more property tags are defined when there is two or more property. The component tag can be used for the child tag of the property tag. Other components can be referred to by specifying the component name for the body of the property tag as well as the arg tag.

examples.dicon.xml.SetterInjectionClient

private static final String PATH =
"examples/dicon/xml/SetterInjection.dicon";
S2Container container = S2ContainerFactory.create(PATH); System.out.println(container.getComponent(Date.class));

Method Injection

The value can be set by calling the method.

examples/dicon/xml/MethodInjection.dicon

<components>
<component class="java.util.HashMap">
<initMethod name="put">
<arg>"aaa"</arg>
<arg>"111"</arg>
</initMethod>
</component>
</components>

The name of the method is specified by the name attribute of the initMethod tag. The usage of the arg tag is similar to constructor's case. Two or more initMethod tag can be defined.

examples.dicon.xml.MethodInjectionClient

private static final String PATH =
"examples/dicon/xml/MethodInjection.dicon";
S2Container container = S2ContainerFactory.create(PATH); Map map = (Map) container.getComponent(Map.class);
System.out.println(map.get("aaa"));

In the timing of S2Container.destroy(), the call method can be specified with the destroyMethod tag. If OGNL is used, the method can be called more easily.

<components>
    <component class="java.util.HashMap">
        <initMethod>#self.put("aaa", "111")</initMethod>
        <initMethod>#out.println("Hello")</initMethod>
    <component>
</components>

#self means the component. #out means System.out.

Definition of component by OGNL

The component can be defined by using OGNL.

<component name="initialContext">new javax.naming.InitialContext()</component>
<component name="dataSource">initialContext.lookup("jdbc/oracle")</component>

AOP

AOP can be applied to the component. The example of applying TraceInterceptor to ArrayList is as follows.

<components>
<component name="traceInterceptor"
class="org.seasar.framework.aop.interceptors.TraceInterceptor"/>
<component class="java.util.ArrayList">
<aspect>traceInterceptor</aspect>
</component> <component class="java.util.Date">
<arg>0</arg>
<aspect pointcut="getTime, hashCode">traceInterceptor</aspect>
</component>
</components>

The name of Interceptor is specified by the body of the aspect tag. The method name is specified for pointcut attribute by the comma delimitation. When the pointcut attribute is not specified, all the methods of the interface which the component inpliments. The regular expression (reqex of JDK1.4) can be used for the method name.

examples.dicon.xml.AopClient

private static final String PATH =
"examples/dicon/xml/Aop.dicon";
S2Container container = S2ContainerFactory.create(PATH);
List list = (List) container.getComponent(List.class);
list.size();
Date date = (Date) container.getComponent(Date.class);
date.getTime();
date.hashCode();
date.toString();

The execution result is as follows.

BEGIN java.util.ArrayList#size()
END java.util.ArrayList#size() : 0
BEGIN java.util.Date#getTime()
END java.util.Date#getTime() : 0
BEGIN java.util.Date#hashCode()
BEGIN java.util.Date#getTime()
END java.util.Date#getTime() : 0
END java.util.Date#hashCode() : 0
BEGIN java.util.Date#getTime()
END java.util.Date#getTime() : 0

Division and include of component definition

If all components are described in one file, the file expands immediately and management becomes difficult. Therefore, the function to divide the definition of the component into the plural and the divided definition are provided for one and the function to bring is provided in S2Container. The definition of each usecase is recommended to be divided.

<components>
    <include path=""Definition of component of usecase 1".dicon"/>
    <include path=""Definition of component of usecase 2".dicon"/>
</components>

Namespace

The namespace can be specified by the namespace attribute of the components tag so that the name should not coflict between two or more component definitions.

foo.dicon

<components namespace="foo">
    <component name="aaa" .../>
    <component name="bbb" ...>
        <arg>aaa</arg>
    </component>
</components>

bar.dicon

<components namespace="bar">
    <component name="aaa" .../>
    <component name="bbb" ...>
        <arg>aaa</arg>
    </component>
    <component name="ccc" ...>
        <arg>foo.aaa</arg>
    </component>
</components>

app.dicon

<components>
    <include path="foo.dicon"/>
    <include path="bar.dicon"/>
</components>

It is possible to refer in the same component definition without the namespace. When the component of other component definitions is referred, namespace dot(.) is applied to the head of the component name. Because the namespace is different, foo.aaa and bar.aaa are recognized as a different component though give the same name. It is recommended that the name of the definition file be made namespace.dicon as a custom.

Instance management

The instance of the component managed with the container is managed with Singleton for default. The component returned by S2Container.getComponent() is always a meaning of the same. When you want the component newly made to be returned whenever S2Container.getComponent() is called, prototype is specified for instance attribute of the component tag. As for the instance attribute, singleton becomes default.

When the dependency is injected into the component outside the container, S2Container.injectDependency(Object outerComponent,Class componentClass) and S2Container.injectDependency(Object outerComponent,String componentName) are used. Outer is specified for instance attribute for the component made on the outside.

<components>
    <component name="employeeService" class="..."/>
    <component name="home" class="..HomePage" instance="outer">
        <property name=employeeService>employeeService</property>
    </componet>
</components>

Lifecycle

The lifecycle of the component can be managed with the container by using the initMethod tag and the destroyMethod tag. The method of the specification with the initMethod tag when the container is begun is called. The method of the specification with the destroyMethod tag when the container is ended is called. Even if destroyMethod is specified, except when the instance attribute is singleton, S2Container is disregarded.

AutoBinding

It is possible to control in detail by specifying the autoBinding attribute of the component tag.

autoBinding description
auto When constructor's argument is specified specifying, S2Container follows it. When the default constructor who does not have the argument is defined, the component is made by handling the constructor. The component is made by the constructor which the number of constructor's arguments is one or more and the type of all the arguments is an interface when there is no constructor of default. When property is specified specifying, S2Container follows it. 明示的に指定されていないプロパティで型がインターフェースの場合は自動的にバインドします。
constructor コンストラクタの引数が明示的に指定されている場合は、それに従います。指定されていない場合、引数のないデフォルトコンストラクタが定義されている場合はそのコンストラクタを使ってコンポーネントが作成されます。デフォルトのコンストラクタがない場合、コンストラクタの引数の数が1以上で、引数の型がすべてインターフェースのコンストラクタを使ってコンポーネントを作成します。プロパティが明示的に指定されている場合はそれに従います。
property コンストラクタの引数が明示的に指定されている場合は、それに従います。指定されていない場合は、デフォルトのコンストラクタでコンポーネントを作成します。型がインターフェースのプロパティを自動的にバインドします。
none コンストラクタの引数が明示的に指定されている場合は、それに従います。プロパティが明示的に指定されている場合はそれに従います。

コンポーネントでS2Containerを利用する

コンポーネントはコンテナに依存しないことが望ましいのですが、コンポーネントによっては、コンテナのサービスを呼び出したい場合もあるでしょう。S2Container自身もcontainerという名前で、コンテナに登録されているので、arg,propertyタグのボディでcontainerを指定することで、コンテナのインスタンスを取得できます。また、S2Container型のsetterメソッドを定義しておいて自動バインディングで設定することもできます。

誰がS2Containerを作成するのか

誰がS2Containerを作成するのでしょうか。その目的のためにS2ContainerServletが用意されています。S2ContainerServletを使うためには、web.xmlに次の項目を記述します。src/org/seasar/framework/container/servlet/web.xmlに記述例もあります。

<servlet>
<servlet-name>s2servlet</servlet-name>
<servlet-class>org.seasar.framework.container.servlet.S2ContainerServlet</servlet-class>
<init-param>
<param-name>configPath</param-name>
<param-value>app.dicon</param-value>
</init-param>
<load-on-startup/> </servlet>
<servlet-mapping>
<servlet-name>s2servlet</servlet-name>
<url-pattern>/s2servlet</url-pattern>
</servlet-mapping>

configPathでメインとなる定義ファイルを指定します。定義ファイルはWEB-INF/classesにおきます。S2ContainerServletは、他のサーブレットよりもはやく起動されるようにload-on-startupタグを調整してください。S2ContainerServletが起動した後は、SingletonS2ContainerFactory.getContainer()でS2Containerのインスタンスを取得できます。S2Containerのライフサイクルは、S2ContainerServletと連動します。

app.diconの役割

app.diconの役割は、分割されたXML定義をインクルードすることです。app.diconにはコンポーネントの定義はしないようにしてください。サンプルで、他の定義ファイルをインクルードしているケースもありますが、あくまでもサンプルなためで、実際の開発では、インクルードはapp.diconのみで行うことを推奨します。場所は、CLASSPATHに通してあるディレクトリに置く必要があります。通常はWEB-INF/classesにおくと良いでしょう。