2003年
「世紀の大発見?!」でおなじみのやまろうです。
何が大発見っていうと、まさに今回のテーマ「EJB Strategyパターン」
です。今世の中では「デザインパターン」「J2EEパターン」
「EJBデザインパターン」なんてものがありますが、わたくし自身が
新たなるパターンを考え出しました。
それが「EJB Strategyパターン」です。簡単に言うと、「EJBデザインパターン」
の中で出てくる「EJB Commandパターン」のStateful版といったものです。
誰でも思いつきそうな簡単なもので、しかも汎用的に使えるものなので、
既に誰か思いついているんじゃないのか?と思って「J2EEパターン」
「EJBデザインパターン」を調べ、インターネットで検索してみた
のですが、見当たりませんでした。唯一似てたのが、
「EJB Commandパターン」だったというわけです。ということで
世界初の発見かもしれません。
それでは説明を開始します。J2EEパターンの書式を真似してみました。
■文脈
EJBは、開発するのに、ホームインターフェース、Bean本体
リモート(ローカル)インターフェースを作らなければなりません。
さらにデプロイするために、DD(Deployment Discripter)を作らなければ
なりません。
■問題
EJB開発には、以下の問題が発生します。
・ビジネスロジックの実装(Bean本体)以外に作らなければならない
ものが多く手間が掛かる。
・デバッグ作業の手間が多い。
コーディング→コンパイル→jarの作成→earの作成→デプロイ→テスト実行
の繰り返し
■フォース
・EJBを一つだけ作ります。
・ビジネスロジックを実装した普通のJavaBeansを作り、EJBに渡します。
・EJBは渡されたJavaBeansを実行します。
■解決策
EJBにStrategyパターンを適用します。Strategyを実行するEJBを一つ
だけつくり、各ビジネスロジックをStrategyクラス(普通のJavaBeans)
に実装します。
Strategyクラスを作るだけで、各ビジネスロジックを実行する
EJBを開発することが出来ます。
というわけで、要は入り口となるEJBをひとつ作って、そいつは渡された
クラスを実行する。つまり、渡すクラスのみを開発すればいいって
わけです。それではソースです。
package ejbstrategy;
import javax.ejb.CreateException;
import java.rmi.RemoteException;
import javax.ejb.EJBHome;
public interface StrategyServerHome extends EJBHome {
public StrategyServer create() throws CreateException, RemoteException;
}
package ejbstrategy;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface StrategyServer extends EJBObject {
public void setStrategy(EJBStrategy strategy) throws RemoteException;
public Object handle(Event event) throws Exception, RemoteException;
}
package ejbstrategy;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.CreateException;
/**
* 渡されたStrategyを実行するEJB
*/
public class StrategyServerBean
implements SessionBean {
/** セッションコンテキスト */
private SessionContext context;
/** 処理を委譲するオブジェクト */
private EJBStrategy strategy;
/**
* strategyに処理を委譲する。
* @param event イベント名とパラメータ
* @exception Exception 例外
*/
public Object handle(Event event) throws Exception {
return strategy.handle(event);
}
/**
* strategyを設定する。
* @param strategy 戦略
*/
public void setStrategy(EJBStrategy strategy) {
this.strategy = strategy;
}
public void ejbCreate() throws CreateException {}
public void setSessionContext(SessionContext context) {
this.context = context;
}
public void ejbRemove() {}
public void ejbActivate() {}
public void ejbPassivate() {}
}
package ejbstrategy;
import java.io.Serializable;
/**
* StrategyServer実行時に渡すオブジェクト
*/
public class Event implements Serializable {
/** イベント名 */
private String name;
/** データ */
private Object data;
public String getName() {
return name;
}
public Object getData() {
return data;
}
public void setName(String name) {
this.name = name;
}
public void setData(Object data) {
this.data = data;
}
}
package ejbstrategy;
import java.io.Serializable;
/**
* StrategyServerに渡すStrategyのインターフェース。
*/
public interface EJBStrategy extends Serializable {
public Object handle(Event event) throws Exception;
}
package sample;
import ejbstrategy.*;
/**
* パターン説明の為のサンプルStrategy
*/
public class SampleStrategy implements EJBStrategy {
public Object handle(Event event) {
Object result = null;
String name = event.getName();
if (name.equals("doLoad")) {
//DBを読み込む等の処理をする。サンプルなので適当な値を返すだけ
result = (String) event.getData() + " executed load"; //(a)
} else if (name.equals("doUpdate")) {
//DBを更新する等の処理をする。サンプルなので適当な値を返すだけ
result = (String) event.getData() + " executed update"; //(b)
}
return result;
}
}
package sample;
import java.util.*;
import java.io.*;
import javax.ejb.*;
import javax.rmi.*;
import javax.naming.*;
/**
* EJBのlookup処理を行うユーティリティ
*/
public class EJBHomeFactory {
private static EJBHomeFactory instance = new EJBHomeFactory();
private Context context;
private Map homeMap = new HashMap();
private ResourceBundle bundle = ResourceBundle.getBundle("lookup");
private EJBHomeFactory() {
try {
Properties prop = new Properties();
InputStream in = super.getClass().getResourceAsStream(
"/initialContext.properties");
prop.load(in);
context = new InitialContext(prop);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public static EJBHomeFactory getInstance() {
return instance;
}
public synchronized EJBHome getEJBHome(String ejbName, Class homeClass) {
EJBHome home = null;
try {
home = (EJBHome) homeMap.get(homeClass);
if (home != null) {
return home;
}
Object objref = context.lookup(bundle.getString(ejbName));
home = (EJBHome)PortableRemoteObject.narrow(objref, homeClass);
homeMap.put(homeClass, home);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return home;
}
}
package sample;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import ejbstrategy.*;
/**
* パターン説明の為のサンプルServlet
*/
public class SampleServlet extends HttpServlet {
private static final String CONTENT_TYPE = "text/html; charset=Shift_JIS";
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Object result1 = null;
Object result2 = null;
try {
EJBHomeFactory factory = EJBHomeFactory.getInstance(); //(1)
StrategyServerHome home = (StrategyServerHome)
factory.getEJBHome("StrategyServer",
StrategyServerHome.class);
StrategyServer bean = home.create(); //(2)
SampleStrategy strategy = new SampleStrategy(); //(3)
bean.setStrategy(strategy); //(4)
Event event = new Event(); //(5)
event.setData("sampledata1");
event.setName("doLoad"); //(6)
result1 = bean.handle(event); //(7)
event.setData("sampledata2");
event.setName("doUpdate");
result2 = bean.handle(event); //(8)
} catch (Exception ex) {
throw new ServletException(ex);
}
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>SampleServlet</title></head>");
out.println("<body bgcolor=\"#ffffff\">");
out.println("result1=" + result1);
out.println("result2=" + result2);
out.println("</body></html>");
}
}
SampleServletを実行すると、
[実行結果]
result2=sampledata2 executed update
とブラウザ上に表示されます。
[ソースを追ってみよう!]
(3)でSampleStrategyクラスを生成してます。このクラスがEJBに渡すクラス、
実際に業務ロジックを実装したクラス。EJBStrategyインターフェースを
実装(implements)してます。(4)でSampleStrategyをEJBに渡します。
(5)から(6)でEJBに渡すEventオブジェクトを生成して値を設定しています。
簡単に言えばEJBのメソッド実行時に渡すパラメータオブジェクトです。
Eventオブジェクトはイベント名とデータを持っています。このプログラムでは
setDataで文字列を設定していますが、Object型なので、任意のValueObjectや
コレクションクラスを設定することが出来ます。
StrategyServerクラスはhandleメソッドしかないので、複数の処理を実装する場合、
handleメソッドの中でEventのnameを見て処理を分岐させます。そしてdataを
使って処理を実行します。そして戻り値の型はObject型なので、どんなObject
でも返すことが出来ます。(但しEJBなのでSerializableなObjectに限る)
(7)でhandleメソッドを実行。Eventのnameに"doLoad"を指定したので
SampleStrategyクラスの(a)が実行されます。
(8)ではEventのnameに"doUpdate"を指定したのでSampleStrategyクラスの(b)が
実行されます。
そんなわけでEJB Strategyパターンを使えば、ロジック(Strategy)をEJBに渡して、
EJBに実行させることが出来ます。そして、EJB Commandパターンと違い、状態を
Strategyの中に保持することが出来ます。なのでStateful SessionBeanを
実装できます。
いかがだったでしょうか?EJB CommandパターンとStrategyパターンの特性を
組み合わせたEJB Strategyパターン。それぞれの業務プロセス用に
Strategyクラスのみを開発すればEJBアプリケーションが出来上がるので
生産性が向上するはずです。
配備記述子は以下の通りです。
[ejb-jar.xml]
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD [次行へ]
Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>
<ejb-jar>
<display-name>StrategyServer</display-name>
<enterprise-beans>
<session>
<ejb-name>StrategyServer</ejb-name>
<home>ejbstrategy.StrategyServerHome</home>
<remote>ejbstrategy.StrategyServer</remote>
<ejb-class>ejbstrategy.StrategyServerBean</ejb-class>
<session-type>Stateful</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction> (#)
<method>
<ejb-name>StrategyServer</ejb-name>
<method-name>handle</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
とても良いアイデアだと思ったのですがここで、問題があります。
本来EJBはメソッド毎にトランザクション属性を指定できるのですが、
StrategyServerBeanには、handleメソッド一つしかなく、
これをイベント名によって処理を分岐させて使いまわしているので、
トランザクション属性を処理によって変更することが出来ません。
「更新処理の時はRequirdだけど、検索の時は
Supportsでいいのになー」といった要求に答えられません。
ちなみに(#)のとこでトランザクションの指定をしています。
次回はこの問題を解決したいと思います。お楽しみに!!
やまろう