2011年8月9日火曜日

Androidアプリを登録しました

昨日、個人で使うために作ったアプリを公開しました。

どんなアプリかというと、最近使用したアプリのショートカットを表示するウィジェットです。

Android端末のユーザの大半は、その機能はHomeボタン長押しで使えるとご認識しているでしょう。
しかしながら、実は、私の使用しているAndroid端末(IS05)では、Home長押しは「起動中のアプリ」を表示します。
さらには、「最近起動したアプリ」を表示する機能が見当たりません(案外、調べればありそうですが・・・)。

と、いうような経緯があり、それならばウィジェットでその機能を提供し、ワンタッチで起動できるアプリとして作成してみました。

つまるところ、ただの俺得アプリに過ぎませんが、興味がありましたら、使ってみてください。
Android Market - 最近アプリ

2011年7月23日土曜日

JavaSE7のtryは便利

7月28日に正式リリースされるJavaSE7では、いくつか便利な構文が増えます。
今回は、その中でも特に有用であろうtryの新しい構文について調べてみました。

ファイルのコピーを例にして、tryの構文の拡張を調べてみます
JavaSE6以前のソースコードをJavaSE7のコードに置き換えます。

これまで(JavaSE6以前)のコード
FileInputStream fis = null;
FileOutputStream fos = null;
try {
 fis = new FileInputStream("D:\\test.txt");
 fos = new FileOutputStream("D:\\test2.txt");
 byte[] bytes = new byte[1024];
 int size = -1;
 while ((size = fis.read(bytes)) != -1) {
  fos.write(bytes, 0, size);
 }
} catch (FileNotFoundException e) {
 e.printStackTrace();
} catch (IOException e) { 
 e.printStackTrace();
} finally {
 if (fos != null) {
  try {
   fos.close();
  } catch (IOException ex) {
  }
 }
 if (fis != null) {
  try {
   fis.close();
  } catch (IOException ex) {
  }
 }
}

これから、上記のソースコードをJavaSE7風に書き換えていきます。

例外のマルチキャッチ
catchの部分をひとつにまとめることができます
FileInputStream fis = null;
FileOutputStream fos = null;
try {
 fis = new FileInputStream("D:\\test.txt");
 fos = new FileOutputStream("D:\\test2.txt");
 byte[] bytes = new byte[1024];
 int size = -1;
 while ((size = fis.read(bytes)) != -1) {
  fos.write(bytes, 0, size);
 }
} catch (FileNotFoundException | IOException e) {
 e.printStackTrace();
} finally {
 if (fos != null) {
  try {
   fos.close();
  } catch (IOException ex) {
  }
 }
 if (fis != null) {
  try {
   fis.close();
  } catch (IOException ex) {
  }
 }
}
これで、例外処理時に同じコードを書かずに済みます。
また、「|」で複数の例外を指定した場合は、左の例外から評価されるようです。
これまでの構文のcatchで上に書いていた例外は、その順で左から書けばよいでしょう。つまり、Throwableを書く場合は、一番右です。
AutoCloseableインターフェース
JavaSE7から、AutoCloseableというインターフェースが追加されます。
このインターフェースには、closeというメソッドがあります(ご存知のとおり、JavaSE6以前にもcloseメソッドはありますが、それをオーバーライドしている感じです)。このメソッドが「try-finallyの処理で勝手に呼ばれる」ようです。 AutoCloseableを利用するためのコードは下記の通りです。
try (FileInputStream fis = new FileInputStream("D:\\test.txt"); FileOutputStream fos = new FileOutputStream("D:\\test2.txt")) {
 byte[] bytes = new byte[1024];
 int size = -1;
 while ((size = fis.read(bytes)) != -1) {
  fos.write(bytes, 0, size);
 }
} catch (FileNotFoundException | IOException e) {
 e.printStackTrace();
}
tryの()の中ではセミコロンで区切ると、複数のAutoCloseableを並べて記述できるようです。
Stream関係のインターフェースはAutoCloseableのサブインターフェースになっているようです。
インターフェースを実装する際に、AutoCloseableを意識する必要はあまりないと思われます。ъ(゚Д゚)グッジョブ!!(JavaSE6以前のソースを書き換える場合は別)
※public void close() throws IOException => public void close() throws Exception というふうに変わります。

なにはともあれ、コード量がかなり減りました。ワーイヽ(゚∀゚)メ(゚∀゚)メ(゚∀゚)ノワーイ
AutoCloseableを検証
実際に自分の眼で見て確認しました。
closeの順番と、実際に例外が起こった場合について。
public class Test {
 public static void main(String[] args) {
  new Test().start();
 }

 void start() {
  try (AutoCloseableImpl1 ac1 = new AutoCloseableImpl1(); AutoCloseableImpl2 ac2 = new AutoCloseableImpl2()) {
   
  }
 }
}

class AutoCloseableImpl1 implements AutoCloseable {

 AutoCloseableImpl1() {
  System.out.println(getClass().getSimpleName());
 }

 @Override
 public void close() {
  System.out.println(getClass().getSimpleName() + "#close()");
 }
}

class AutoCloseableImpl2 implements AutoCloseable {

 AutoCloseableImpl2() {
  System.out.println(getClass().getSimpleName());
 }

 @Override
 public void close() {
  System.out.println(getClass().getSimpleName() + "#close()");
 }
}
上記のプログラムを実行します。
実行結果は

AutoCloseableImpl1
AutoCloseableImpl2
AutoCloseableImpl2#close()
AutoCloseableImpl1#close()

closeが呼ばれており、closeする順番もオッケー。
ちなみに、AutoCloseableImpl2()コンストラクタで例外を投げた場合は、以下

AutoCloseableImpl1
AutoCloseableImpl1#close()
Exception in thread "main" java.lang.NullPointerException
・・・

期待通りです。

余談ですが、catchもfinallyもないtry構文が書けるようになりました。(内部的にはfinallyが存在しますが)

実は、JavaSE7にはjava.nio.file.Filesというクラスが追加されており、ファイルをコピーするだけなら、このクラスを使ったほうが良いです。
まぁ、今回はあくまで勉強のためですから・・・

今回の検証でもちいたIDEはNetBeansです。
Eclipse3.7でも検証は可能でしたが、不都合な点がありましたので、NetBeansを使用しました。
現在のEclipse3.7とJDK1.7の詳細については こちら

2011年7月17日日曜日

Eclipse3.7 で JavaSE7

Eclipse3.7 で JavaSE7

JavaSE7の正式リリースは2011年7月28日で、NetBeansは既にJavaSE7に対応しています。
EclipseはまだJavaSE7に対応していないのですが、pluginをインストールすれば使えるようになります。

各ツールのサイト
Java™ Platform, Standard Edition 7 Developer Preview Release
Eclipse Classic 3.7
JDT/Eclipse Java 7 Support(BETA)
JDK準備
JDK7 Preview Releaseをダウンロードして、インストールします。
開発ツールだけインストールすればOKです。
Eclipse準備
Eclipseは3.7の「Classic」を使います。
「IDE for Java Developers」や、「IDE for Java EE Developers」では、pluginのインストールができませんでした。
Install New SoftWare -> Work with:
「http://build.eclipse.org/eclipse/java7patch/」を入力して、pluginをインストールします。
Eclipse設定
Preferences -> Java -> Installed JREs
インストールしたJDK1.7を追加し、使用するようにします。
あとは、Java Compilerの設定で、Compiler compliance level を 1.7 にすれば準備完了です。

実は、上記で設定した開発環境には問題があり、メソッドや変数の候補を出せなくなります。
普段から、何も考えずにとりあえず Ctrl + Space 押して幸せになっている私にとっては致命的です。
Eclipseで正式にサポートされるまではJavaSE7は我慢するか、NetBeansを使いましょう。

2011年5月31日火曜日

アルゴリズムを考える

アルゴリズムというものについて考えてみる

Twitterでこんなツイートを見つけました。

自然数nに対して操作F「nが偶数ならばnを2で割る。nが奇数ならばnを3倍して1を加える。」を10回行う。10回目で初めて1となる自然数を全て求めよ。(07京都高校生数学コンテスト)

プログラマとしては、さっとプログラムを書いて結果を出してみたくなるのではないでしょうか。

この解を出すアルゴリズムは大きく分けて2通りあります。
その1.1から1024(2^10)までの全ての数についてチェックして探す
その2.結果の「1」から逆に遡って探す

上記その1のアルゴリズムは2重ループによる処理で、その2は再帰による処理になります。
おそらく、この問題で求められている解き方は2の方法でしょう。

書いてみた
import java.util.ArrayList;
import java.util.Collection;


public class Main {

 /*
  * こいつを呼ぶ
  */
 public static void main(String[] x_args) {
  new Main().start();
 }

 public void start() {
  exec(new F10To1ImplVer1());
  System.out.println();
  exec(new F10To1ImplVer2());
 }

 void exec(F10To1 x_f) {
  {
   System.out.println(x_f.getClass().getSimpleName() + ":start.");
   long time_s = System.currentTimeMillis();
   int count = 10;
   int end = (int) Math.pow(2, count);
   Collection<Integer> values = x_f.getF10To1(end, count);
   for (Integer value : values) {
    System.out.print(value + "\t");
   }
   long time_e = System.currentTimeMillis();
   System.out.println();
   System.out.println(":end.");
   System.out.println("time => " + (time_e - time_s) + "ms");
  }
 }
 
}

interface F10To1 {

 /*
  * 戻り値Collection内の要素の順番は保証しない
  */
 Collection<Integer> getF10To1(int x_end, int x_count);
}

/**
 * 全ての自然数についてチェックするパターン
 */
class F10To1ImplVer1 implements F10To1 {

 public Collection<Integer> getF10To1(int x_end, int x_count) {
  Collection<Integer> result = new ArrayList<Integer>();
  // 1...最後までのループ
  for (int i = 1 ; i <= x_end ; i++) {
   if (isF10To1(i, x_count)) {
    result.add(Integer.valueOf(i));
   }
  }
  return result;
 }

 private boolean isF10To1(int x_value, int x_count) {
  int tmp = x_value;
  // 9回処理する
  for (int i = 0 ; i < x_count - 1 ; i++) {
   tmp = f(tmp);
   if (tmp == 1) {
    // 1になったら対象外
    return false;
   }
  }
  // 10回目
  tmp = f(tmp);
  return tmp == 1;
 }

 /*
  * 1回分の処理
  */
 private int f(int x_value) {
  if (x_value % 2 == 0) {
   return x_value / 2;
  }
  return x_value * 3 + 1;
 }
}

/**
 * 逆から辿るパターン
 */
class F10To1ImplVer2 implements F10To1 {

 public Collection<Integer> getF10To1(int x_end, int x_count) {
  int tmp = 1;
  // 再帰処理でチェックします
  Collection<Integer> result = new ArrayList<Integer>();
  revF(result, tmp, x_count);
  return result;
 }

 private void revF(Collection<Integer> x_values, int x_value, int count) {
  boolean can3 = canRevF_3(x_value);
  int v2 = revF_2(x_value);
  int v3 = revF_3(x_value);
  count--;
  if (count < 1) {
   if (can3) {
    x_values.add(Integer.valueOf(v3));
   }
   x_values.add(Integer.valueOf(v2));
   return;
  }
  if (can3 && (1 < v3)) {
   revF(x_values, v3, count);
  }
  if (v2 != 1) {
   revF(x_values, v2, count);
  }
 }

 /*
  * 引数が2で割った結果だとした場合の元の数を返す
  */
 private int revF_2(int x_value) {
  return x_value * 2;
 }

 /*
  * 引数が3倍して1を足した結果だとした場合に元の数が存在するか判断する
  */
 private boolean canRevF_3(int x_value) {
  if (x_value < 4) {
   return false;
  }
  if ((x_value - 1) % 3 != 0) {
   return false;
  }
  return (x_value - 1) / 3 % 2 == 1;
 }

 /*
  * 引数が3倍して1を足した結果だとした場合の元の数を返す
  */
 private int revF_3(int x_value) {
  return (x_value - 1) / 3;
 }
}
もしかしたら上記の実装に間違いがあるかもしれません。その場合は、ご指摘いただけるとありがたいです。
F10To1ImplVer1が総当り(その1)で、F10To1ImplVer2が再帰処理を行う(その2)クラスです。
10回程度のループではそれほど差が出ません(マシンにもよります)が、20回程に回数を増やすと極端に処理時間に差が出ます。

上記の2通りのアルゴリズムで、私が先に書いたのは総当り(その1)の方です。
その後、少し考えてその2の方法を書きました。

普段から簡単にプログラムを書こうとしているせいか、「本来はどういうアルゴリズムであるべきか」をあまり考えなくなってしまったようです。

私は細かい実装はあまり気にしないのですが、気にせずともより良いアルゴリズムで書けるのが理想的ですね。

2011年4月24日日曜日

パッケージ内のクラス一覧を取得する

普段からSpringなどのDIフレームワークを使っているのですが、 Springでは、xmlファイルでパッケージ名を指定すると、そのパッケージ内のクラスをインスタンス化して注入してくれます。
便利な機能なのでよく使うのですが、どのようにしてパッケージ名からクラス一覧を取得しているかを知りませんでした。

実装を知るには、ソースを読めば手っ取り早いですが、今回はソースは読まずに、実装方法はこんな感じだろうかと考えてみます。

パッケージ名から、そのパッケージ内のクラス一覧を取得する
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;

/**
 * 指定パッケージ内のクラス情報のコレクションを返す。
 * 
 * @param x_packageName パッケージ名
 * @return クラス情報のコレクション
 */
public static Collection<Class<? extends Object>> getClasses(String x_packageName) {
 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 JavaFileManager fm = compiler.getStandardFileManager(new DiagnosticCollector<JavaFileObject>(), null, null);

 // 一覧に含めるオブジェクト種別。(クラスのみを含める)
 Set<JavaFileObject.Kind> kind = new HashSet<JavaFileObject.Kind>();
 kind.add(JavaFileObject.Kind.CLASS);

 Collection<Class<? extends Object>> results = new ArrayList<Class<? extends Object>>();

 Iterable<JavaFileObject> jfos = null;
 try {
  jfos = fm.list(StandardLocation.CLASS_PATH, x_packageName, kind, false);
 } catch (IOException e) {
  e.printStackTrace();
 }
 if (jfos != null) {
  for (JavaFileObject fos : jfos) {
   try {
    Class<? extends Object> clazz = Class.forName(x_packageName + "." + fos.getName().replaceAll("\\.class$", ""));
    if (clazz.isEnum() || clazz.isInterface() || clazz.isAnonymousClass() || clazz.isLocalClass()) {
     continue;
    }
    results.add(clazz);
   } catch (ClassNotFoundException e) {
    e.printStackTrace();
   }
  }
 }
 return results;
}
使用するのはjava.lang.reflectパッケージのクラスかと思っていましたが、実際はjavax.toolsパッケージのクラスです。
これらのクラスがあるのは、javaのバージョン1.6以降のようです。
バージョン1.5以前ではもうちょっと面倒なやり方になると思います。

フレームワークの実装を知ることで、フレームワークの使い方を知ります。
今回は、実装を想像しただけですが...

2011年4月18日月曜日

ウェイトアップしています

現在、私は週2回ほど筋トレをしています。 始めたのは2010年8月頃で、半年以上は継続していることになります。
体の変化については、体重62kgから68kgになり、体脂肪は14%から15.5%になりました。 今のところはいい感じにウェイトアップしています。 ちなにみ、身長は180cmなので、まだまだ痩せ体型といえます。

今回は、筋トレについて。 まだまだ素人ですが、これまでの私の経験に基づいて、ウェイトアップの方法を説明します。
この説明が参考になる人は、おそらく私のような太りにくい痩せ型の体型でしょう。 該当する人も、そうでない人も、一例として見てもらいたいと思います。

まず、ウェイトアップで最も重要なのは、トレーニングよりも栄養の摂取です。 なので、今回は私が日頃、どのような栄養(サプリメント)を摂取しているかと、それはどのタイミングなのかについて説明します。

プロテイン
種類原材料特徴
ホエイ牛乳吸収が速い
カゼイン牛乳吸収が遅い
エッグ良質のタンパク質短時間で消化
大豆大豆健康に良く消化が早い
ザ・バルクアップトレーニングより
とりあえず、ホエイと大豆(ソイ)だけ注目しておけばよいです。 摂取する種類と時間については、運動前後はホエイ、就寝前は大豆(ソイ)が妥当でしょう。
炭水化物
特に痩せ体質の人が筋肉量アップをするには炭水化物がタンパク質以上に重要な役割を果たすと考えています。
ザ・バルクアップトレーニングより
サイトの説明通りで、プロテインより重要です。 炭水化物の摂取量が少ないと、体重は増えません。 私は以前に炭水化物を制限してトレーニングしていた事があるのですが、そのときは体重は全く増えず、体脂肪だけが落ちていきました。 それはそれでありかと思われるかもしれませんが、トレーニングしている事と健康面から考えて、十分に摂取すべきです。 少し多めに摂取して、超過した分はランニングすることで落とすくらいがちょうどいいでしょう。
BCAA
トレーニング時の疲労の軽減にも効果がありますのでハードなトレーニングでも疲労が軽減することで運動パフォーマンスを最大限に生かす事ができ、よりトレーニング効果を高めてくれます。
ザ・バルクアップトレーニングより
サイトの説明通りで、効率よくトレーニングするために必要になります。 筋トレの目的は、筋肉に可能な限り負荷をかけることです。 トレーニングをしていて、体力が無くなるより先に筋肉に十分な負荷をかけられればいいのですが、 プロテイン等を摂取していると、それよりも先に体力が無くなる事がありえます。 そうなると、効率よく負荷をかけ続けることが難しくなるので、疲労を軽減するBCAAをトレーニング前に摂取しておきます。
グルタミン
グルタミンは筋肉の分解を抑えてくれるのでウエイトとレーニン後には必ず摂っておきたいサプリメントです。
グルタミンは体内のアミノ酸の60%を占めていることからも異化を抑えてくれるサプリメントの中でも一番効果的です。
ザ・バルクアップトレーニングより
筋肉を増加させるための筋トレですが、その筋トレ中(主に筋トレ後)にも筋肉は異化(分解みたいなもの)します。 そもそも、筋肉が異化するのは体内のタンパク質(アミノ酸)が不足しているためなので、異化する筋肉の代替として摂取します。 即効性のプロテインのようなものと考えればいいかと思います。
私が購入しているサプリメント
グルタミン DNS グルタミンパウダー(300g)
BCAA DNS BCAAパウダー(200g)
ホエイプロテイン ザバス(SAVAS) ホエイプロテイン100 ココア味 360g
ホエイプロテイン(ウェイトアップ) ザバス(SAVAS) ウェイトアップ 360g
大豆(ソイ)プロテイン ザバス(SAVAS) ウエイトダウン 360g
ホエイプロテイン(ウェイトアップ)はタンパク質の量が少なく、炭水化物が含まれています。
全部一括で買い揃えると相当の金額になります。
いったん揃えてしまえば、あとは無くなったものを補充していくだけなので、金額的にはそれほど気にならないと思います。
上記のプロテインは飲みやすさで選んでいますが、探せば他にも安いものや、高価なものがあります。
実際にはどのように摂取しているか
週7日のうち、2日がトレーニング日で、時間帯は18:00~21:00の間の90分です。その前後と就寝前に摂取しています。
また、運動後はすぐに炭水化物を摂取するようにします。無理矢理でも詰め込みます。
トレーニング前トレーニング後就寝前
トレーニング日ホエイプロテイン
BCAA
ホエイプロテイン(ウェイトアップ)
グルタミン
トレーニングの無い日大豆プロテイン
グルタミン
注意してほしい点が一つ
サプリメントを摂取するということについて、特にホエイプロテインは動物性タンパク質なので、 どうしても胃や腸に大きな負担をかけてしまいます。
サプリメントの過剰な摂取は避けて、日頃の食事で野菜やヨーグルト、ビタミン剤を摂るようにしたほうがよいでしょう。
ちなみに、私は、スキムミルクで作る自家製ヨーグルトを毎朝食べて腸の調子を整えています。

2011年4月8日金曜日

Struts2 + Spring + Slim3 サンプル

今回は、 【修正版】 Struts2 + Spring + Slim3で構築したフレームワークを使ってサンプルのソースを書いてみます。

サンプルは簡単なデータ登録と表示の実装です。

作成するファイル一覧
今回、作成するファイルは以下の通りです。それ以外のファイルは一切触りません。
ファイル一覧
/src/com/brightgenerous/s3/sample/action/Sample_Action.java
/src/com/brightgenerous/s3/sample/action/Sample_Service.java
/src/com/brightgenerous/s3/sample/action/Sample_ServiceImpl.java
/src/com/brightgenerous/s3/sample/data/model/Account.java
/src/com/brightgenerous/s3/sample/data/meta/AccountMeta.java
/war/WEB-INF/content/sample/index.jsp
/war/WEB-INF/content/sample/show.jsp
「Account.java」と「AccountMeta.java」はSlim3のプラグインを使って自動生成します。
javaのソースコード
1.com.brightgenerous.s3.sample.action.Sample_Action.java
package com.brightgenerous.s3.sample.action;

import java.util.List;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.springframework.beans.factory.annotation.Autowired;

import com.brightgenerous.s3.sample.data.model.Account;
import com.opensymphony.xwork2.ModelDriven;

@ParentPackage("struts-default")
@Namespace("/sample")
public class Sample_Action implements ModelDriven {

 private Account p_model = new Account();

 @Autowired
 private Sample_Service p_service;

 @Override
 public Account getModel() {
  return p_model;
 }

 public List getAccounts() {
  return p_service.get();
 }

 /**
  * [Action:初期表示]
  */
 @Action("index")
 public String index() {

  return "index";
 }

 /**
  * [Action:表示]
  */
 @Action("show")
 public String show() {

  return "show";
 }

 /**
  * [Action:登録]
  */
 @Action("regist")
 public String regist() {
  p_service.regist(p_model);
  return show();
 }
}
2.com.brightgenerous.s3.sample.action.Sample_Service.java
package com.brightgenerous.s3.sample.action;

import java.util.List;

import com.brightgenerous.s3.sample.data.model.Account;

interface Sample_Service {

 Account regist(Account x_account);

 List<Account> get();
}
3.com.brightgenerous.s3.sample.action.Sample_ServiceImpl.java
package com.brightgenerous.s3.sample.action;

import java.util.List;

import org.slim3.datastore.Datastore;
import org.springframework.stereotype.Component;

import com.brightgenerous.s3.sample.data.meta.AccountMeta;
import com.brightgenerous.s3.sample.data.model.Account;

@Component
class Sample_ServiceImpl implements Sample_Service {

 @Slim3GlobalTransaction
 private GlobalTransaction p_tx;

 @Override
 public Account regist(Account x_account) {
  p_tx.put(x_account);
  return x_account;
 }

 @Override
 public List get() {
  AccountMeta meta = AccountMeta.get();
  return Datastore.query(meta).asList();
 }
}
4.com.brightgenerous.s3.sample.data.model.Account.java
package com.brightgenerous.s3.sample.data.model;

import java.io.Serializable;

import com.google.appengine.api.datastore.Key;

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Model;

@Model(schemaVersion = 1)
public class Account implements Serializable {

 private static final long serialVersionUID = 1L;

 @Attribute(primaryKey = true)
 private Key key;

 @Attribute(version = true)
 private Long version;

 private String nickname;

 private String email;

 /**
  * Returns the key.
  * 
  * @return the key
  */
 public Key getKey() {
  return key;
 }

 /**
  * Sets the key.
  * 
  * @param key the key
  */
 public void setKey(Key key) {
  this.key = key;
 }

 /**
  * Returns the version.
  * 
  * @return the version
  */
 public Long getVersion() {
  return version;
 }

 /**
  * Sets the version.
  * 
  * @param version the version
  */
 public void setVersion(Long version) {
  this.version = version;
 }

 public String getNickname() {
  return nickname;
 }

 public void setNickname(String x_nickname) {
  nickname = x_nickname;
 }

 public String getEmail() {
  return email;
 }

 public void setEmail(String x_email) {
  email = x_email;
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((key == null) ? 0 : key.hashCode());
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj) {
   return true;
  }
  if (obj == null) {
   return false;
  }
  if (getClass() != obj.getClass()) {
   return false;
  }
  Account other = (Account) obj;
  if (key == null) {
   if (other.key != null) {
    return false;
   }
  } else if (!key.equals(other.key)) {
   return false;
  }
  return true;
 }
}
Slim3プラグインのantから「gen-model」を選択して、自動生成されたソースをもとにしています。
追加部分は「nickname」と「email」の変数と、そのgetter・setterだけです。
jspのソースコード
1./WEB-INF/content/sample/index.jsp
<%@page pageEncoding="UTF-8" isELIgnored="false" session="false"%>
<%@ taglib uri="/struts-tags" prefix="s" %>


 
 
 

 

 表示
2./WEB-INF/content/sample/show.jsp
<%@page pageEncoding="UTF-8" isELIgnored="false" session="false"%>
<%@ taglib uri="/struts-tags" prefix="s" %>


  
ニックネームEmail
戻る

以上で実装は完了です。

ここまで書いておいて、言ってしまうのですが、GlobalTransactionのインスタンスが直に注入されるのは気持ち悪いです。無理にSpringを使うことは無いと思い直しました。さらに、Springだけが原因かは調べていませんが、spin-upの時間もかなり掛かっています。

実行してみる
折角なので、動かしてみます。

Transactionは上手く機能しました。画像ではお見せできていませんが、例外が発生した場合は、ちゃんとrollbackしてくれています。

なんとかNo-configurationで書けたのですが、この方法を続けるにはDIを軽量化する必要があるのかなぁ...と思ってみたりみなかったり。

近いうちに、GWTとStruts2の連携についても書ければいいかな、なんて考えています。