SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

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

SpringとRMIを使用してサービスを公開する

  • X ポスト
  • このエントリーをはてなブックマークに追加

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\t"
                    + 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\t"
                    + 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」を参照してください。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Stephen Lum(Stephen Lum)

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

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/474 2007/01/15 19:21

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング