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

土曜日, 4月 06, 2013

JavaでREST クライアント

JavaでREST

RESTクライアントとしてJavaを選ぶことは正直言って正しい Tech Choiceとは思えないが,それでも現実的にそれをやらざる得ないことも多い.
とはいえ,それでもCommons等を使ってなんとかそれなりに書くことはできる(最もPythonやgroovyの非ではないが).
JerseyやRestEasy等といったRESTライブラリもあるが,ここではApache HttpComponentsを使う.

サンプル

ここではMongoDBのRESTインターフェースに接続し,テストDBのテスト用コレクションに対してクエリをかける例を示す.
MongoDBはデフォルトではRESTインターフェースを有効化していないので,Mongodを起動する際 --restパラメータを渡す必要がある.


package org.tanuneko.restclient;

import java.io.IOException;
import java.io.StringWriter;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MongoRestClient {

private static Logger logger = LoggerFactory.getLogger( MongoRestClient.class );

public static void queryFind( String dbName, String collection, String userName ) {

DefaultHttpClient httpClient = new DefaultHttpClient();
HttpGet req = new HttpGet( "http://127.0.0.1:28017/" + dbName + "/" + collection + "/?filter_user_id=" + userName );

try {
HttpResponse res = httpClient.execute( req );
StringWriter sw = new StringWriter();
IOUtils.copy( res.getEntity().getContent(), sw );
logger.info( sw.toString() );
} catch( IOException ioE ) {
logger.error( ioE.getMessage(), ioE );
System.exit( 1 );
}

}

public static void main(String args[]) {
        queryFind( "DevTest", "DevTest", "tanu" );
}

}


MongoDBのRESTインターフェースについてはここを参照されたし.



金曜日, 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を使う必要がある.

木曜日, 4月 04, 2013

非同期ロガー

非同期ロギング

Javaはlog4jから始まってslf4j, logbackとロギングAPIの歴史の立役者が揃っている.
そのようなロギングAPIはサイズまたは日付にによる自動ローリング,仔細なフォーマッティングの指定等といった必要不可欠でかつ極めて有用な機能をすべて提供してくれている.

そういった機能の中で私が注目したいのが非同期ロギングである.マルチスレッディングのアプリケーションにおいてロギングは避けることの出来ないクリティカルセクションである.しかしながら,非同期ロガーを使えばロギングはもはやクリティカルセクションではなくなるlog.

ここでは実装はlogback,インターフェースはslf4j-apiの組み合わせによる非同期ロガーの例を紹介してみる.

logback Async Logger

使い方は単純.logback.xml (ユニットテストにはlogback-test.xml )にAsyncAppenderの定義をして,そのappenderをrootの入れ子要素として宣言すればよい.
以下は実際のlogback.xmlの一例.
ここではASYNCアペンダーにFILEアペンダーを宣言して,ファイルへのロギングを非同期化している.


<?xml version="1.0" encoding="UTF-8"?>

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>
    </encoder>
  </appender>
  
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <!-- <file>${LOGBACK_LOGDIR}/sample.log</file> -->
    <file>${LOGBACK_HOMEDIR}/samplelog.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${LOGBACK_HOMEDIR}/samplelog-%d{yyyy-MM-dd}.log</fileNamePattern>
      <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
        <maxFileSize>20MB</maxFileSize>
        <maxHistory>2</maxHistory>
      </timeBasedFileNamingAndTriggeringPolicy>
    </rollingPolicy>
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>    
    </encoder>
  </appender>
  
  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
      <appender-ref ref="FILE"/>
      <!-- discardingThreashold == 0 means it won't drop TRACE, DEBUG and INFO level log even when the remaining capacity of blocking queue is less than 20% -->
      <discardingThreshold>0</discardingThreshold>
  </appender>

  <!-- Strictly speaking, the level attribute is not necessary since -->
  <!-- the level of the root level is set to DEBUG by default.       -->
  <root level="INFO">          
    <appender-ref ref="STDOUT" />
    <appender-ref ref="ASYNC" />
  </root>  
  
</configuration>

考慮すべきこと

ASYNCロガーは色々便利そうではあるが,非同期の常,扱いは同期のものよりやや手数がかかる.一番気をつけなければいけないのはやはりブロッキングキューのサイズだろう.
もし大量のスレッドが(大量ではないにせよ)沢山のログを非同期ロガーに送り込んだ場合,言い換えるならば沢山のログをロガーのブロッキングキューに追加した場合,最悪の場合ブロッキングキューがフルになってしまう.そのような場合,非同期ロガーはブロッキングキューに空きが出るまでアプリケーションスレッドをブロックさせる.この振る舞いをlogbackはpseudo synchronous (疑似同期)と呼んでいる.logbackでは以下のポイントがpseudo synchronousを引き起こしやすいとしている.

 logback 公式ページより引用
  • Large numbers of application threads (沢山のスレッド)
  • Large numbers of logging events per application call (沢山のロギング)
  • Large amounts of data per logging event (各ロギングイベントでの沢山のデータ)
  • High latency of child appenders (レイテイシの大きい子アペンダー)
上記いずれかに当てはまると思ったならば,是非改善したいところである.

月曜日, 4月 01, 2013

Commons IO FileUtils

また Commons IO

前回のCommons Lang StringUtilsに引き続き,Commons IO FileUtilsの紹介..
StringUtilsに負けず劣らずニッチを上手く扱っている上,使い勝手が良い.

Version 2.4を使うとして,Mavenのdependencyは

<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>

Gradleならば

'commons-io:commons-io:2.4'

Grapesであれば

@Grapes(
@Grab(group='commons-io', module='commons-io', version='2.4')
)

便利なメソッド群

Javadocを一瞥してみると,便利そうなメソッドがすぐ見つかるだろう.実際使い勝手は良い.

いくつかをピックアップして試してみたコードは以下の通り.
FileUtils無しに同じコードを書いたとすれば沢山のStream系オブジェクトの扱い及び例外処理だらけのコードになるであろうことは想像に難くない.

import java.io.File;

import static org.hamcrest.CoreMatchers.*;
import org.junit.Test;
import static org.junit.Assert.assertThat;
import static org.apache.commons.io.FileUtils.*;


public class FileUtilTest {

@Test
public void testReadWrite() throws Exception {
File tmpFile = null;
File tmpFile2 = null;
File tmpFile3 = null;
try {
tmpFile = File.createTempFile( "tmp", "test" );
           // write() .. write string to file
                        // sizeOf() .. checks file size
write( tmpFile, "1\n2\n" );
assertThat( sizeOf( tmpFile ), is( 4L ) );

                        // readLine() .. read lines from files and persist them to List<String>
int lineNum = 1;
for( String line : readLines( tmpFile ) ) {
assertThat( Integer.parseInt( line ), is( lineNum++ ) );
}
                        // readFileToString() .. read file and returns its content as single string
assertThat( "1\n2\n", is( readFileToString( tmpFile ) ) );
tmpFile2 = File.createTempFile( "tmp", "test" );

                        // write() .. write string to file
write( tmpFile2, "1\n2\n" );
                        // contentEquals() .. compare two files each other
                        // contentEqualsIgnoreEOL .. compare two files each other and ignore EOL
contentEquals( tmpFile, tmpFile2 );
tmpFile3 = File.createTempFile( "tmp", "test" );
write( tmpFile3, "1\r\n2\r\n" );
contentEqualsIgnoreEOL( tmpFile, tmpFile3, null );
} finally {
                        // forceDelete() .. delete file or directory in force manner ( it means delete all recursively )
forceDelete( tmpFile );
forceDelete( tmpFile2 );
forceDelete( tmpFile3 );
}
}
}

日曜日, 3月 31, 2013

Commons Lang StringUtils

使うべし

Apache Commons Langの一部であるStringUtilsは極めて便利である.もしこれを存じ上げないのならば,すぐにでも実践投入することをお勧めする.

このユーティリティ,一言でいうならばString関連のニッチを上手く捉えた優れものだ.
例えばisEmptyメソッド.
今までは以下のようなコードがあったと考えられる.


if( str != null && !str.equals( "" )) {
...
}


このコードを見るたびにユニットテストのカバレージを維持する為の儀式的作業またはそれを放棄した場合でのカバレージの低下が頭に浮かんで,気分は萎えるばかりだ.

しかし,StringUtilsのisEmptyメソッドを使えば以下の通り,そのような悩みはもう無用!


if( !StringUtils.isEmpty( str ) ) {
...
}

別のニッチな例としてsplitメソッドがあげられるだろう.これを使えば今までStringTokenizerを使って書いていたコードがスッキリする.joinメソッドも合わせて使えばコードの読みやすさはgroovyのそれに近くなってくる.

サンプルコード

以下のコードではStringUtilsの便利なメソッドを色々と試してみている.
defaultIfEmpty() .. もし文字列が空ならデフォルトバリューとして指示した文字列を返す.プロパティファイルから値を取得した場合値がなかったらデフォルトバリューに変換,とかのケースで使えるだろう.
join() .. スクリプト言語でおなじみ.Iterableの値を区切り文字で区切って連結した文字列を返す
split() .. 指定した区切り文字で与えられた文字列をトークナイズする.返り値は配列.
abbreviate() .. 文字列を指定した文字列長の長さで省略記号(...)を使って省略する.
isEmpty() .. 文字列が null または 空文字 ( "" ) ならばtrue. さもなければfalse.



package test.java;

import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.*;

import java.util.ArrayList;

import org.apache.commons.lang3.StringUtils;
import org.junit.Test;

public class StringUtilTest {

@Test
public void testStringUtils() {
// in real use case we may want to do the static import for StringUtils
// try defaultIfEmpty(). Remember first param of assertThat is actual and 2nd expected
assertThat( StringUtils.defaultIfEmpty( "Banana", "N/A" ), is( "Banana") );
assertThat( StringUtils.defaultIfEmpty( "", "N/A" ), is( "N/A" ) );

// try split. It can be an alternative of string tokenizer
for( String fruit: StringUtils.split( "Banana|Orange|Apple", '|' ) ) {
assertThat( fruit, anyOf( containsString( "Banana" ),containsString( "Orange" ), containsString( "Apple" ) ) );
}

// try join
assertThat( StringUtils.join( new ArrayList<String>(){{add("Banana");add("Orange");add("Apple");}}, "," ),  is( "Banana,Orange,Apple" ) );

// try abbreviate
// + 3 means the length of elipses ( "..." )
assertThat( StringUtils.abbreviate( "Tanu Tanuki", "Tanu Tanuki".indexOf( " " ) + 3 ), is( "Tanu..." ) );

// try isEmpty
assertThat( StringUtils.isEmpty( "Notempty."), is( false ) );
assertThat( StringUtils.isEmpty( "" ), is( true ) );
assertThat( StringUtils.isEmpty( null ), is( true ) );

}
}


他にも便利なメソッドが

以上で見てきたように,StringUtilsを一度知れば,これが非常に強力なツールの一つになることは想像できると思う.
上記以外でも大変便利なメソッドがたくさんあるので是非JavaDocを眺めてほしい.




金曜日, 3月 29, 2013

Java ロギングのこと .. log4j-over-slf4j.jar slf4j-log4j12.jar

ロガー移行

Javaには優れたロガーが多い.slf4j, logback 等々.
現時点ではおそらくlogbackならびにslf4jの評価が高く,実際私のところでもアプリケーションデベロッパ達は徐々にlog4jからそれらに移行しつつある.

問題発生!

そんな中私が早晩おこるであろうと思っていたことが案の定(笑)発生した.
log4j-over-slf4jとslf4j-log4j12のクラスパス上での衝突である.

上記2ライブラリはブリッジデザインでのslf4j-api具現パッケージであり,双方をクラスパスに載せると相互参照永久ループが発生しアプリケーションはクラッシュする.

もし以下の点がたくさん思い当たるならば,より問題がおこる確率は高くなる.

  1. プロジェクトやmulti-module mavenプロジェクトがある程度以上の規模である
  2. 相互独立した開発チームが同一のmulti-module mavenプロジェクトを開発している
  3. 未熟にデザインされたライブラリの依存性を持っている
  4. slf4jフレームワークに習熟した人が少ない
  5. 各プロジェクトはロガーを好きに選ぶことができる
  6. library dependenciesの管理が徹底されていない

手前のケースだとまさに上に述べたとおりで,あるプロジェクトがlog4jからslf4jへの移行過渡期にlog4j-over-slf4jへのdependencyを,また別のプロジェクトがlog4j-slf4jを採用した結果あるアプリケーションがクラッシュする羽目に陥った.

解決策。。


この問題を解決するのは決して難しくはない.slf4j-log4j12またはlog4j-over-slf4jのどちらかをdependencyから外せばよい.そして残したほうのスコープをruntimeにする.どちらを外すか?パフォーマンスの理由からlog4j-over-slf4jを外すのがいいだろう.slf4jの公式ホームページからパフォーマンス分析結果を見ることができる.

以下引用
NOTE ON PERFORMANCE Contrary to other bridging modules, namely jcl-over-slf4j and log4j-over-slf4j, which reimplement JCL and respectively log4j, the jul-to-slf4j module does not reimplement the java.util.logging because packages under the java.* namespace cannot be replaced. Instead, jul-to-slf4j translates LogRecord objects into their SLF4J equivalent. Please note this translation process incurs the cost of constructing a LogRecordinstance regardless of whether the SLF4J logger is disabled for the given level or nor. Consequently, j.u.l. to SLF4J translation can seriously increase the cost of disabled logging statements (60 fold or 6000%) and measurably impact the performance of enabled log statements (20% overall increase). As of logback version 0.9.25, it is possible to completely eliminate the 60 fold translation overhead for disabled log statements with the help of LevelChangePropagator.

これはいけない!さっさとけしちゃいましょう  エイヤッ 

以上問題解決.あとはロガー使用を中央管理する体制を強化するなりデベロッパをトレーニングするなりして適宜問題の芽をつむいでおきましょう.