ラベル DI の投稿を表示しています。 すべての投稿を表示
ラベル DI の投稿を表示しています。 すべての投稿を表示

日曜日, 4月 07, 2013

Spring 基本のキホン2 - コレクションをインジェクト

Spring CollectionsのDI

Springの基本第二弾としてCollectionsのインジェクトを選んでみた.とはいえ,Collectionsのインジェクションの機能は正直言って個人的に微妙である.何故微妙なのか?理由は2つある.まず1つめには,Collectionsをコンテクストファイルに記述しればコンテクストファイルが冗長になって可読性が低くなる.2つ目に一般的にプロパティファイルのプロパティの数は決して少なくないしプロダクトがサポートしなければいけない環境又はインスタンスの数が多い場合はこういったプロパティファイルはビルド時に自動生成する.勿論,コンテクストファイルを自動生成する手もあるにはあるが,今のところは私のチームではやっていない.

とはいえ,非常に便利な機能には違いがないので,是非私たちのツールセットの一つに加えておきたい.

サンプル

ここでは一つのJavaクラス,1つのコンテクストファイルそして実行確認用に1つのテストクラスを用意している.
そんな特別なことはなく,単にSet,Map,List,Propertyのフィールドとセッターを注入対象クラスに定義し,コンテクストファイルにはそれぞれに対応するpropertyタグを定義する.Listならlistタグ,MapならMapタグ,Setならセットタグ,Propertiesならpropsタグを定義.ListならびにSetのエレメント定義にはvalueタグ,Mapならentryタグ,Propertiesならばpropタグを使うこと.

CollectionsHolder.java


package org.tanuneko.spring;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class CollectionsHolder {

private List<String> strList;
private Set<String> strSet;
private Map<Integer,String> strMap;
private Properties props;

public List<String> getStrList() { return strList; }
public Set<String> getStrSet() { return strSet; }
public Map<Integer,String> getStrMap() { return strMap; }
public Properties getProps() { return props; }

public void setStrList(List<String> strList ) {
this.strList = strList;
}

public void setStrSet(Set<String> strSet) {
this.strSet = strSet;
}

public void setStrMap(Map<Integer,String> strMap) {
this.strMap = strMap;
}

public void setProps(Properties props ) {
this.props = props;
}

}

context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">
   
    <bean id="colHolder" class="org.tanuneko.spring.CollectionsHolder">
        <!-- injecting list -->
    <property name="strList">
    <list>
    <value>Cat</value>
    <value>Dog</value>
    <value>Dog</value>
    </list>
    </property>
    <!-- injecting set -->
    <property name="strSet">
    <set>
    <value>Okachimachi</value>
    <value>Brooklyn</value>
    <value>Brooklyn</value>
    </set>
    </property>
    <!-- injecting map -->
    <property name="strMap">
    <map>
    <entry key="1" value="Vanderbilt St"/>
    <entry key="2" value="Willoghby St"/>
    </map>
    </property>
    <!-- injecting map -->
    <property name="props">
    <props>
    <prop key="prop.shape.name">Triangle</prop>
    <prop key="prop.shape.height">12.5</prop>
    <prop key="prop.shape.base">7</prop>
    </props>
    </property>
    </bean>

</beans>

SpringSampleTest.java

package org.tanuneko.spring;

import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import static org.hamcrest.CoreMatchers.*;

/**
 * Unit test for simple App.
 */
public class SpringSampleTest {
static ApplicationContext ctx = null;
@BeforeClass
public static void prep() {
ctx = new ClassPathXmlApplicationContext( "spring/context.xml" );
}
@Test
public void testListHolder() {
CollectionsHolder holder = (CollectionsHolder)ctx.getBean( "colHolder" );
// list.
assertThat( holder.getStrList().get( 0 ), is( "Cat" ) );
assertThat( holder.getStrList().get( 1 ), is( "Dog" ) );
assertThat( holder.getStrList().get( 2 ), is( "Dog" ) );
// Set. In different with list set won't allow to have any dupe entries
for( String str: holder.getStrSet() ) {
assertThat( str, anyOf( is("Okachimachi"), is("Brooklyn" ) ) );
}
assertThat( holder.getStrSet().size(), is( 2 ) );
// Map. Similarly to set no dupe entries are allowed. O(1) reachable unless there are conflicts
assertThat( holder.getStrMap().get( 1 ), is( "Vanderbilt St" ) );
assertThat( holder.getStrMap().get( 2 ), is( "Willoghby St" ) );
// Properties. O(1) reachable for arbitrary keys
assertThat( (String)holder.getProps().get( "prop.shape.name"), is( "Triangle" ) );
assertThat( ( Double.parseDouble( (String)holder.getProps().get( "prop.shape.height" ) ) *
     Double.parseDouble( (String)holder.getProps().get( "prop.shape.base" ) ) ) / 2,
     is( 43.75 ) );
}
}


金曜日, 4月 05, 2013

Spring 基本のキホン

Spring

Springを上手く使いこなすことができれば,DI/IOCをエレガントにこなすことが出来る.オブジェクトの初期化やインスタンス生成でぐちゃぐちゃしたコードを書きたくない,テスト用DIをスマートに行いたい,JDBCやHibernate等をもう少し綺麗に書きたい,と思っておれば是非Springを試してみてほしい.

Spring サンプル

Springの基礎を丁寧に解説しているページはネットにゴマンとあるので,ここでは基本のキホンのサンプルと簡略な説明にとどめておく.

このサンプルでは4つのクラス,1つのインターフェース,1つのコンテクストxmlを使用する.

まずは土台となるBeanIFインタフェースとSpringSampleクラス.
DIに適用するクラスはテスト用・実働用に別のクラスを注入できるようにするため,インターフェース又は抽象クラスであるべき.

BeanIF.java 

public interface BeanIF {

int getInstanciationCounter();
String getMessage();

}

SpringSample.java


import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component("sample")
@Scope("prototype")
public class SpringSample implements BeanIF {

private static int instanciationCounter = 0;

@Autowired
private Injectee inject;

@Resource
private Injectee2 inject2;

public SpringSample() {
instanciationCounter++;
}

public int getInstanciationCounter() {
return instanciationCounter;
}

public void getMessage() {
return inject.showMessage() + ":" + inject2.showMessage();
}

}


上記クラスでは4つのアノテーションを使っている.
Component .. Spring Bean Management Frameworkで管理されるPojoに汎用的に使われる.
                   Controller,Repository,ServiceもSpring Mojoとしてクラスをマークするが用途は違う. 
Scope .. Singletonは最初のPojo登録の時にのみクラスは初期化される.prototypeはgetBean()されるたびに別のインスタンスが帰ってくる.
Autowired .. 型が合致又は継承又はインターフェース実現クラスであれば自動的に注入.requiredパラメータを使えばautowiringが失敗した場合の挙動も制御できる.Spring由来.
Resource .. Autowiredの条件+PojoのIdが合致する場合に注入.JSR250由来.Autowired + QualifierはResourceとほぼ同様の振る舞いになる.

次にInjecteeとinjectee2 - それぞれautowired又はResourceでDIされる - を見てみる.


@Component("inject")
public class Injectee {

public void showMessage() {
                return "Injected.";
}

}


@Component("inject2")
public class Injectee2 {

public String showMessage() {
        return "injectee2";
}

}



Componentアノテーションによりこれら2つのクラスもまたSpringにより管理化に置かれる.
ではどのようにこのアノテーションドリブンによるPojoの検索・登録がおこなわれるのか.
キーはcontext.xmlにある.


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="myapp.package" />

</beans>


context:annotation-configでautowired等のアノテーションを有効にし,context:component-scanにて指定されたパッケージ以下をSpringがスキャンし,Componentアノテーション等でマークアップされたクラスを登録し,autowiring等も行う.contextネームスペースを使う際,xmlns.contextをbeansタグに追加することを忘れないこと.

これでPojoの用意及び依存性注入も出来たはずなので,実際以下のテストクラスを使って試してみる.


import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import static org.hamcrest.CoreMatchers.*;

/**
 * Unit test for simple App.
 */
public class SpringSampleTest {

static ApplicationContext ctx = null;

@BeforeClass
public static void prep() {
ctx = new ClassPathXmlApplicationContext( "spring/context.xml" );
}

@Test
public void testBase() {
SpringSample sample = (SpringSample)ctx.getBean( "sample" );
sample = (SpringSample)ctx.getBean( "sample" );
assertThat( sample.getMessage(), is( "injectee:injectee2" ) );
assertThat( sample.getInstanciationCounter(), is( 2 ) );
}

}

テストが成功すれば,依存性注入も成功し,prototypeの挙動も確認できていることとなる.
Springは結構丁寧なロギングをしているので,もしその情報を見たければlog4j又はslf4j api + slf4j-log4j12を使う必要がある.