Web サービスクライアントの ServiceLocator

Sean Brydon

課題

多くのアプリケーションは、サービスロケータパターンを使用します。 Web サービスはアプリケーションでますます普及しつつあり、Web サービスをサポートするには、サービスロケータコードを更新する必要があります。また、現在、アプリケーションがサービスロケータパターンを適用していない場合も、このことを検討することを推奨します。

エンタープライズアプリケーションには、配布されたコンポーネントおよび Web サービスへのアクセスを可能にする参照をルックアップする手段が必要です。J2EE[tm] (Java[tm] 2 Platform, Enterprise Edition) アプリケーションは、JNDI (Java Naming and Directory Interface) を使用して、エンタープライズ Bean のホームインタフェースや JMX (JMS) コンポーネント、データソース、接続、接続ファクトリ、そして現在は Web サービスをルックアップします。繰り返しの多いルックアップコードは、コードを見づらく、かつ保守しにくくします。「サービスロケータ」パターンは、このコードを 1 つのクラスに一元化し、冗長なコードがアプリケーション中に散らばるのを防ぎます。

ここでの対処法は、次の状況で適用できます。

対処法

サービスロケータパターンは以前から存在し、多くの J2EE アプリケーションがサービスロケータクラスを使用し、J2EE リソースをルックアップしています。ルックアップ可能な Web サービスがあり、参照先のサービスが取得できる以上は、Web サービスをサポートするためのこのことを行う方法を検討する必要があります。

この対処法では、次のことを説明します。

ServiceLocator パターンの復習

ここでは、Java Adventure Builder の従来のサービスロケータに Web サービスのサポートを追加するを見てみましょう。以下のコードを見ると分かるように、EJB ホームや JMS 接続ファクトリ、あるいはデータソース (データベースなど) などのさまざまな種類の J2EE リソースに対する参照を取得するメソッドがあります。

public class ServiceLocator {

  private transient InitialContext ic;

  public ServiceLocator() throws ServiceLocatorException {
    try {
      ic = new InitialContext();
    } catch (Exception e) {
      throw new ServiceLocatorException(e);
    }
}

  /**
  * @return the EJB Home factory corresponding to the homeName
  */
  public EJBHome getRemoteHome(String jndiHomeName, Class className) throws ServiceLocatorException {
    try {
      Object objref = ic.lookup(jndiHomeName);
      return (EJBHome) PortableRemoteObject.narrow(objref, className);
    } catch (Exception e) {
      throw new ServiceLocatorException(e);
    }
  }

  /**
  * @return the factory for the factory to get JMS connections from
  */
  public ConnectionFactory getJMSConnectionFactory(String jmsConnFactoryName)
                                               throws ServiceLocatorException {
    try {
      return (ConnectionFactory) ic.lookup(jmsConnFactoryName);
    } catch (Exception e) {
    throw new ServiceLocatorException(e);
   }
  }

  /*
  * @return the DataSource corresponding to the name parameter
  */
  public DataSource getDataSource(String dataSourceName) throws ServiceLocatorException {
    try {
      return (DataSource)ic.lookup(dataSourceName);
    } catch (Exception e) {
    throw new ServiceLocatorException(e);
    }
  }
   ...//and other J2EE resources
}

コード例 1: Web サービスのサポートのない J2EE リソースのサービスロケータ

このサービスロケータパターンに関わっている主な要素をいくつか見てみます。

UML diagram of service locator

ビジネスオブジェクトまたはサービスを利用するクライアント側では、まずサービスをルックアップし、そのサービスに対する参照を取得する必要があります。サービスロケータパターンを利用するにあたっては、次の主要要素を含めます。

サービスロケータについて多少の理解ができたので、Web サービスのサポートを追加する方法を見てみます。

サービスロケータへの Web サービスサポートの追加

Web サービスをサポートするには、サービスロケータにどのようなメソッドを追加する必要があるのかを見てみましょう。

この例では、アプリケーションが配備されていて、サービスが実行されており、クライアントアプリケーションの側は、そのサービスにアクセスするサーブレットを持つ WAR ファイルであると仮定します。J2EE の場合、Web アプリケーション内の Web サービスアプリケーションにアクセスするサーブレットなどのコンポーネントには、サービスを呼び出すコードが必要になります。また、web.xml には、サービスに対する参照が含まれています (コード例 2 を参照)。

<service-ref>
    <description>Client of Some Service</description>
    <service-ref-name>service/
SomeService</service-ref-name>
    <service-interface>
        com.sun.j2ee.blueprints.myclientapplication.
SomeService
    </service-interface>
    <wsdl-file>WEB-INF/wsdl/SomeService.wsdl</wsdl-file>
    <jaxrpc-mapping-file>WEB-INF/someservice-mapping.xml</jaxrpc-mapping-file>
    <service-qname xmlns:servicens="urn:
SomeService">servicens:SomeService</service-qname>
</service-ref>

コード例 2: web.xml 配備記述子におけるサービス参照

クライアントアプリケーションが配備され、この service-ref で JNDI 名がバインドされている場合、クライアントは単にネーミングサービスにアクセスし、サービスにアクセスできるようにすればいいだけです。この例では、ネーミングディレクトリでサービス参照が "java:comp/env/service/SomeService" としてバインドされていると仮定しましょう。表 1 の左欄のコードは、サービスロケータクラスのないコードを示しています。表の右欄に示すように、サービスロケータを使用するように、このコードをリファクタリングする必要があります。

サービスロケータがないコード サービスロケータを使用するようにリファクタリング
String s= "java:comp/env/service/SomeService";

try {        
  Context ic = new InitialContext();
  Service myService =(Service)ic.lookup(s);
  SomeServiceSEI port =(SomeServiceSEI)

      myService
.getPort(SomeServiceSEI.class);

  // ここで単に Web サービスメソッドを呼び出す
  port.someMethod(someParameter);

} catch(Exception exe){
           ...
}
String s= "java:comp/env/service/SomeService";

try {        
  ServiceLocator sLoc = new ServiceLocator();
 
SomeServiceSEI port = (SomeServiceSEI)
   
sLoc.getServicePort(s,SomeServiceSEI.class);
 
  // ここで単に Web サービスメソッドを呼び出す
  port.someMethod(someParameter);

} catch(ServiceLocatorException sle){
   ...
} catch(Exception exe){
   ...
}

表 1: サービスロケータパターンを使用するためのリファクタリング

このように、わずかの変更で、Web サービス用にサービスロケータが更新されています。コードがわずかに変更されただけですが、このコードがアプリケーション内の多くの場所で繰り返されることがしばしばあるため、非常に有用です。時間の経過とともに、多くの場所でコードを繰り返していると、少し矛盾が生じて乱雑になり、保守しにくいものになります。このため、サービスロケータを利用することによって、いくぶんコードを整理できます。

また、Web サービスのルックアップをサポートするには、サービスロケータクラスも更新する必要があります。コード例 3 は、Web サービス参照のルックアップサポートを含むサービスロケータを示しています。

/**
 * Implements Service Locator pattern for Web services
 */
public class ServiceLocator {
    private transient InitialContext ic;
   
    public ServiceLocator() throws ServiceLocatorException  {
        try {
            ic = new InitialContext();
        } catch (Exception e) {
            throw new ServiceLocatorException(e);
        }
    }

    public Remote getServicePort(String jndiHomeName, Class className) throws ServiceLocatorException {
        try {
            Service service = (Service) ic.lookup(jndiHomeName);
            return service.getPort(className);
        } catch (Exception e) {
            throw new ServiceLocatorException(e);
        }
    }
}

コード例 3: J2EE コンポーネントが Web サービスクライアントとして機能するためのサポートを含む ServiceLocator

これで全部です。このパターンの利用は容易です。アプリケーションにクラスがもう 1 つ追加することになりますが、少しコードを整理できます。

対処法: キャッシュ方式のサービスロケータへの Web サービスサポートの追加

アプリケーションの中には、キャッシュ方式のサービスロケータがデザインされているものがあります。これは、不要な JNDI 初期コンテキストの作成およびサービスオブジェクトのルックアップを回避するためです。次のコード例 4 は、Web サービスのルックアップでこのキャッシュ方式を適用するサービスロケータの例です。

public class ServiceLocator {
   
    private InitialContext ic;
    // リソースへの参照の保持に使用される
    private Map cache = Collections.synchronizedMap(new HashMap());
   
    private static ServiceLocator instance = new ServiceLocator();
   
    public static ServiceLocator getInstance() {
        return instance;
    }
   
    private ServiceLocator() throws ServiceLocatorException {
        try {
            ic = new InitialContext();
        } catch (Exception e) {
            throw new ServiceLocatorException(e);
        }
    }

    public Remote getServicePort(String jndiHomeName, Class className) throws ServiceLocatorException {
        Remote servicePort = (Remote)
cache.get(jndiHomeName);
       
if (servicePort == null) {
          try {
            Service service = (Service) ic.lookup(jndiHomeName);
            servicePort = service.getPort(className);
           cache.put(jndiHomeName, servicePort);
          } catch (Exception e) {
            throw new ServiceLocatorException(e);
          }
        }
        return
servicePort;
    }
}

コード例 4: Web サービス参照に対するキャッシュ方式の ServiceLocator

これらのコード例から分かるように、Web サービスをルックアップするアプリケーションでは、ほかの J2EE リソースをルックアップするのとほぼ同じ方法でサービスロケータパターンを利用することができます。

参考資料

この項目に関する詳細は、次の資料を参照してください。


© Sun Microsystems 2005. All of the material in The Java BluePrints Solutions Catalog is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.