![]() ![]() ![]() ![]() シッククライアントを配備するための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アプリケーションサーバーアーキテクチャを利用して、エンドユーザーのためのシッククライアントを作成することを提案します。このチュートリアルでは、シッククライアントの株式取引アプリケーションを作成および配備しながら、概要を説明していきます。 必要な環境
インストール実際の開発作業に入る前に、次の手順を実行して、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; Springの取得Spring Frameworkをまだインストールしていない場合は、これをダウンロードします。今回は、執筆時点の最新の安定版リリースである1.2.8を選択しました。ダウンロードする場合は、必要となるいくつかのサードパーティライブラリが含まれている「Spring Framework with dependencies」のダウンロードをお勧めします。また、リソースとして、ソースとjavadocもダウンロードしてください。ダウンローが終了したら、ダウンロードファイルを解凍します。 準備がすべて完了しました。いよいよこれからが本番です。 Webアプリケーション/サーバーサイドプロジェクトの作成まず、EclipseでJavaプロジェクトを作成します。
図2 新しいWebプロジェクト ![]() 図3 新しいプロジェクト名 ![]() サードパーティライブラリの追加サードパーティライブラリをいくつか追加します。
これらをすべて、「WEB-INF/lib」ディレクトリにコピーします。
StockTraderServerへのSpringの追加Spring Frameworkをプロジェクトに組み込みます。そのためには、MyEclipseプラグインを使用するのが最も簡単です。
図5 Spring機能の追加 ![]() メモ
[MyEclipse Libraries]チェックボックスをオフにするのは、クラスパスに「spring.jar」が既に追加されているためです。あるいは、MyEclipseオプションを使用して「spring.jar」を追加することもできます。ただし、MyEclipseオプションを使用する方法の場合は、既定で不要なjarも数多く追加されるため、今回はこの方法を使用しませんでした。このような余分なjarを既定のMyEclipse設定から削除するには、[Window]-[Preferences]-[MyEclipse]-[Project Capabilities]-[Spring]をクリックします。
図6 Springの追加 ![]() 「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> 今回の例では、
<servlet-name>タグで囲まれたサーブレット名/テキストは「springDispatcher」であるため、WEB-INFディレクトリの下に「springDispatcher-servlet.xml」という名前の新しいファイルを追加することになります(図7を参照)。図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> StockTradeServerへのコードの追加このチュートリアルの目的上、StockTradeServerはきわめて単純です。株式に関する情報を要求するStockServiceと、取引を実行するOrderServiceという2つのサービスを作成し、公開します。 StockServiceStockServiceが基本です。株式情報のリストを取得および設定するメソッドを作成します。
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がインストールされていない場合は、パラメータ化型を除外します。
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; } } 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; } } <?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 シッククライアントから Spring Remotingを使用すると、Beanをサービスとして宣言し、多様なプロトコルを使って公開することができます。Spring Remotingが現在サポートしているのは、HTTP、Hessian、Burlap、RMI、またはJAX-RPC Webサービスです。それだけではなく、Spring Remotingでは、コードを修正せずにプロトコルを切り替えることができます。つまり、シッククライアントを構築してイントラネットに配備する場合に、RMIを使用できるのです。HTTPではなく、なぜRMIなのでしょうか。双方向通信であることに留意してください。RMIを使用すると、サーバーがクライアントに通知することができます。同様に、シックアプリケーションをネットワークの外部に配備してファイアウォールを経由させる場合も、同じサービスをHTTPで公開することができます(その場合も、コードの修正は必要ありません)。 それでは、Spring Remotingの利用方法を具体的に見ていきましょう。
<!-- 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を使用します。<!-- 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は受付を締め切っており、修正の予定はありません)。
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); } } <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. stockServiceRmi Beanの場合、
serviceUrlプロパティのコンテキストは、「springDispatcher-servlet.xml」で宣言したserviceName値と同じであることに注意してください。springDispatcher-servlet.xml serviceName="stockServiceRmi" applicationContext-test.xml serviceUrl="rmi://localhost:1099/stockServiceRmi" <beans> <bean id="ctx" class="org.springframework.context.support. Springは、SingletonBeanFactoryLocatorに、ApplicationContextへの参照の取得を委ねます。
MyEclipseに、これらの新しいアプリケーションコンテキストと、その間の依存性を理解させる必要もあります。このためには、パッケージエクスプローラで[stocktradeserver]ノードを右クリックし、[MyEclipse-Spring]に戻ります。[Add...]をクリックし、「beanRefFactory.xml」ファイルと「applicationContext-test.xml」ファイルを追加して、[OK]をクリックします。
[Config Sets]タブをクリックし、[New]をクリックします。名前として「stocktradeserver」を入力し、4つのアプリケーションコンテキストをすべてオンにします(図9を参照)。[Properties]ウィンドウが閉じるまで[OK]をクリックします。
図10 [Deploy MyEclipse J2EE Server]ボタン ![]() ドロップダウン内のプロジェクトが「stocktradeserver」であることを確認してください。[Add]をクリックし、サーバーとして[Tomcat 5]を選択し、[Finish]をクリックします。「Successfully deployed」というメッセージが表示されたら、[OK]をクリックします(図11を参照)。
図11 正しく配備されたStockTradeServer ![]() 図12 MyEclipseプラグインを通じてのTomcatの開始 ![]() http://localhost:8080/stocktradeserver/service/stockServiceHttpInvoker エラーが表示されますが、これは実際には良い兆候です。なぜでしょうか。Javaのバイナリの.classファイルのブラウザを通じてHTTP要求を行っていることを忘れないでください。レンダリングは絶対に不可能です。HttpInvokerServiceExporterが起動されたというメッセージも表示されますが、これは、StockServiceがHTTPで公開されていることを意味します。
JUnitテストが正しく実行されます(図14を参照)。 次のようなエラーが表示されることがあります。 java.rmi.ServerException: RemoteException occurred in server thread; この場合は、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の資格を持つ。
|