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の連携についても書ければいいかな、なんて考えています。

2011年3月31日木曜日

enumを使いたおしたい

こういうパターンはいかがでしょうか?という内容です。

javaのenumについてです。

先日、ふと、Effective Javaを読み直してenumについての認識を改めました。

enumの変数というのは、「唯一のインスタンス」
その結果、定数のようにイコールの演算子で一致するのです。

以前からそれは理解していました。
が、実は、思ってた以上に普通のクラスと同じで、インターフェースを実装できます。
さらには、mainメソッドを書いて実行することもできるのです。

これを定数としてしか使わないのは勿体無い。
ということで、有効活用をちょっと考えてみた。

こういうのはいかが?
package sample;

import java.math.BigDecimal;

enum NumberUtility {

 SIMPLE {

  @Override
  Integer createInteger(String x_str) {
   return Integer.valueOf(x_str);
  }

  @Override
  BigDecimal createBigDecimal(String x_str) {
   return new BigDecimal(x_str);
  }
 },

 QUIET {

  @Override
  Integer createInteger(String x_str) {
   Integer result = null;
   if (x_str != null) {
    try {
     result = SIMPLE.createInteger(x_str);
    } catch (NumberFormatException e) {
    }
   }
   return result;
  }

  @Override
  BigDecimal createBigDecimal(String x_args) {
   BigDecimal result = null;
   if (x_args != null) {
    try {
     result = SIMPLE.createBigDecimal(x_args);
    } catch (NumberFormatException e) {
    }
   }
   return result;
  }
 };

 abstract Integer createInteger(String x_str);

 abstract BigDecimal createBigDecimal(String x_str);
}

abstract class Test {

 void exec() {
  System.out.println("100 => " + getNumberUtility().createInteger("100"));
  System.out.println("10.0 => " + getNumberUtility().createBigDecimal("10.0"));
  System.out.println("null => " + getNumberUtility().createInteger("null"));
 }

 abstract NumberUtility getNumberUtility();
}

public class TestMain {

 public static void main(String[] args) {

  try {
   new Test() {

    @Override
    NumberUtility getNumberUtility() {
     return NumberUtility.SIMPLE;
    }
   }.exec();
  } catch (Exception e) {
   System.out.println("catch exception. => " + e.getMessage());
  }
 
  System.out.println();

  try {
   new Test() {

    @Override
    NumberUtility getNumberUtility() {
     return NumberUtility.QUIET;
    }
   }.exec();
  } catch (Exception e) {
   System.out.println("catch exception. => " + e.getMessage());
  }
 }
}
enumの中にabstractメソッドがあって、その場で実装しています。こんなことができるんですねぇ。知りませんでした。
書いた内容について簡単に言うと、Utilityクラスの切り替えです。
Utilityの処理の役割としてはstaticなメソッドで実装したいのだけど、ロジックの違いをインスタンスで変えたいって感じです。

Javaの言語仕様での、enumの実現方法がシンプルだからこそ、これだけの自由度があるのでしょう。
単純さが自由になります。

1行目でエラーが出ましたと言わせたい

今回は、ちょっとしたネタです。
知っている人も多いかもしれません。

内容はタイトルの通り「ソースコードの1行目でエラーを出す」方法です。
もしかすると、JVMに依存するかもしれませんのであしからず。

エラーを吐くコードを1行目に書く方法
public class ExceptionAtLine1 {public static void main(String[] x_args) { System.out.println(x_args[100]); } }
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
 at ExceptionAtLine1.main(ExceptionAtLine1.java:1)
すぐに思いつく方法です。

いや、そんなの当たり前だろって思われてる人も多いでしょう。

では、以下の方法はどうでしょうか?

Genericsを ”利用して” エラーを吐く方法
package sample;

public class ExceptionAtLine1Zwei {

 public static void main(String[] x_args) {
  IMethod method = new IMethodImpl();
  method.method(Integer.valueOf(100));
 }
}

interface IMethod {
 void method(T x_obj);
}

class IMethodImpl implements IMethod {

 @Override
 public void method(String x_obj) {
  System.out.println(x_obj);
 }
}
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
 at sample.IMethodImpl.method(ExceptionAtLine1Zwei.java:1)
 at sample.ExceptionAtLine1Zwei.main(ExceptionAtLine1Zwei.java:7)
どう見ても呼ばれるべきメソッドがありませんね。
Genericsを中途半端に使用すると恐ろしいことになる良い例です。

Eclipseで警告が出るようにしておきましょう。
厳しさは易しさです。

2011年3月26日土曜日

【修正版】 Struts2 + Spring + Slim3

「Struts2 + Spring + Slim3の連携に挑戦」の修正版です。

前回の内容を修正しました。
改めてSlim3のLocalTransactionとGlobalTransactionについて調べた結果、前回の内容では不完全だったことがわかりました。

今回は完全に書き直しです。

Slim3の新規プロジェクト作成
まず最初に、EclipseのSlim3プラグインを使用してSlim3の新規プロジェクトを作成します。
プロジェクト名とルートパッケージ
Project names3sample
Root Packagecom.brightgenerous.s3.sample.data
不要なファイルを削除しておきます。
削除するファイルとディレクトリ
/src/application_en.properties
/src/application_ja.properties
/war/css
/war/ktrwjr
/war/common.jsp
Jarファイル追加
以下のjarファイルを「war/WEB-INF/lib」に追加します。
追加するjarファイル
aopalliance-1.0
commons-beanutils-1.7.0
commons-digester-2.0
commons-fileupload-1.2.1
commons-io-1.3.2
commons-lang-2.3
commons-logging-1.0.4
commons-validator-1.3.1
freemarker-gae-pre3
javassist
ognl-3.0
spring-aop
spring-beans
spring-context
spring-core
spring-tx
spring-web
struts2-convention-plugin-2.2.1.1
struts2-core-2.2.1.1
struts2-spring-plugin-2.2.1.1
xwork-core-2.2.1.1
freemarker-gae-pre3は、gae用のfreemarkerです。
Springは2.5を使用します。
web.xml
「war/WEB-INF」に配置するweb.xmlファイルです。


 
  slim3.rootPackage
  com.brightgenerous.s3.sample.data
 

 
  contextConfigLocation
  classpath:applicationContext*.xml
 

 
  DatastoreFilter
  org.slim3.datastore.DatastoreFilter
 

 
  struts2
  org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
  
   actionPackages
   com.brightgenerous.s3.sample.action
  
 

 
  DatastoreFilter
  /*
  REQUEST
 
 
  struts2
  /*
 

 
  GlobalTransactionServlet
  org.slim3.datastore.GlobalTransactionServlet
  1
 

 
  GlobalTransactionServlet
  /slim3/gtx
 

 
  org.springframework.web.context.ContextLoaderListener
 
 
  com.brightgenerous.s3.sample.gae.GaeInitListener
 

 
  
   /slim3/gtx
  
  
   admin
  
 


Slim3の新規プロジェクトを作成して生成されるweb.xmlの内容のほとんど少しを消すことになります。
「com.brightgenerous.s3.sample.gae.GaeInitListener」は、独自に実装するクラスです。ソースは「実装するクラス」にて後述します。
applicationContext.xml
「classpath:applicationContext*.xml」(src直下)に配置します。


 

 

 

 
 
  
   
    PROPAGATION_REQUIRED
   
  
 
 
  
  
 
 
  
   
    transactionInterceptor
   
  
  
   
    *ServiceImpl
   
  
 


「transactionManager」のみ用意してやれば他はそのまま使えます。なかなかナイスな設計です。
「transactionManager」と「transactionInterceptor」を用意します。
「com.brightgenerous.s3.sample.gae.GaeFixInternalPersistenceAnnotationProcessor」、 「com.brightgenerous.s3.sample.gae.Slim3TransactionManager」、 「com.brightgenerous.s3.sample.gae.Slim3TransactionInterceptor」については、後述です。
実装するクラス
1.com.brightgenerous.s3.sample.gae.GaeInitListener
package com.brightgenerous.s3.sample.gae;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import ognl.OgnlRuntime;

public class GaeInitListener implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {

 @Override
 public void contextInitialized(ServletContextEvent sce) {
  OgnlRuntime.setSecurityManager(null);
 }

 @Override
 public void contextDestroyed(ServletContextEvent arg0) {
 }

 @Override
 public void sessionCreated(HttpSessionEvent arg0) {
 }

 @Override
 public void sessionDestroyed(HttpSessionEvent arg0) {
 }

 @Override
 public void attributeAdded(HttpSessionBindingEvent arg0) {
 }

 @Override
 public void attributeRemoved(HttpSessionBindingEvent arg0) {
 }

 @Override
 public void attributeReplaced(HttpSessionBindingEvent arg0) {
 }
}
OGNLのセキュリティマネージャがなんとかかんとか...理由については、詳しく調べていません。
2.com.brightgenerous.s3.sample.gae.GaeFixInternalPersistenceAnnotationProcessor
package com.brightgenerous.s3.sample.gae;

public class GaeFixInternalPersistenceAnnotationProcessor {
}
「java.lang.NoClassDefFoundError」を回避することだけが目的です。
3.com.brightgenerous.s3.sample.gae.Slim3TransactionManager
package com.brightgenerous.s3.sample.gae;

import org.slim3.datastore.Datastore;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;

public class Slim3TransactionManager extends AbstractPlatformTransactionManager {

 private static final long serialVersionUID = 3423679590583692519L;

 @Override
 protected void doBegin(Object x_arg0, TransactionDefinition x_arg1) throws TransactionException {
  TransactionObject transactionObject = (TransactionObject) x_arg0;
  transactionObject.setTransaction(Datastore.beginGlobalTransaction());
 }

 @Override
 protected void doCommit(DefaultTransactionStatus x_arg0) throws TransactionException {
  TransactionObject transactionObject = (TransactionObject) x_arg0.getTransaction();
  transactionObject.getTransaction().commit();
 }

 @Override
 protected Object doGetTransaction() throws TransactionException {
  return new TransactionObject();
 }

 @Override
 protected void doRollback(DefaultTransactionStatus x_arg0) throws TransactionException {
  TransactionObject transactionObject = (TransactionObject) x_arg0.getTransaction();
  transactionObject.getTransaction().rollback();
 }
}
package com.brightgenerous.s3.sample.gae;

import java.io.Serializable;

import org.slim3.datastore.GlobalTransaction;

public class TransactionObject implements Serializable {

 private static final long serialVersionUID = -8353097480608667182L;

 private GlobalTransaction p_transaction;

 public GlobalTransaction getTransaction() {
  return p_transaction;
 }

 public void setTransaction(GlobalTransaction x_transaction) {
  p_transaction = x_transaction;
 }
}
package com.brightgenerous.s3.sample.gae;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Slim3GlobalTransaction {
}
「org.springframework.jdbc.datasource.DataSourceTransactionManager」の実装を参考にして、Slim3用に簡単に対応させています。
「TransactionObject」、「Slim3GlobalTransaction」も作成します。 このクラスのみ自作です。間違い等あれば指摘していただけるとありがたいです。
4.com.brightgenerous.s3.sample.gae.Slim3TransactionInterceptor
 private void injectTransaction(Object x_obj, TransactionInfo x_txInfo) {
  TransactionStatus txStatus = x_txInfo.getTransactionStatus();
  DefaultTransactionStatus dtxStatus;
  if (!(txStatus instanceof DefaultTransactionStatus)) {
   return;
  }
  dtxStatus = (DefaultTransactionStatus) txStatus;
  Object tx = dtxStatus.getTransaction();
  if (tx instanceof TransactionObject) {
   tx = ((TransactionObject) tx).getTransaction();
  }
  if (!(tx instanceof GlobalTransaction)) {
   return;
  }
  @SuppressWarnings("rawtypes")
  Class clazz = x_obj.getClass();
  do {
   Field[] fields = clazz.getDeclaredFields();
   for (Field field : fields) {
    if (!field.isAccessible()) {
     field.setAccessible(true);
    }
    Slim3GlobalTransaction s3gtx = field.getAnnotation(Slim3GlobalTransaction.class);
    if (s3gtx != null) {
     if (field.getType().isInstance(tx)) {
      try {
       field.set(x_obj, tx);
      } catch (IllegalAccessException e) {
       throw new RuntimeException(e);
      } catch (IllegalArgumentException e) {
       throw new RuntimeException(e);
      }
     }
    }
   }
   clazz = clazz.getSuperclass();
  } while ((clazz != null) && !clazz.equals(Object.class));
 }
「org.springframework.transaction.interceptor.TransactionInterceptor」のソースをコピーしてきて、 上記のメソッドが適当な箇所で呼ばれるように書き換えます。
ライセンスを考慮して全体は公開しませんが、元のソースを読めば変更箇所はわかるはずです。

以上でStruts2 + Spring + Slim3の設定は完了です。
次回こそ、このフレームワークを使用して簡単なサンプルを書いてみます。

2011年3月24日木曜日

Struts2 + Spring + Slim3

この内容は修正されました。
修正後はこちら

Struts2 + Spring + Slim3の連携に挑戦。

ソースの省略はしません。
今回と次回で、簡単なサンプルを動作させるまでの手順を載せます。

Slim3の新規プロジェクト作成
まず最初に、EclipseのSlim3プラグインを使用してSlim3の新規プロジェクトを作成します。
プロジェクト名とルートパッケージ
Project names3sample
Root Packagecom.brightgenerous.s3.sample.data
不要なファイルを削除しておきます。
削除するファイルとディレクトリ
/src/application_en.properties
/src/application_ja.properties
/war/css
/war/ktrwjr
/war/common.jsp
Jarファイル追加
以下のjarファイルを「war/WEB-INF/lib」に追加します。
追加するjarファイル
aopalliance-1.0
commons-beanutils-1.7.0
commons-digester-2.0
commons-fileupload-1.2.1
commons-io-1.3.2
commons-lang-2.3
commons-logging-1.0.4
commons-validator-1.3.1
freemarker-gae-pre3
javassist
ognl-3.0
spring-aop
spring-beans
spring-context
spring-core
spring-tx
spring-web
struts2-convention-plugin-2.2.1.1
struts2-core-2.2.1.1
struts2-spring-plugin-2.2.1.1
xwork-core-2.2.1.1
freemarker-gae-pre3は、gae用のfreemarkerです。
Springは2.5を使用します。
web.xml
「war/WEB-INF」に配置するweb.xmlファイルです。



 
  slim3.rootPackage
  com.brightgenerous.s3.sample.data
 

 
  contextConfigLocation
  classpath:applicationContext*.xml
 

 
  struts2
  org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
  
   actionPackages
   com.brightgenerous.s3.sample.action
  
 

 
  struts2
  /*
 

 
  org.springframework.web.context.ContextLoaderListener
 
 
  com.brightgenerous.s3.sample.gae.GaeInitListener
 


Slim3の新規プロジェクトを作成して生成されるweb.xmlの内容のほとんどを消すことになります。
「com.brightgenerous.s3.sample.gae.GaeInitListener」は、独自に実装するクラスです。ソースは「実装するクラス」にて後述します。
applicationContext.xml
「classpath:applicationContext*.xml」(src直下)に配置します。




 

 

 
 

 
 
 
  
   
    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRED,readOnly
   
  
 
 
  
  
 
 
  
   
    transactionInterceptor
   
  
  
   
    *ServiceImpl
   
  
 


「transactionManager」のみ用意してやれば他はそのまま使えます。なかなかナイスな設計です。
「com.brightgenerous.s3.sample.gae.GaeFixInternalPersistenceAnnotationProcessor」、「com.brightgenerous.s3.sample.gae.Slim3TransactionManager」については、後述です。
実装するクラス
1.com.brightgenerous.s3.sample.gae.GaeInitListener
package com.brightgenerous.s3.sample.gae;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import ognl.OgnlRuntime;

public class GaeInitListener implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {

 @Override
 public void contextInitialized(ServletContextEvent sce) {
  OgnlRuntime.setSecurityManager(null);
 }

 @Override
 public void contextDestroyed(ServletContextEvent arg0) {
 }

 @Override
 public void sessionCreated(HttpSessionEvent arg0) {
 }

 @Override
 public void sessionDestroyed(HttpSessionEvent arg0) {
 }

 @Override
 public void attributeAdded(HttpSessionBindingEvent arg0) {
 }

 @Override
 public void attributeRemoved(HttpSessionBindingEvent arg0) {
 }

 @Override
 public void attributeReplaced(HttpSessionBindingEvent arg0) {
 }
}
OGNLのセキュリティマネージャがなんとかかんとか...理由については、詳しく調べていません。
2.com.brightgenerous.s3.sample.gae.GaeFixInternalPersistenceAnnotationProcessor
package com.brightgenerous.s3.sample.gae;

public class GaeFixInternalPersistenceAnnotationProcessor {

 public GaeFixInternalPersistenceAnnotationProcessor() {
 }
}
「java.lang.NoClassDefFoundError」を回避することだけが目的です。
3.com.brightgenerous.s3.sample.gae.Slim3TransactionManager
package com.brightgenerous.s3.sample.gae;

import java.io.Serializable;

import org.slim3.datastore.Datastore;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;

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

public class Slim3TransactionManager extends AbstractPlatformTransactionManager {

 private static final long serialVersionUID = 3423679590583692519L;

 @Override
 protected void doBegin(Object x_arg0, TransactionDefinition x_arg1) throws TransactionException {
  TransactionObject transactionObject = (TransactionObject) x_arg0;
  transactionObject.p_transaction = Datastore.beginTransaction();
 }

 @Override
 protected void doCommit(DefaultTransactionStatus x_arg0) throws TransactionException {
  TransactionObject transactionObject = (TransactionObject) x_arg0.getTransaction();
  transactionObject.p_transaction.commit();
 }

 @Override
 protected Object doGetTransaction() throws TransactionException {
  return new TransactionObject();
 }

 @Override
 protected void doRollback(DefaultTransactionStatus x_arg0) throws TransactionException {
  TransactionObject transactionObject = (TransactionObject) x_arg0.getTransaction();
  transactionObject.p_transaction.rollback();
 }

 private class TransactionObject implements Serializable {

  private static final long serialVersionUID = -8353097480608667182L;

  Transaction p_transaction;
 }
}
「org.springframework.jdbc.datasource.DataSourceTransactionManager」の実装を参考にして、Slim3用に簡単に対応させています。
このクラスのみ自作です。間違い等あれば指摘していただけるとありがたいです。

以上でStruts2 + Spring + Slim3の設定は完了です。
次回は、このフレームワークを使用して簡単なサンプルを書いてみます。