日曜日, 7月 20, 2014

Spring プロファイル

プロファイルを変えることでインジェクトするデータ内容を変えたりしたいこともあるかもしれない.
Springでプロファイルを使用するのは簡単で,単にProfileアノテーションを使えばよい.
以下の例はプロファイルを有効に利用しているとは言えない例ではあるが..


package org.tanuneko;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {

    public static void main( String args[]) {

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().setActiveProfiles( "profileA" );
        ctx.register( AppConfig.class );
        ctx.refresh();
        Data data = ctx.getBean( Data.class );
        System.out.println( data.getData() );

    }
}

package org.tanuneko;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;


@Configuration
@ComponentScan("org.tanuneko")
@Profile( {"profileA","profileB"} )
public class AppConfig {

    @Bean(initMethod="init",destroyMethod="destruct")
    public Data data() {
        return new Data();
    }
}


package org.tanuneko;

public class Data {

    private String data;

    public Data() {
        data = "test data.";
    }

    public void init() {
        System.out.println( "initialized" );
    }

    public void destruct() {
        System.out.println( "destroyed" );
    }

    public String getData() { return data; }
}


月曜日, 7月 14, 2014

モックのDI

IoCコンテナを使えば,テスト用にドライバ/スタブを容易にDIできる.
当然のことなのだが,意外にこの利点をあえて使わない(?)テストケースを書いてくることがままある.これを使えば当然テストコードはかなり可読性が高くなるので,使わない手はないはずだが.

以下はモックのDIの参考例.
以下の例では,DataDumpServiceのインタフェースを介してConsoleDataDumpService並びにDataNodeNodeIFインタフェースを介して テスト用データを持つDataNodeをユニットテスト中にDIする.

DataDumpServiceインタフェース

package org.tanuneko;

public interface DataDumpService {

    public void dump( Object obj );

}

ConsoleDumpServiceクラス

package org.tanuneko;

import org.springframework.stereotype.Service;

@Service
public class ConsoleDataDumpService implements DataDumpService {

    public void dump( Object obj ) {
        System.out.println( obj );
    }
}

NodeIFインタフェース

package org.tanuneko;

public interface NodeIF<X> {

    public X getData();
}

DataNodeクラス

package org.tanuneko;

public class DataNode<X> implements NodeIF {

    private X data;

    public DataNode( X data ) {
        this.data = data;
    }

    public X getData() {
        return data;
    }
}

AppConfig(非テスト用)

package org.tanuneko;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


@Configuration
@ComponentScan("org.tanuneko")
public class AppConfig {

    @Bean
    public NodeIF data() {
        return new DataNode( "This is prod" );
    }
}

App(非テスト用)

package org.tanuneko;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;


public class App {

    public App() {

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register( AppConfig.class );
        ctx.refresh();
        NodeIF node = ctx.getBean( DataNode.class );
        System.out.println( node.getData() );
        DataDumpService srv = ctx.getBean( ConsoleDataDumpService.class );
        srv.dump( node.getData() );

    }

    public static void main( String args[] ) {

        App a = new App();

    }
}

テストアップランチャ

package org.tanuneko;

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


@Component
public class TestAppLauncher {

    @Autowired
    DataDumpService dp;

    @Autowired
    NodeIF dataNode;

}

AppConfig(テスト用)

package org.tanuneko;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


@Configuration
@ComponentScan("org.tanuneko")
public class AppTestConfig {

    @Bean
    public NodeIF data() {
        return new DataNode( "This is test" );
    }

}

テストクラス

package org.tanuneko;

import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;


public class AppTest {

    private static TestAppLauncher testApp;

    @BeforeClass
    public static void init() {

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register( AppTestConfig.class );
        ctx.refresh();
        testApp = ctx.getBean( TestAppLauncher.class );

    }

    @Test
    public void testDataNode() {

        testApp.dp.dump( testApp.dataNode.getData() );

    }

}

金曜日, 7月 04, 2014

Java8 - Collectors 平均関連

業務用のコードでは使うことはあまりないが,あったら便利な,ストリームの要素から平均を抽出するコード.


        System.out.println(

            Stream.of(50d, 62d, 79.4d, 62d, 80.0d, 89.25d, 5d)
                .collect( Collectors.averagingDouble( (x)->x) ).doubleValue() + " " +
            Stream.of(20,15,30,45,50,25)
                .collect( Collectors.averagingInt( (x)->x ) ).intValue() + " " +
            Stream.of(300L, 200L, 100L, 400L, 500L )
                .collect( Collectors.averagingLong( (x)->x ) ).longValue()

        );


現実的な用途の一つとして考えられるのはメッセージキュー中のデータの平均サイズを取得するようなものだろう.以下は5つの別スレッド上で動作するデータプロバイダーがデータキューにデータを書き込んで、サンプラーが先頭から30個のデータの長さの平均を計算する例.

package org.tanuneko;

import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class DataSampler {

    private static final int MAX_SAMPLESIZE = 30;
    private CopyOnWriteArrayList<Data> q;

    public DataSampler() {
        q = new CopyOnWriteArrayList<>();
        Executors.newFixedThreadPool(5).execute( new DataProvider( this ) );
    }

    public void doSampling() {

        while( true ) {

            System.out.println(
            q.stream().limit( MAX_SAMPLESIZE ).mapToInt( Data::getDataSize )
                    .summaryStatistics().getAverage()
            );

            try {
                Thread.sleep( 2000 );
            } catch( InterruptedException iE ) {
                iE.printStackTrace();
                System.exit(1);
            }
            System.out.println( "(QSIZE=" + q.size() + ",MAXSAMPLESIZE=" + MAX_SAMPLESIZE + ")" );

        }

    }

    public void addData( Data d ) {
        q.add( d );
    }

    public static void main (String args[] ) {

        DataSampler d = new DataSampler();
        d.doSampling();

    }

}

class DataProvider implements Runnable {

    private DataSampler sampler;

    public DataProvider( DataSampler sampler ) {
        this.sampler = sampler;
    }

    public void run() {

        Random r = new Random();
        r.setSeed( System.currentTimeMillis() );

        while( true ) {

            sampler.addData( new Data( genRandBytes() ) );
            try {
                Thread.sleep( r.nextInt( 3000 ) );
            } catch (InterruptedException iE) {
                throw new RuntimeException(iE);
            }

        }
    }

    private byte[] genRandBytes() {

        Random r = new Random();
        r.setSeed( System.currentTimeMillis() );
        return Stream.generate(()->"a").limit( r.nextInt( 100 ) )
                .collect( Collectors.joining() )
                .getBytes();

    }
}

class Data {

    private byte[] buffer;
    public Data( byte[] buffer ) {
        this.buffer = new byte[ buffer.length ];
        System.arraycopy( this.buffer, 0, buffer, 0, buffer.length );
    }
    public byte[] getData() {
        return buffer;
    }
    public int getDataSize() {
        return buffer.length;
    }

}