田中公平先生の解説一覧
NHKの熱中夜話あるいは、熱中スタジアムで田中公平先生が出ている回は見逃せないと思い、過去の番組のページを集めてみた。
BS熱中夜話ヒーローソングナイト第1夜誕生編
BS熱中夜話ヒーローソングナイト第2夜進化編
BS熱中夜話アニメソングナイト第1夜水木一郎&堀江美都子編
BS熱中夜話アニメソングナイト第2夜新世代作曲家編
熱中スタジアム特撮ソング第1夜ウルトラマン&仮面ライダーソング
熱中スタジアム特撮ソング第2夜戦隊&メタルヒーローソング
年に一回はこんな感じで出てたらしい。
八月にはhttp://www.nhk.or.jp/n-stadium/anison/index.htmlが放送されるらしいし、楽しみ。
junitで例外のテストをもっと簡単にしたい
最近のJunitでは例外のチェックが前よりは簡単になったけど、JDaveの匿名クラスを使った記述方法と比べていまいちだなと感じていた。
例外のチェック方法の比較
JUnit4での例その1:@Testのexceptionを使う場合。
「JUnit Cookbook」より
@Test(expected= IndexOutOfBoundsException.class) public void empty() { new ArrayList<Object>().get(0); }
JUnit4での例その2:@Ruleを使う場合。
「http://kentbeck.github.com/junit/javadoc/latest/org/junit/rules/ExpectedException.html」より。
// These tests all pass. public static class HasExpectedException { @Rule public ExpectedException thrown= new ExpectedException(); @Test public void throwsNothing() { // no exception expected, none thrown: passes. } @Test public void throwsNullPointerException() { thrown.expect(NullPointerException.class); throw new NullPointerException(); } @Test public void throwsNullPointerExceptionWithMessage() { thrown.expect(NullPointerException.class); thrown.expectMessage("happened?"); thrown.expectMessage(startsWith("What")); throw new NullPointerException("What happened?"); } }
あとは「JUnit4.7 の新機能 Rules とは〜その2 - A Memorandum」が参考になる。
JDaveでの例
「JDave-examples」から必要な部分を抜粋。
import jdave.Block; import jdave.Specification; import jdave.junit4.JDaveRunner; @RunWith(JDaveRunner.class) public class StackSpec extends Specification<Stack<?>> { public class FullStack { private Stack<Integer> stack; public Stack<Integer> create() { stack = new Stack<Integer>(10); for (int i = 0; i < 10; i++) { stack.push(i); } return stack; } public void complainsOnPush() { specify(new Block() { public void run() throws Throwable { stack.push(100); } }, should.raise(StackOverflowException.class)); } } }
好みの問題かもしれないけど、
- Blockを実装した匿名クラスを使う事で例外が起きるであろう箇所を指定できる。
- そのすぐ近くに投げられるであろう例外と例外メッセージを記述できる。
という部分がとても気に入っている。
ただ、以下のような違いもあって、Junitから乗り換えるのが難しいと感じる部分もある。
- JDaveRunnerでうごかさないといけない
- Specificationクラス内にテストのサブクラスを記述しないといけい
- createメソッドが必要
だったらJUnitのassertThatで同じような感じで記述できればいいかなと思って、以下のような感じで記述するためのカスタムMatcherを作ってみた。
assertThat( new Block(){ public void run() throws Throwable { stack.push(100); } }, should.raise(StackOverflowException.class));
設計と実装
「hamcrest の Matcher を独自拡張 - java.util.Date 版 closeTo - 倖せの迷う森」の独自のDate用Matcherを作成した例を参考にして、実装してみる。
TypeSafeMatcherについて
この例ではorg.hamcrest.TypeSafeMatcherというクラスを使っていて、これはhamcrest1.2以降で導入されている。
で、hamcrest-core1.2を使おうとしたら、mavenのセントラルレポジトリにないではないか。「Google Code Archive - Long-term storage for Google Code Project Hosting.」を見てみると、mavenレポジトリのアップロードに苦労しているっぽい。仕方がないので、ローカルにインストールして使う。
jdaveの例外記述バリエーション
- 例外メッセージチェックの有無。
- 例外クラスを厳密にチェックするか派生クラスを許可するか。
の組み合わせで、以下の4つが存在する。
- should.raise(Class
expected) - should.raise(Class
expectedType, String expectedMessage) - should.raiseExactly(Class
expected) - should.raiseExactly(Class
expected,String expectedMessage)
assertThatメソッドのactualとBlockの相性
ひとまずJDaveのBlockインターフェースをそのまま使ってみたのだが、assertThatメソッドと第一引数として渡しているBlockの相性が悪い。
assertThatの第一引数actualに渡したオブジェクトは、toString()メソッドで実際の値をかえさなければいけないが、インターフェースを実装した匿名クラスだと、toStringで匿名クラス名しか表示できない。
かといって、toString()メソッドをそれぞれ実装するのは記述量がふえてしまって良くない。
ということでBlockを抽象クラスに変更して、Matcherでの評価時に発生した例外を保存出来るようにし、toString()メソッドでその例外の情報を返すようにした。
実際のコード
ひとまずbitbucketに置いてみた。
http://bitbucket.org/cnaos/hamcrest-exception-matcher/
主要なクラスの解説
Blockクラス
jdaveのBlockインターフェースとほぼ一緒。前述の理由によりassertThat()に適合するように修正している。
public abstract class Block { /** ブロックの実行時に発生した例外 */ private String actualException="No Exception was thrown"; /** * Evaluate this block. * * @throws Throwable if an exception is thrown during block evaluation. */ public abstract void run() throws Throwable; /** * 発生した例外を設定します。 * @param actualException */ public void setActualException(String actualException) { this.actualException = actualException; } @Override public String toString() { return this.actualException; } }
RaiseMatcherクラス
Block用のカスタムMatcher。
要となる部分は、matchesSafelyメソッドで、この部分で渡されたBlockを実行して、投げられた例外をチェックしている。
public class RaiseMatcher extends TypeSafeMatcher<Block> { /** 例外の期待値 */ private final ExpectedException<? extends Throwable> expectation; /** * * @param aExpectation 例外の期待値 */ public RaiseMatcher(ExpectedException aExpectation) { this.expectation = aExpectation; } @Factory public static <E extends Throwable> Matcher<Block> raise(final Class<E> expected) { return new RaiseMatcher(new ExpectedException<E>(expected)); } @Factory public static <E extends Throwable> Matcher<Block> raise(final Class<E> expected, final String expectedMessage) { return new RaiseMatcher(new ExpectedExceptionWithMessage<E>(expected, expectedMessage)); } @Factory public static <E extends Throwable> Matcher<Block> raiseExactly(final Class<E> expected) { return new RaiseMatcher(new ExactExpectedException<E>(expected)); } @Factory public static <E extends Throwable> Matcher<Block> raiseExactly(final Class<E> expected, final String expectedMessage) { return new RaiseMatcher(new ExactExpectedExceptionWithMessage<E>(expected, expectedMessage)); } /** * * @see jdave.Specification#specify(Block,ExpectedException) * @param block テストで実行するブロック * @return 指定したテスト条件を満たした場合はtrue */ @Override protected boolean matchesSafely(final Block block) { try { block.run(); } catch (final Throwable t) { block.setActualException(expectation.createDescription(t)); if (!expectation.matches(t)) { return false; } return true; } return false; } /** * 投げられる事を期待している例外クラス名や例外メッセージを返す * @param description 期待する条件の説明を返す */ @Override public void describeTo(Description description) { description.appendText("throw "); description.appendValue(expectation); } }
ExpectedExceptionクラス
Blockから投げられる例外に関する期待条件クラス。
jdaveのExpectedExceptionをhamcrestMatcherに合うように改変した。
public class ExpectedException<T> { protected final Class<? extends T> expected; public ExpectedException(Class<? extends T> expected) { if(expected == null){ throw new NullPointerException(); } this.expected = expected; } public boolean matches(Throwable t) { return matchesType(t.getClass()); } public String createDescription(Throwable t){ return t.getClass().getName(); } protected boolean matchesType(Class<? extends Throwable> actual) { return expected.isAssignableFrom(actual); } @Override public String toString() { return expected.getName(); } }
TwitterのOAuth認証を使ったサービスを開発する際の注意(その2)
前回のエントリで、対策が具体的でなかったのは、SHA-1などのハッシュ関数を使っても問題ないか自信が持てなかったためです。すみません。とりあえず自分で納得できる対策が調べられたと思うので書いておきます。
twitterIDをそのままSHA-1などの暗号学的ハッシュ関数にかけただけでは弱いので避けるということは知っていましたが、単なる固定文字列のsaltを付け加えるだけだと、常に同じ値が生成されるのでこれでいいのか自信が持てませんでした。
で、調べてみたら自動ログイン用のクッキーの値に必要な特性は、セッションIDの特性によくにていて、値の生成は「ユーザIDや時刻等の情報と擬似乱数とを混ぜ合わせた文字列に対してハッシュ関数を使用する」で問題なさそうだということが分かりました。
また、管理方法もに似ていて、「ログインごとに認証用のキーとなる値を新しい値に更新しなければいけない」ということも分かりました。
以下の情報を参考にしました
「解答:まちがった自動ログイン処理」で参考にした箇所
セキュリティのベストプラクティスとしてはクッキーにユーザに関連するいかなる情報(暗号化された情報を含む)も保存すべきではありません。クッキーにユーザに関連した情報(ユーザID、パスワード、メール、氏名、etc)を保存しなければならないシステムである場合、設計を見直し、ユーザ情報をクッキーに保存しなくても良い設計にしなければなりません。クッキーに保存しても良い値は、表示設定などのユーザ情報と関係の無い情報、セッションIDなどの予測不可能なランダムな値のみです。
ユーザIDくらいは大丈夫かと思ってたけど、そうだったのか。
【まとめ】
- 自動ログイン機能は基本的にセキュリティ上のリスクを増加させるので安全性が重要なサービスでは実装しない。
- 自動ログインの実装にはセッション管理と同様にランダムなクッキーの値を使用する。
- 自動ログイン用のクッキーはログインの度に新しい値に更新する。(古い鍵の削除も忘れない)
- ログイン時に自動ログインオプションが無効かつ自動ログイン用のクッキーが設定されている場合は自動ログインテーブルの該当レコードと自動ログインクッキーも削除する。
- 自動ログイン用のクッキーは自動ログイン処理を行う場合にのみ必要なので自動ログイン専用のディレクトリを設定し、そのディレクトリのURLがリクエストされた場合にのみ送信する。(XSSリスクの低減)
- ログアウトした場合、自動ログインクッキーが設定されている場合、自動ログインテーブルの該当レコードと自動ログインクッキーも削除する。
- 実際の運用では古い自動ログイン用のレコードが溜まるので定期的に不要なレコードを削除する。
「安全なウェブサイトの作り方」で参考にした箇所
1.4 セッション管理の不備」が参考になった。
以下は項目名だけ抜粋
1) セッション ID を推測が困難なものにする
2) セッション ID を URL パラメータに格納しないようにする
3) HTTPS 通信で利用する Cookie には secure 属性を加える
4-1) ログイン成功後に、新しくセッションを開始するようにする
または
4-2) ログイン成功後に、既存のセッションIDとは別に秘密情報を発行し、ページの遷移毎にその値を確認する
「IPA セキュア・プログラミング講座:Webアプリケーション編」で参考にした箇所
自動ログイン用のキーの特性として、セッションIDと同じ特性が必要らしいので、「第4章セッション対策セッション乗っ取り:#2 セッションID強度を高める」が参考になった。
(1) 公共に知られていそうなIDは使用しない
例えば、会員番号やユーザID等、公共に知られていそうな値では容易に推測が可能であるので、使用すべきではない。セッションIDは、人間が覚えておく必要は無いので、見た目が無意味な値であっても、まったく問題はない。(2) ランダムなIDを使用する
00001、00002...等の規則性や連続性のある値は、推測が容易であるのでランダムなIDを使用する。例えば、ユーザIDや時刻等の情報と擬似乱数とを混ぜ合わせた文字列に対してハッシュ関数を使用することで、ランダムなIDが生成されるようになる。
ただし、擬似乱数生成関数やハッシュ関数を自前で作成することは避けるべきである。(3) 桁数と文字種を多くする
桁数が短く、文字種の少ない場合、ブルートフォースアタックで破られてしまうおそれがある。これを防ぐためには、使用する文字種、桁数をなるべく多くし、総当りするパターンを増やすことである。例えば、英数小文字大文字で20桁の値を考えた場合、組み合わせとしては「(26+26+10)^20」通りとなる。(4) ユニークなセッションIDを発行する
セッションIDは、どのような場合でも、常にユニークな値を発行するようにする。
- Webアプリケーションを利用する他ユーザに対して発行する場合
同じセッションIDを発行してしまうと「なりすまし」のおそれがある。
- 同一ユーザに対して発行する場合
セッションの度に同じIDを発行することがないようにする。これは一度セッションIDが盗まれてしまうと、Webアプリケーション内での「なりすまし」が永続的に可能となってしまうからである。
IPAの資料には、セッション設計のガイドラインはあるけど、自動ログインについての記述が無かったので、追加して欲しいと思った。
TwitterのOAuth認証を使ったサービスを開発する際の注意
「OAuth を使ってソーシャル・ネットワーキング Web サイトにアクセスする: 第 2 回: OAuth 対応のWeb 版 Twitter クライアントを作成する」のサンプルをいじくっていて気づいたんですが、TwitterのOAuth認証を使ったサービスを開発する場合、AccessTokenの管理方法に注意が必要です。
あんまり自信がないので、誤りがあればどんどん指摘してください。
以下の条件が成り立つ場合、サービス内でなりすましができてしまいます。
- サービス(Consumer)がtwitterIDとAccessTokenをひもづけて保管している。
- サービスがブラウザへ渡すcookieにtwitterIDをそのまま保存している
- ブラウザからサービスへtwitterIDを含むCookieが渡された場合、twitterIDをキーとしてサービス内に保存しているAccessTokenを検索し、存在していれば認証済みであるとみなす。
- なりすまし対象のアカウントがすでにOAuth認証済みで、サービス内になりすまし対象アカウントのAccessTokenが保存されている。
想定されるシナリオ
"サービス"はtwitterAPIを利用するWebアプリケーションです。
"Alice"(通常のユーザ)と"Carol"(なりすまし攻撃者)がいて、AliceがTwitterAPIを利用するサービスにOAuthで許可を与えたとします。
twitterからサービスへAlice用のAccessTokenが送信され、サービスはAliceのtwitterIDとひもづけて保存します。
twitterの認証画面からサービスの画面に戻った時に、サービスからAliceのブラウザにtwitterIDを含むcookieが送信されます。
次にAliceがサービスにアクセスした際に、サービスはcookieからtwitterIDを取得し、そのIDに対応するAccessTokenを取り出します。対応するAccessTokenが存在していれば、サービスはAliceがOAuthで許可を与えているものと見なします。
Carolはサービスにアクセスする際にAliceのスクリーンネームからtwitterIDを割り出し、AliceのtwitterIDを含むcookieを偽造することでサービス内でAliceになりすますことができます。
twitteIdはintの数値であり、これはTwitterAPIを使うことでスクリーンネームから容易に取得できます。ブラウザから確認したいのであれば、Twitter API Viewerのサービスを使ってみてください。
最後にひとこと
今後、このような事故がおこった場合、「OAuth認証によってパスワードによる認証が無くなってしまったため、これまで安全と思って送受信したり保存したりしていたIDがセキュリティ上の弱点となってしまった事例」とか言われるんだと思います。
これからOAuth対応でいろいろ手が入るサービスが出てくるでしょうが、気になる人はcookieにtwitteridが入っていないか注意してください。
2010/05/29追記
追加エントリ「TwitterのOAuth認証を使ったサービスを開発する際の注意(その2) - Sacrificed & Exploited」を書きました。
mavenのレポートプラグインてんこ盛り設定
mavenレポートプラグインをてんこ盛りにしたpom.xmlの設定例です。
以下のレポートプラグインを使います。
- org.apache.mavenのプラグイン
- maven-site-plugin
- プロジェクトのサイトを作成する
- maven-jxr-plugin
- クロスリファレンスを作成する
- maven-surefire-report-plugin
- テスト結果のレポートを作成する
- maven-checkstyle-plugin
- checkstyleのチェックレポートを作成する。
- maven-pmd-plugin
- PMDでのチェックレポートを作成する。
- maven-site-plugin
- org.codehaus.mojoのプラグイン
- findbugs-maven-plugin
- findbugsのチェックレポートを出力する。
- cobertura-maven-plugin
- jdepend-maven-plugin
- JDependのメトリックスレポートを出力する。
- javancss-maven-plugin
- JavaNCSSのメトリックスレポートを出力する。
- findbugs-maven-plugin
Mavenレポジトリの設定
<pluginRepositories> <pluginRepository> <id>Codehaus repository</id> <url>http://repository.codehaus.org/</url> </pluginRepository> </pluginRepositories>
プロジェクトの共通の設定
主に文字コードとコンパイラバージョンを設定します。
これらのプロパティに対応しているプラグインでは設定パラメタの記述を省略し、対応していないプラグインはこのプロパティ設定を参照するようにしています。
<properties> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.target>1.6</maven.compiler.target> <maven.compiler.source>1.6</maven.compiler.source> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties>
ビルドプラグインの設定
cobertura-maven-pluginの設定
カバレッジ測定用の設定。
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.4</version> <configuration> <instrumentation> <excludes> <exclude>**/*Test.class</exclude> </excludes> </instrumentation> </configuration> <executions> <execution> <goals> <goal>clean</goal> </goals> </execution> </executions> </plugin>
レポートプラグインの設定
maven-site-pluginの設定
※maven-site-plugin2.1を使うには、Maven 2.1.x 以降が必要です。
わざわざinputEncoding,outputEncodingを指定しているのは、指定しないとmavenプロパティの設定が使われず、ISO-8859-1が使われてしまうためです。
<plugin> <artifactId>maven-site-plugin</artifactId> <version>2.1</version> <configuration> <locales>ja</locales> <inputEncoding>${project.build.sourceEncoding}</inputEncoding> <outputEncoding>${project.reporting.outputEncoding}</outputEncoding> </configuration> </plugin>
maven-jxr-pluginの設定
<plugin> <artifactId>maven-jxr-plugin</artifactId> <version>2.1</version> <configuration> <aggregate>true</aggregate> <inputEncoding>${project.build.sourceEncoding}</inputEncoding> <outputEncoding>${project.reporting.outputEncoding}</outputEncoding> </configuration> </plugin>
maven-surefire-report-pluginの設定
<plugin> <artifactId>maven-surefire-report-plugin</artifactId> <version>2.5</version> <configuration> <showSuccess>false</showSuccess> </configuration> </plugin>
maven-checkstyle-pluginの設定
<plugin> <artifactId>maven-checkstyle-plugin</artifactId> <version>2.5</version> </plugin>
maven-pmd-pluginの設定
<plugin> <artifactId>maven-pmd-plugin</artifactId> <version>2.5</version> <configuration> <targetJdk>${maven.compiler.target}</targetJdk> </configuration> <reportSets> <reportSet> <reports> <report>pmd</report> <report>cpd</report> </reports> </reportSet> </reportSets> </plugin>
findbugs-maven-pluginの設定
※maven-site-pluginでlocaleをjaにしていると、文字化けを起こします。どうもwindows環境だとプラットフォームのデフォルトエンコーディングが優先されているみたいなのですが対処方法が分かりません。
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> <version>2.3.1</version> <configuration> <inputEncoding>${project.build.sourceEncoding}</inputEncoding> <outputEncoding>${project.reporting.outputEncoding}</outputEncoding> </configuration> </plugin>
cobertura-maven-pluginの設定
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.4</version> <configuration> <formats> <format>html</format> </formats> </configuration> </plugin>
jdepend-maven-pluginの設定
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>jdepend-maven-plugin</artifactId> <version>2.0-beta-2</version> </plugin>
javancss-maven-pluginの設定
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>javancss-maven-plugin</artifactId> <version>2.0</version> </plugin>
まとめ
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>example</groupId> <artifactId>maven-report-example</artifactId> <version>1.0-SNAPSHOT</version> <name>maven-report-example</name> <properties> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <maven.compiler.target>1.6</maven.compiler.target> <maven.compiler.source>1.6</maven.compiler.source> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <pluginRepositories> <pluginRepository> <id>Codehaus repository</id> <url>http://repository.codehaus.org/</url> </pluginRepository> </pluginRepositories> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>1.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.1</version> <configuration> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.4</version> <configuration> <instrumentation> <excludes> <exclude>**/*Test.class</exclude> </excludes> </instrumentation> </configuration> <executions> <execution> <goals> <goal>clean</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.5</version> </plugin> </plugins> </build> <reporting> <plugins> <plugin> <artifactId>maven-site-plugin</artifactId> <version>2.1</version> <configuration> <locales>ja</locales> <inputEncoding>${project.build.sourceEncoding}</inputEncoding> <outputEncoding>${project.reporting.outputEncoding}</outputEncoding> </configuration> </plugin> <plugin> <artifactId>maven-jxr-plugin</artifactId> <version>2.1</version> <configuration> <aggregate>true</aggregate> <inputEncoding>${project.build.sourceEncoding}</inputEncoding> <outputEncoding>${project.reporting.outputEncoding}</outputEncoding> </configuration> </plugin> <plugin> <artifactId>maven-surefire-report-plugin</artifactId> <version>2.5</version> <configuration> <showSuccess>false</showSuccess> </configuration> </plugin> <plugin> <artifactId>maven-checkstyle-plugin</artifactId> <version>2.5</version> </plugin> <plugin> <artifactId>maven-pmd-plugin</artifactId> <version>2.5</version> <configuration> <targetJdk>${maven.compiler.target}</targetJdk> </configuration> <reportSets> <reportSet> <reports> <report>pmd</report> <report>cpd</report> </reports> </reportSet> </reportSets> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> <version>2.3.1</version> <configuration> <inputEncoding>${project.build.sourceEncoding}</inputEncoding> <outputEncoding>${project.reporting.outputEncoding}</outputEncoding> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.4</version> <configuration> <formats> <format>html</format> </formats> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>jdepend-maven-plugin</artifactId> <version>2.0-beta-2</version> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>javancss-maven-plugin</artifactId> <version>2.0</version> </plugin> </plugins> </reporting> </project>
NetBeansでlombokを使う
「Java の冗長性を排除する手軽な方法」で紹介されていたlombokというライブラリが便利そうだったので、NetBeansで試してみた。
一言で言うと、lombokはJavaBeanのsetterやgetterを省略可能にして記述量を減らしてくれるライブラリ。JavaBeansのメソッドはIDEで機械的に生成できるけど、このくらいの単純な仕様にして欲しかった。
ふつうのライブラリとちょっと違うのは、IDEのコンパイラがアノテーションを処理できるようにインストールが必要なこと。javacで扱う分にはjarがクラスパスに入ってればいいらしいけど。
lombokのインストール手順
Mavenで使う場合
Use Lombok via Mavenに従えばよい。
jarを一本化するonejar-maven-pluginを使ってみた
「javaプロジェクトを簡単に単一jarファイルで配布する - lime55の日記」から
「One-JARでアプリケーションの配布を単純化」経由で
「Google Code Archive - Long-term storage for Google Code Project Hosting.」を見つけたので試してみる。
One-Jarの仕組みをごくおおざっぱに説明すると、
- Jarの中にJarが入れ子で入っている。
- 外側のJarのMainクラス=外側のJarに含まれる特別なクラスローダが起動される
- 入れ子になっている内側のJarをロードする
- 入れ子のMainクラスを起動する
これまで、「Mavenで配布用zipファイルを作成する - Sacrificed & Exploited」でいろいろな配布パッケージを作る方法を試したけど、jar一個ですむならそっちの方が楽だし。
onejar-maven-pluginの使い方
onejar-maven-pluginの使い方はここにある。
パターンAとパターンBの2つの方法があるみたい。
という違いくらい。
以下のpom.xmlはパターンBの例で、「example.onejarexample.SearchFrame」をmainclassに指定している。
<project> .... <build> <plugins> .... <plugin> <groupId>org.dstovall</groupId> <artifactId>onejar-maven-plugin</artifactId> <version>1.4.1</version> <executions> <execution> <configuration> <mainClass>example.onejarexample.SearchFrame</mainClass> <!-- Optional --> <onejarVersion>0.96</onejarVersion> <!-- Optional, default is false --> <attachToBuild>true</attachToBuild> <!-- Optional, default is "onejar" --> <classifier>onejar</classifier> </configuration> <goals> <goal>one-jar</goal> </goals> </execution> </executions> </plugin> .... </plugins> </build> <pluginRepositories> <pluginRepository> <id>onejar-maven-plugin.googlecode.com</id> <url>http://onejar-maven-plugin.googlecode.com/svn/mavenrepo</url> </pluginRepository> </pluginRepositories> .... </project>
設定ファイルが読み込めない場合がある
プロパティファイルやXMLファイルの読み込み方法によって、onejar化した場合に設定ファイルを読み込めない場合がある。
クラスローダ経由(ClassLoader.getResource())でクラスパス上の設定ファイルを読み込んでいる場合は、外部ファイルのファイルをクラスローダの検索範囲に入れることが出来ないため、設定ファイルを読み込むことができない。
ファイルとして設定ファイルを直接オープンしている分には問題ない。
内側のjarに記述したManifestファイルが読み込まれないので、そこにClass-Pathの設定を記述しても無視されてしまうためらしい。入れ子になっているjar内に配置するとクラスパスに入るけど、これじゃあ設定ファイルの意味ないし....