デベロッパー2006年8月1日 12:30
文字サイズ文字サイズ小文字サイズ中文字サイズ大

シッククライアントを配備するためのJavaアプリケーションサーバー基盤の構築

この記事のURLhttp://japan.internet.com/developer/20060801/27.html
著者:Stephen Lum
海外internet.com発の記事

はじめに

 インターネットとその相棒のWebブラウザが世の中に与えた影響の大きさは、誰もが認めるところです。もし、読者の皆さんが私と同類ならば、Webブラウザを通じてニュースやスポーツの速報をチェックしたり、NASDAQの株価が下落した翌日に自分のポートフォリオの値をチェックしたりしていることでしょう。しかし、WebアプリケーションはHTTPに依存しているため、さまざまな制限があります。ご存知のように、Webアプリケーションの世界ではHTTP Webクライアントの弱点が広く認識されており、この弱点を補うために、"Web 2.0"アプリケーション、リッチインターネットアプリケーション(RIA)、AJAX、Lazlo、Flexなどの技術が考案されています。

 これらの技術はすべて、Webブラウザというシンクライアント(thin-client)をシッククライアント(thick-client)に見せようとするものです。しかし、私の考えでは、フォルクスワーゲンの色や形状を変えてポルシェのような外観にしても、実際にポルシェのように走ることは決してできません。私は、Webクライアントとデスクトップアプリケーションの戦いを始めるつもりは毛頭ありません。それぞれに目的があると考えています。しかし、Dojo、script.aculo.us、AJAX、Prototypeなどを使った開発作業に多くの時間を費やした結果、Web 2.0の機能の多くは、HTTPの制限的な要求/応答パラダイムを回避して、最終的には、本来の意図を超えた処理をHTTPに実行させようとする試みにすぎないと感じています。

 いつの日か、機能豊富なWeb 2.0アプリケーションのダウンロードサイズが、Java Web Startを通じて配備される充実したデスクトップアプリケーションのダウンロードサイズに迫るような状況になったとしても、私はまったく驚きません。そのことを念頭に置いた上で、今回は、従来のJ2EE/Webアプリケーションサーバーアーキテクチャを利用して、エンドユーザーのためのシッククライアントを作成することを提案します。このチュートリアルでは、シッククライアントの株式取引アプリケーションを作成および配備しながら、概要を説明していきます。

必要な環境

  • Eclipse 3.1.2
  • J2EE準拠のWebサーバー(Tomcat、Resin、Jettyなど)
  • MyEclipse 4.1.1
  • Java 5(およびその実用的な知識)
  • サーブレットコンテナまたはJ2EEサーバー(このチュートリアルではTomcat 5.5+を使用)
  • Spring 1.2+(およびその実用的な知識)

インストール

 実際の開発作業に入る前に、次の手順を実行して、JREではなくJDKを使用していることを確認します。

  1. [Window]-[Preferences]をクリックし、[Java]ノードを展開して、[Installed JRE]をクリックします。
  2. [Add]をクリックし、[Browse]をクリックして[JRE Home Directory]を参照します。JAVA_HOMEディレクトリに移動し、[OK]をクリックします。
  3. [JRE Name]に「JDK」と入力し、[OK]をクリックします。[JDK]の横にチェックマークがあることを確認し、[OK]をクリックして(図1を参照)、[Windows]-[Preferences]を終了します。
  4. 図1 JREではなくJDKを使用していることを確認する
    図1 JREではなくJDKを使用していることを確認する

Tomcatのインストール

 このチュートリアルでは、本稿執筆時点での最新リリースであるTomcat 5.5.17を使用します。Tomcat内でRMIも使用するため、Tomcatのインストール先は、空白を含まないパスにする必要があります。つまり、「C:Program FilesApache Software Foundation」などは不適切です。空白を含まないディレクトリにTomcatをインストールしない限り、Tomcat上でRMI Serverを動かすことはできません(実際のASF Bugzillaバグについては、こちらを参照してください。Apacheは受付を締め切っており、修正の予定はありません)。今回の例では、「C:devtoolsjavaapache-tomcat-5.5.17」にTomcatをインストールしました。

 このチュートリアルのいずれかの部分を実行して、次のような例外が発生する場合は、RMI部分を削除する(または起動しない)か、Tomcatのインストール先を、空白を含まないディレクトリに変更します。

java.rmi.ServerException: RemoteException occurred in server thread;
 nested exception is:
            java.rmi.UnmarshalException: error unmarshalling
 arguments; nested exception is:
            java.net.MalformedURLException: no protocol: Files/Apache
... ... ...
Caused by: java.net.MalformedURLException: no protocol: Files/Apache
            at java.net.URL.(URL.java:567)
... ... ...

Springの取得

 Spring Frameworkをまだインストールしていない場合は、これをダウンロードします。今回は、執筆時点の最新の安定版リリースである1.2.8を選択しました。ダウンロードする場合は、必要となるいくつかのサードパーティライブラリが含まれている「Spring Framework with dependencies」のダウンロードをお勧めします。また、リソースとして、ソースとjavadocもダウンロードしてください。ダウンローが終了したら、ダウンロードファイルを解凍します。

 準備がすべて完了しました。いよいよこれからが本番です。

Webアプリケーション/サーバーサイドプロジェクトの作成

 まず、EclipseでJavaプロジェクトを作成します。

  1. [File]-[New]-[Project]をクリックします。Javaプロジェクトを選択し、[Next]をクリックします。
  2. New Projectウィザードが表示されます。[MyEclipse]-[J2EE Projects]の[Web Project]をクリックし(図2を参照)、[Next]をクリックします。
  3. 図2 新しいWebプロジェクト
    図2 新しいWebプロジェクト
  4. プロジェクト名として「stocktraderserver」と入力します(図3を参照)。今回は、場所として、「C:devDevxEclipseRcpSpringRemotingStockTraderServer」を選択しました。[Finish]をクリックします。
  5. 図3 新しいプロジェクト名
    図3 新しいプロジェクト名

サードパーティライブラリの追加

 サードパーティライブラリをいくつか追加します。

  1. 解凍されたspring-framework-1.2.8ディストリビューションには、次のjarが含まれています。
    • Table 1 …… クラスパスに追加するサードパーティの外部jar
    • spring.jar …… 「dist」フォルダにあるjar
    • commons-logging.jar …… 「libjakarta-commons」フォルダにあるjar
    • log4j-1.2.13.jar …… 「liblog4j」フォルダにあるjar
    これらをすべて、「WEB-INF/lib」ディレクトリにコピーします。
  2. Eclipseのパッケージエクスプローラで、[StockTradeServer]プロジェクトを右クリックします。
  3. プロパティを選択し、[Java Build Path]をクリックします。
  4. [Libraries]タブをクリックします。
  5. [Add JARs...]をクリックし、コピーしたjarを追加します。また、[Add External JARS...]をクリックし、spring dependencies 1.2.8を解凍してできたフォルダを参照して、「lib/junit」ディレクトリにある「junit.jar」を追加します(図4を参照)。
  6. 図4 Javaビルドパス
    図4 Javaビルドパス

StockTraderServerへのSpringの追加

 Spring Frameworkをプロジェクトに組み込みます。そのためには、MyEclipseプラグインを使用するのが最も簡単です。

  1. Eclipseメニューの[MyEclipse]-[Add Spring Capabilities...]をクリックします。
  2. ウィザードが表示されたら、[MyEclipse Libraries]チェックボックスと[Spring core]チェックボックスを両方ともオフにして、[Next]をクリックします(図5を参照)。
  3. 図5 Spring機能の追加
    図5 Spring機能の追加
メモ
 [MyEclipse Libraries]チェックボックスをオフにするのは、クラスパスに「spring.jar」が既に追加されているためです。あるいは、MyEclipseオプションを使用して「spring.jar」を追加することもできます。ただし、MyEclipseオプションを使用する方法の場合は、既定で不要なjarも数多く追加されるため、今回はこの方法を使用しませんでした。このような余分なjarを既定のMyEclipse設定から削除するには、[Window]-[Preferences]-[MyEclipse]-[Project Capabilities]-[Spring]をクリックします。
  1. 新しいSpring Bean設定ファイルを作成し、[Browse]ボタンを使用して、WEB-INFディレクトリに「applicationContext.xml」を作成します。終了したら、[Finish]をクリックします。
  2. パッケージエクスプローラに「S」の記号が表示され、Springが追加されたことが示されます(図6を参照)。
  3. 図6 Springの追加
    図6 Springの追加
  4. 残念ながら、SpringのDispatcherServletはWebプロジェクトに追加されていません。そこで、次の操作を実行します。
  5. 「web.xml」を開きます。このファイルは、パッケージエクスプローラで[WebRoot]-[WEB-INF]を展開すると見つかります。「web.xml」に次の内容を追加します。
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.4"
        xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener
            </listener-class>
        </listener>
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/applicationContext.xml</param-value>
        </context-param>
    
        <servlet>
            <servlet-name>springDispatcher</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet
            </servlet-class>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>springDispatcher</servlet-name>
            <url-pattern>/service/*</url-pattern>
        </servlet-mapping>
    
    </web-app>
    
  6. 次に、SpringのWebApplicationContextを作成します。Springでは、そのためのルールが明確に定義されており、WEB-INFディレクトリに新しいxmlファイルを作成する必要があります。この新しいファイルの名前は、「web.xml」内の<servlet-name>タグで囲まれたテキストに「servlet.xml」を付加したものにしなければなりません。
  7. 今回の例では、<servlet-name>タグで囲まれたサーブレット名/テキストは「springDispatcher」であるため、WEB-INFディレクトリの下に「springDispatcher-servlet.xml」という名前の新しいファイルを追加することになります(図7を参照)。
    図7 プロジェクトへの「springDispatcher-servlet.xml」の追加
    図7 プロジェクトへの「springDispatcher-servlet.xml」の追加
    次に「springDispatcher-servlet.xml」を開き、以下を入力します。これは、SpringApplicationContextの基本のXMLテンプレートです。
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
     "http://www.springframework.org/dtd/spring-beans.dtd">
    
    <beans>
    
    </beans>
    
  8. 「springDispatcher-servlet.xml」をMyEclipseプラグインに追加します。パッケージエクスプローラで、[stocktradeserver]プロジェクトノードを右クリックします。左側で、[MyEclipse-Spring]を選択します。[Add...]をクリックし、「springDispatcher-servlet.xml」にナビゲートして選択し、[OK]をクリックします。再び[OK]をクリックして[Properties]ウィンドウを閉じます(図8を参照)。
  9. 図8 MyEclipse-Springへの「springDispatcher-servlet.xml」の追加
    図8 MyEclipse-Springへの「springDispatcher-servlet.xml」の追加

StockTradeServerへのコードの追加

 このチュートリアルの目的上、StockTradeServerはきわめて単純です。株式に関する情報を要求するStockServiceと、取引を実行するOrderServiceという2つのサービスを作成し、公開します。

StockService

 StockServiceが基本です。株式情報のリストを取得および設定するメソッドを作成します。

  1. 「stephenlum.services.stock」内に、株式情報を取得および設定する2つのメソッドを伴うインターフェイスStockServiceを作成します。
  2. package stephenlum.services.stock;
    
    import java.util.List;
    import java.util.Map;
    
    import stephenlum.services.stock.dto.StockDTO;
    
    public interface StockService {
        public List<StockDTO> getStocks(List<String> tickerList);
    
        public void setStocks(Map<String, StockDTO> mapOfStocks);
    }
    
メモ
 パラメータ化型(parameterized type)は5.0でのみ有効、という内容のエラーが表示される場合は、コンパイラレベルを5.0に変更する必要があります。これは、[Window]-[Preferences]-[Compiler]で行います。Java 5.0がインストールされていない場合は、パラメータ化型を除外します。
  1. パッケージ「stephenlum.services.stock.dto」内にデータ転送オブジェクトStockDTOを作成します。StockDTOは回線を通じて提供されるため、Serializableを実装する必要があります。以下のリストは、今回の例でStockDTO用に作成した多くのプロパティを示しています。
  2. package stephenlum.services.stock.dto;
    
    import java.io.Serializable;
    
    /**
     * User: Stephen Lum
     */
    public class StockDTO implements Serializable {
        private String tickerSymbol;
        private Double avgVol;
        private Double change;
        private String daysRange;
        private String fiftyTwoWeekRange;
        private Double lastTrade;
        private String marketCap;
        private Double volume;
    
        public StockDTO(String tickerSymbol, Double avgVol, Double change,
            String daysRange, String fiftyTwoWeekRange, Double lastTrade,
            String marketCap, Double volume) {
            this.tickerSymbol = tickerSymbol;
            this.avgVol = avgVol;
            this.change = change;
            this.daysRange = daysRange;
            this.fiftyTwoWeekRange = fiftyTwoWeekRange;
            this.lastTrade = lastTrade;
            this.marketCap = marketCap;
            this.volume = volume;
        }
    
        public Double getAvgVol() {
            return avgVol;
        }
    
        public Double getChange() {
            return change;
        }
    
        public String getDaysRange() {
            return daysRange;
        }
    
        public String getFiftyTwoWeekRange() {
            return fiftyTwoWeekRange;
        }
    
        public Double getLastTrade() {
            return lastTrade;
        }
    
        public String getMarketCap() {
            return marketCap;
        }
    
        public String getTickerSymbol() {
            return tickerSymbol;
        }
    
        public Double getVolume() {
            return volume;
        }
    }
    
  3. パッケージ「stephenlum.services.stock」内にクラスStockServiceImplを作成します。このクラスではインターフェイスStockServiceを実装します。クラスを作成したら、インターフェイスメソッドを実装します。
  4. package stephenlum.services.stock;
    
    import java.util.List;
    import java.util.Map;
    import java.util.ArrayList;
    import java.util.Iterator;
    
    import stephenlum.services.stock.dto.StockDTO;
    
    public class StockServiceImpl implements StockService {
        private Map mapOfStocks;
    
        public List<StockDTO> getStocks(List<String> tickerList) {
                    List<StockDTO> resultList = new ArrayList<StockDTO>();
    
            for (Iterator it = tickerList.listIterator(); it.hasNext();) {
                resultList.add((StockDTO)mapOfStocks.get(it.next()));
            }
    
            return resultList;
        }
    
        public void setStocks(Map<String, StockDTO> mapOfStocks) {
            this.mapOfStocks = mapOfStocks;
        }
    }
    
  5. 「applicationContext.xml」内でSpring Beansを宣言します。このファイル内で、StockService用のBeanと、StockDTOのインターフェイスを宣言します。StockDTOについては、Microsoft、Sun、Oracle、およびIBMという4つの会社のインスタンスを作成します。
  6. <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
        "http://www.springframework.org/dtd/spring-beans.dtd">
    
    <beans>
        <bean id="stockService"
              class="stephenlum.services.stock.StockServiceImpl">
            <property name="stocks">
                <map>
                    <entry key="msft" value-ref="msft"></entry>
                    <entry key="sunw" value-ref="sunw"></entry>
                    <entry key="orcl" value-ref="orcl"></entry>
                    <entry key="ibm" value-ref="ibm"></entry>
                </map>
            </property>
        </bean>
    
        <bean id="msft" class="stephenlum.services.stock.dto.StockDTO">
            <constructor-arg index="0"><!--ticker-->
                <value>MSFT</value>
            </constructor-arg>
            <constructor-arg index="1"><!--avgVol-->
                <value>75692500</value>
            </constructor-arg>
            <constructor-arg index="2"><!--change-->
                <value>-.16</value>
            </constructor-arg>
            <constructor-arg index="3"><!--daysRange-->
                <value>22.91 - 24.00</value>
            </constructor-arg>
            <constructor-arg index="4"><!--fiftyTwoWeekRange-->
                <value>23.03 - 28.38</value>
            </constructor-arg>
            <constructor-arg index="5"><!--lastTrade-->
                <value>22.99</value>
            </constructor-arg>
            <constructor-arg index="6"><!--marketCap-->
                <value>234.53B</value>
            </constructor-arg>
            <constructor-arg index="7"><!--volume-->
                <value>75692500</value>
            </constructor-arg>
        </bean>
    
        <bean id="sunw" class="stephenlum.services.stock.dto.StockDTO">
            <constructor-arg index="0"><!--ticker-->
                <value>SUNW</value>
            </constructor-arg>
            <constructor-arg index="1"><!--avgVol-->
                <value>63942900</value>
            </constructor-arg>
            <constructor-arg index="2"><!--change-->
                <value>-.02</value>
            </constructor-arg>
            <constructor-arg index="3"><!--daysRange-->
                <value>4.56 - 4.70</value>
            </constructor-arg>
            <constructor-arg index="4"><!--fiftyTwoWeekRange-->
                <value>3.56 - 5.40</value>
            </constructor-arg>
            <constructor-arg index="5"><!--lastTrade-->
                <value>4.59</value>
            </constructor-arg>
            <constructor-arg index="6"><!--marketCap-->
                <value>16.05b</value>
            </constructor-arg>
            <constructor-arg index="7"><!--volume-->
                <value>66703285</value>
            </constructor-arg>
        </bean>
    
        <bean id="orcl" class="stephenlum.services.stock.dto.StockDTO">
            <constructor-arg index="0"><!--ticker-->
                <value>ORCL</value>
            </constructor-arg>
            <constructor-arg index="1"><!--avgVol-->
                <value>47443800</value>
            </constructor-arg>
            <constructor-arg index="2"><!--change-->
                <value>-.07</value>
            </constructor-arg>
            <constructor-arg index="3"><!--daysRange-->
                <value>13.56 - 13.93</value>
            </constructor-arg>
            <constructor-arg index="4"><!--fiftyTwoWeekRange-->
                <value>11.75 - 15.21</value>
            </constructor-arg>
            <constructor-arg index="5"><!--lastTrade-->
                <value>13.70</value>
            </constructor-arg>
            <constructor-arg index="6"><!--marketCap-->
                <value>73.08B</value>
            </constructor-arg>
            <constructor-arg index="7"><!--volume-->
                <value>46390248</value>
            </constructor-arg>
        </bean>
    
        <bean id="ibm" class="stephenlum.services.stock.dto.StockDTO">
            <constructor-arg index="0"><!--ticker-->
                <value>IBM</value>
            </constructor-arg>
            <constructor-arg index="1"><!--avgVol-->
                <value>5250390</value>
            </constructor-arg>
            <constructor-arg index="2"><!--change-->
                <value>-.38</value>
            </constructor-arg>
            <constructor-arg index="3"><!--daysRange-->
                <value>79.51 - 81.00</value>
            </constructor-arg>
            <constructor-arg index="4"><!--fiftyTwoWeekRange-->
                <value>73.45 - 89.94</value>
            </constructor-arg>
            <constructor-arg index="5"><!--lastTrade-->
                <value>80.28</value>
            </constructor-arg>
            <constructor-arg index="6"><!--marketCap-->
                <value>124.47B</value>
            </constructor-arg>
            <constructor-arg index="7"><!--volume-->
                <value>7019100</value>
            </constructor-arg>
        </bean>
    
    </beans>
    

Spring Remoting

 シッククライアントからStockServiceを呼び出せるようにするために、StockService Beanを公開しなければなりません。それにはどうすればよいのでしょうか。まず考えられるのは、シッククライアント+アプリケーションサーバーのアーキテクチャを採用して、Spring Controllerを効率的に宣言し、このControllerにXMLファイルを返させるという方法です(サーブレットの応答OutputStreamに前述のXMLファイルを割り当てると考えてください)。また、StockServiceをWebサービスとして公開するという方法もあります。どちらも間違いではありませんが、もっとよい方法があります。それがSpring Remotingです。

 Spring Remotingを使用すると、Beanをサービスとして宣言し、多様なプロトコルを使って公開することができます。Spring Remotingが現在サポートしているのは、HTTP、Hessian、Burlap、RMI、またはJAX-RPC Webサービスです。それだけではなく、Spring Remotingでは、コードを修正せずにプロトコルを切り替えることができます。つまり、シッククライアントを構築してイントラネットに配備する場合に、RMIを使用できるのです。HTTPではなく、なぜRMIなのでしょうか。双方向通信であることに留意してください。RMIを使用すると、サーバーがクライアントに通知することができます。同様に、シックアプリケーションをネットワークの外部に配備してファイアウォールを経由させる場合も、同じサービスをHTTPで公開することができます(その場合も、コードの修正は必要ありません)。

 それでは、Spring Remotingの利用方法を具体的に見ていきましょう。

  1. HTTPでStockServiceを公開するには、WebApplicationContext内にSpring Beanを宣言する必要があります。このためには、「springDispatcher-servlet.xml」に以下を追加します。
  2. <!-- httpInvoker exporter for the StockService -->
    <bean name="/stockServiceHttpInvoker"
     class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"
     lazy-init="false">
        <property name="service"><ref bean="stockService"/></property>
        <property name="serviceInterface">
            <value>stephenlum.services.stock.StockService</value>
        </property>
    </bean>
    
    このエントリによって、SpringのHttpInvokerServiceExporterを使用してStockServiceがHTTPで公開されます。SpringのHttpInvokerServiceExporterを使用する場合は、serviceプロパティとserviceInterfaceプロパティも定義する必要があります。また、個々のURLMappingHandler実装は宣言していないことに注意してください。そこで、Webアプリケーションは、Springの既定のBeanNameUrlMappingHandlerを使用します。
  3. このサービスをRMIでも公開します。「applicationContext.xml」に次のエントリを追加します。
  4. <!-- rmi exporter for the StockService -->
    <bean name="/stockServiceRmi"
          class="org.springframework.remoting.rmi.RmiServiceExporter"
          lazy-init="false">
        <property name="service"><ref bean="stockService"/></property>
        <property name="serviceName"><value>stockServiceRmi</value></property>
        <property name="serviceInterface">
            <value>stephenlum.services.stock.StockService</value>
        </property>
        <property name="registryPort" value="1099"/>
    </bean>
    
    2番目のエントリは、同じStockServiceインスタンスをRMIで公開します。目的はそれだけであり、宣言するSpringクラスがRmiServiceExporterであることが唯一の相違です。serviceNameプロパティを宣言する必要があり、registryPortはRMIの既定のポートである1099に宣言します。
メモ
 Tomcat上でRMIを実行するには、Tomcatのインストール先を、空白を含まないパスにする必要があります(実際のASF Bugzillaバグについては、こちらを参照してください。Apacheは受付を締め切っており、修正の予定はありません)。
  1. ここで簡単なテストを作成します。[stocktradeserver]ノードの下に、「test」という名前の新しいソースフォルダを作成します。この新しいフォルダは、[src]と同じレベルにあることが必要です。
  2. TestCaseを拡張して、StockServiceImplTestという名前の新しいクラスを作成します。メソッドgetStocks(List<String> tickerList)をテストするためのtestGetStocks()というテストメソッドを作成します。これは例外をスローします。このメソッドでは、HTTPとRMIの両方の公開をテストします。
  3. StockServiceImplTestクラス
    package stephenlum.services.stock;
    
    import junit.framework.Test;
    import junit.framework.TestSuite;
    import junit.framework.TestCase;
    import org.springframework.beans.factory.access.BeanFactoryLocator;
    import org.springframework.beans.factory.access.SingletonBeanFactoryLocator;
    import org.springframework.beans.factory.access.BeanFactoryReference;
    
    import java.util.List;
    import java.util.ArrayList;
    import java.util.Iterator;
    
    import stephenlum.services.stock.dto.StockDTO;
    
    /**
     * StockServiceImpl Tester.
     *
     * @author Stephen Lum
    * @version 1.0
     */
    public class StockServiceImplTest extends TestCase {
        public StockServiceImplTest(String name) {
            super(name);
        }
    
        public void setUp() throws Exception {
            super.setUp();
        }
    
        public void tearDown() throws Exception {
            super.tearDown();
        }
    
        public void testGetStocks() throws Exception {
            BeanFactoryLocator beanFactoryLocator =
                SingletonBeanFactoryLocator.getInstance();
            BeanFactoryReference beanFactoryReference =
                beanFactoryLocator.useBeanFactory("ctx");
            StockService stockServiceHttp =
                (StockService)beanFactoryReference.getFactory()
                .getBean("stockServiceHttpInvoker");
            StockService stockServiceRmi =
                (StockService)beanFactoryReference.getFactory()
                .getBean("stockServiceRmi");
    
            List tickerList = new ArrayList<String>();
            tickerList.add("msft");
            List stockList = stockServiceHttp.getStocks(tickerList);
            assertNotNull(stockList);
    
            for (Iterator it = stockList.listIterator(); it.hasNext();) {
                StockDTO stockDTO = (StockDTO)it.next();
                assertNotNull(stockDTO.getTickerSymbol());
                System.out.println("stockDTO" + stockDTO.getTickerSymbol());
            }
    
            tickerList.clear();
            tickerList.add("sunw");
            stockList = stockServiceRmi.getStocks(tickerList);
            assertNotNull(stockList);
    
            for (Iterator it = stockList.listIterator(); it.hasNext();) {
                StockDTO stockDTO = (StockDTO)it.next();
                assertNotNull(stockDTO.getTickerSymbol());
                System.out.println("stockDTO" + stockDTO.getTickerSymbol());
            }
    
        }
    
        public static Test suite() {
            return new TestSuite(StockServiceImplTest.class);
        }
    }
    
    
  4. テストケース用のApplicationContextを作成します。「test」ディレクトリの下にファイル「applicationContext-test.xml」を作成し、以下を追加します。
  5. <beans>
        <bean id="stockServiceRmi"
              class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
            <property name="serviceUrl">
                <value>rmi://localhost:1099/stockServiceRmi</value>
            </property>
            <property name="serviceInterface">
                <value>stephenlum.services.stock.StockService</value>
            </property>
            <property name="cacheStub" value="true"/>
            <property name="lookupStubOnStartup" value="true"/>
            <property name="refreshStubOnConnectFailure" value="true"/>
        </bean>
    
        <bean id="stockServiceHttpInvoker"
              class="org.springframework.remoting.httpinvoker.
    HttpInvokerProxyFactoryBean">
            <property name="serviceUrl">
                <value>
    http://localhost:8080/stocktradeserver/service/stockServiceHttpInvoker
                </value>
            </property>
            <property name="serviceInterface">
                <value>stephenlum.services.stock.StockService</value>
            </property>
        </bean>
    </beans>
    
    stockServiceRmi Beanの場合、serviceUrlプロパティのコンテキストは、「springDispatcher-servlet.xml」で宣言したserviceName値と同じであることに注意してください。
    springDispatcher-servlet.xml serviceName="stockServiceRmi"
    applicationContext-test.xml serviceUrl="rmi://localhost:1099/stockServiceRmi"
    
  6. テストの準備はほぼ完了しました。SpringのSingletonBeanFactoryLocatorを使用するため、最後に「beanRefFactory.xml」を作成します。「test」ディレクトリの下にファイル「beanRefFactory.xml」を作成し、以下を追加します。
  7. <beans>
        <bean id="ctx"
              class="org.springframework.context.support.
    ClassPathXmlApplicationContext">
            <constructor-arg>
                <list>
                    <value>applicationContext-test.xml</value>
                </list>
            </constructor-arg>
        </bean>
    </beans>
    
    Springは、SingletonBeanFactoryLocatorに、ApplicationContextへの参照の取得を委ねます。
    MyEclipseに、これらの新しいアプリケーションコンテキストと、その間の依存性を理解させる必要もあります。このためには、パッケージエクスプローラで[stocktradeserver]ノードを右クリックし、[MyEclipse-Spring]に戻ります。[Add...]をクリックし、「beanRefFactory.xml」ファイルと「applicationContext-test.xml」ファイルを追加して、[OK]をクリックします。
    [Config Sets]タブをクリックし、[New]をクリックします。名前として「stocktradeserver」を入力し、4つのアプリケーションコンテキストをすべてオンにします(図9を参照)。[Properties]ウィンドウが閉じるまで[OK]をクリックします。
    図9 StockTradeServerのMyEclipse設定セット
    図9 StockTradeServerのMyEclipse設定セット
  8. テストを実行します。Eclipseで、ツールバーボタンの[Deploy MyEclipse J2EE project to Server]をクリックします(図10を参照)。
  9. 図10 [Deploy MyEclipse J2EE Server]ボタン
    図10 [Deploy MyEclipse J2EE Server]ボタン
    ドロップダウン内のプロジェクトが「stocktradeserver」であることを確認してください。[Add]をクリックし、サーバーとして[Tomcat 5]を選択し、[Finish]をクリックします。「Successfully deployed」というメッセージが表示されたら、[OK]をクリックします(図11を参照)。
    図11 正しく配備されたStockTradeServer
    図11 正しく配備されたStockTradeServer
  10. Tomcatサーバーを開始します(図12を参照)。Tomcatが正常に開始するはずです。
  11. 図12 MyEclipseプラグインを通じてのTomcatの開始
    図12 MyEclipseプラグインを通じてのTomcatの開始
  12. 最初のテストはブラウザを通じて行います。Webブラウザを開き、次のURLを入力します。
  13. http://localhost:8080/stocktradeserver/service/stockServiceHttpInvoker
    
    エラーが表示されますが、これは実際には良い兆候です。なぜでしょうか。Javaのバイナリの.classファイルのブラウザを通じてHTTP要求を行っていることを忘れないでください。レンダリングは絶対に不可能です。HttpInvokerServiceExporterが起動されたというメッセージも表示されますが、これは、StockServiceがHTTPで公開されていることを意味します。
  14. JUnitテストを実行します。パッケージエクスプローラで、「test」ディレクトリの下にあるStockServiceImplTestクラスにナビゲートします。クラスが確認できたら、それを右クリックし、[Run As]-[JUnit Test]を選択します(図13を参照)。
  15. 図13 EclipseからJUnitテストを実行する
    図13 EclipseからJUnitテストを実行する

 JUnitテストが正しく実行されます(図14を参照)。

図14 StockServiceImplTestが正しく実行される
図14 StockServiceImplTestが正しく実行される

 次のようなエラーが表示されることがあります。

java.rmi.ServerException: RemoteException occurred in server thread;
 nested exception is:
            java.rmi.UnmarshalException: error unmarshalling
 arguments; nested exception is:
            java.net.MalformedURLException: no protocol: Files/Apache
... ... ...
Caused by: java.net.MalformedURLException: no protocol: Files/Apache
            at java.net.URL.(URL.java:567)
... ... ...

 この場合は、RMIの部分を削除する(または起動しない)か、Tomcatのインストール先を、空白を含まないディレクトリに変更してください。

シッククライアントとアプリケーションサーバーとの通信

 このチュートリアルでは、SpringとRMIを使用してサービスを公開する簡単な方法を紹介しました。Spring Remotingのおかげで、シッククライアントとアプリケーションサーバーの通信が、これまでより簡単になりました。コードを修正せずにプロトコルを変更できるSpringの柔軟性は、本当に優れています。私の知る限り、手動による方法はもちろん、他のどんなソリューションよりも優れています。このプロトコル交換は、HTTPやWebクライアントでは実現できないようなイベントをプッシュしたい場合に、特に便利です。

次のステップ
 実際に動作するシッククライアント+アプリケーションサーバーのアーキテクチャを構築できたところで、次は、Eclipse Rich Client Platformを利用して実際のシッククライアントを構築する方法を学習しましょう。詳しくは本稿の続編となる「Eclipse RCP Meets Spring: A Perfect Thick-Client Match」を参照してください。

著者紹介

Stephen Lum(Stephen Lum)
ロンドンの投資銀行の上級開発者。Javaのプログラミング経験は7年に及ぶ。SunのJavaプログラマ認定資格とOracleの開発者認定資格のほか、CPAの資格を持つ。

Copyright 2009 Japan Internet.com K.K. All Rights Reserved.http://www.internet.com/