2003年
前回の続き、EJB Strategyパターンというデザインパターンを
思いついたやまろうでしたが、問題点が発覚しました。
その問題点とは、「トランザクションの制御が非常に粗くなる」
というものでした。これはこのパターンが
StrategyServerEJBのObject handle(Event event)
というメソッドを使いまわす為、指定できるトランザクションが
一つに限られるというものでした。
そこで、
1.リモートメソッドを全トランザクション属性分用意する。
2.EJBから処理を委譲されるEJBStrategyインターフェースにも
メソッドを全トランザクション属性分用意する。
3.EJBStrategyインターフェースのメソッド数が増えたため、
インターフェースを空実装したEJBStrategyAdapterを作成する。
ことにしました。さらに、
EJB呼び出しはlookupしたり、HomeからRemoteをcreateしたりと
面倒である為、J2EEパターン・EJBデザインパターンのBusiness Delegate
クラス(StrategyServerDelegate)を作成しました。
package ejbstrategy; import javax.ejb.CreateException; import java.rmi.RemoteException; import javax.ejb.EJBHome; /** * 渡されたStrategyを実行するEJBのHome Interface */ public interface StrategyServerHome extends EJBHome { public StrategyServer create() throws CreateException, RemoteException; } package ejbstrategy; import javax.ejb.EJBObject; import java.rmi.RemoteException; /** * 渡されたStrategyを実行するEJBのRemote Interface */ public interface StrategyServer extends EJBObject { public void setStrategy(EJBStrategy strategy) throws RemoteException; public Object handleRequired(Event event) throws Exception, RemoteException; public Object handleRequiresNew(Event event) throws Exception, RemoteException; public Object handleMandatory(Event event) throws Exception, RemoteException; public Object handleNotSupported(Event event) throws Exception, RemoteException; public Object handleSupports(Event event) throws Exception, RemoteException; public Object handleNever(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に処理を委譲する。トランザクション属性がRequiredで実行される。 * @param event イベント名とパラメータ * @exception Exception 例外 */ public Object handleRequired(Event event) throws Exception { return strategy.handleRequired(event); } /** * strategyに処理を委譲する。トランザクション属性がRequiresNewで実行される。 * @param event イベント名とパラメータ * @exception Exception 例外 */ public Object handleRequiresNew(Event event) throws Exception { return strategy.handleRequiresNew(event); } /** * strategyに処理を委譲する。トランザクション属性がMandatoryで実行される。 * @param event イベント名とパラメータ * @exception Exception 例外 */ public Object handleMandatory(Event event) throws Exception { return strategy.handleMandatory(event); } /** * strategyに処理を委譲する。トランザクション属性がNotSupportedで実行される。 * @param event イベント名とパラメータ * @exception Exception 例外 */ public Object handleNotSupported(Event event) throws Exception { return strategy.handleNotSupported(event); } /** * strategyに処理を委譲する。トランザクション属性がSupportsで実行される。 * @param event イベント名とパラメータ * @exception Exception 例外 */ public Object handleSupports(Event event) throws Exception { return strategy.handleSupports(event); } /** * strategyに処理を委譲する。トランザクション属性がNeverで実行される。 * @param event イベント名とパラメータ * @exception Exception 例外 */ public Object handleNever(Event event) throws Exception { return strategy.handleNever(event); } /** * strategyを設定する。 * @param strategy 戦略(ロジック) */ public void setStrategy(EJBStrategy strategy) { this.strategy = strategy; } public void setSessionContext(SessionContext context) { this.context = context; } public void ejbCreate() throws CreateException {} public void ejbRemove() {} public void ejbActivate() {} public void ejbPassivate() {} } package ejbstrategy; import java.io.Serializable; /** * StrategyServerのhandle〜メソッド実行時に渡すオブジェクト */ 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 handleRequired(Event event) throws Exception; public Object handleRequiresNew(Event event) throws Exception; public Object handleMandatory(Event event) throws Exception; public Object handleNotSupported(Event event) throws Exception; public Object handleSupports(Event event) throws Exception; public Object handleNever(Event event) throws Exception; } package ejbstrategy; /** * EJBStrategyの空実装クラス。 * このクラスを継承すれば実装したいhandle〜メソッドのみを * 実装することが出来る。 */ public abstract class EJBStrategyAdapter implements EJBStrategy { public Object handleRequired(Event event) throws Exception { return null; } public Object handleRequiresNew(Event event) throws Exception { return null; } public Object handleMandatory(Event event) throws Exception { return null; } public Object handleNotSupported(Event event) throws Exception { return null; } public Object handleSupports(Event event) throws Exception { return null; } public Object handleNever(Event event) throws Exception { return null; } } package ejbstrategy; import java.io.Serializable; import java.rmi.RemoteException; import javax.rmi.PortableRemoteObject; import javax.ejb.*; /** * StrategyServerEJBの呼び出しをカプセル化する。(Business Delegate) * lookup等のリモート呼び出しによる複雑さを隠蔽する。 */ public class StrategyServerDelegate implements Serializable { private transient StrategyServer bean; private Handle handle; public StrategyServerDelegate() { try { EJBHomeFactory factory = EJBHomeFactory.getInstance(); StrategyServerHome home = (StrategyServerHome) factory.getEJBHome("StrategyServer", StrategyServerHome.class); this.bean = home.create();
* DelegateがSerializeされた時に復元する為にEJB Handleを保持しておく。
* EJBデザインパターンのサンプルに載っていた方法なのですが、
* うまく動きませんでした。なのでコメントアウトしてあります。
//this.handle = this.bean.getHandle(); } catch (RemoteException ex) { throw new RuntimeException(ex); } catch (CreateException ex) { throw new RuntimeException(ex); } } public void remove() { try { getEJB().remove(); } catch (RemoteException ex) { throw new RuntimeException(ex); } catch (RemoveException ex) { throw new RuntimeException(ex); } } private StrategyServer getEJB() { try { if (this.bean == null) { this.bean = (StrategyServer) PortableRemoteObject.narrow( this.handle.getEJBObject(), StrategyServer.class); } } catch (RemoteException ex) { throw new RuntimeException(ex); } return this.bean; } public void setStrategy(EJBStrategy strategy) { try { getEJB().setStrategy(strategy); } catch (RemoteException ex) { throw new RuntimeException(ex); } } public Object handleRequired(Event event) throws Exception { return getEJB().handleRequired(event); } public Object handleRequiresNew(Event event) throws Exception { return getEJB().handleRequiresNew(event); } public Object handleMandatory(Event event) throws Exception { return getEJB().handleMandatory(event); } public Object handleNotSupported(Event event) throws Exception { return getEJB().handleNotSupported(event); } public Object handleSupports(Event event) throws Exception { return getEJB().handleSupports(event); } public Object handleNever(Event event) throws Exception { return getEJB().handleNever(event); } } package ejbstrategy; import java.util.*; import java.io.*; import javax.ejb.*; import javax.rmi.*; import javax.naming.*; public class EJBHomeFactory { /** EJBHomeFactoryのインスタンス */ private static EJBHomeFactory instance = new EJBHomeFactory(); /** コンテキスト */ private Context context; /** EJBHomeのリファレンスを保持 */ private Map homeMap = new HashMap(); /** EJB名 */ 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); } } /** * コンストラクタ: サービスロケータを返す。 * @return サービスロケータ */ public static EJBHomeFactory getInstance() { return instance; } /** * EJBHome取得: EJBHomeを取得する。homeオブジェクトがすでに存在する場合、それを返す。 * @param ejbName EJB名 * @param homeClass Homeクラス * @return EJBHome */ 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 ejbstrategy.*; /** * パターン説明の為のサンプルStrategy */ public class SampleStrategy extends EJBStrategyAdapter { public Object handleRequired(Event event) { //DBを更新する等の処理をする。サンプルなので適当な値を返すだけ Object result = (String) event.getData() + " executed update"; return result; } public Object handleSupports(Event event) { //DBを読み込む等の処理をする。サンプルなので適当な値を返すだけ Object result = (String) event.getData() + " executed load"; return result; } } 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 { StrategyServerDelegate delegate = new StrategyServerDelegate(); SampleStrategy strategy = new SampleStrategy(); delegate.setStrategy(strategy); Event event = new Event(); event.setData("sampledata1"); event.setName("doLoad"); result1 = delegate.handleSupports(event); event.setData("sampledata2"); event.setName("doUpdate"); result2 = delegate.handleRequired(event); } 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>"); } }
ejb-jar.xmlも以下のように変更しました。
[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>handleRequired</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>StrategyServer</ejb-name>
<method-name>handleRequiresNew</method-name>
</method>
<trans-attribute>RequiresNew</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>StrategyServer</ejb-name>
<method-name>handleMandatory</method-name>
</method>
<trans-attribute>Mandatory</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>StrategyServer</ejb-name>
<method-name>handleNotSupported</method-name>
</method>
<trans-attribute>NotSupported</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>StrategyServer</ejb-name>
<method-name>handleSupports</method-name>
</method>
<trans-attribute>Supports</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>StrategyServer</ejb-name>
<method-name>handleNever</method-name>
</method>
<trans-attribute>Never</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
トランザクション属性をhandle〜メソッドに〜と同じ属性を
設定してあります。
これにより、
「全トランザクションを利用出来るようになった」
「EJB呼び出しがDelegateクラスに隠蔽されて呼び出しが簡単になった」
とのですが、EJBを使う側からこんなご意見が
「メソッド名がhandle〜だと解かりにくいから、もっと具体的な処理名をつけてよ」
「引数をいちいちEventオブジェクトに格納して送るの面倒くさいよ」
とのことです。
確かにhandle〜(Event)、和訳すると「Eventを処理する」だと何の処理をする
のか解りにくいですなぁ。それに引数がEventオブジェクトにしなければ
ならないというのも使いにくいし、コンパイラの型チェックが利かない
しなー。
でも、一つのEJBを使いまわすんだから、インターフェースが汎用的で大雑把な
ものになるのは宿命なんじゃないかぁ?
「キラーン☆いいこと思いついたー!!」
EJBは使いまわすから、大雑把なインターフェースなのは仕方ないから、
使う側からは厳密なインターフェースにすればいいわけだな!
つまりはクライアントとの間にもう一つクラスを挿めばいいわけです!
ということで次回はこの問題を解決します。
んじゃ
やまろう