Apache CXFの紹介

Apache CXFの概要

1. 概要

Apache CXFは、JAX-WSに完全に準拠したフレームワークです。

JAX-WS標準で定義された機能に加えて、Apache CXFは、WSDLとJavaクラス間の変換機能、生のXMLメッセージの操作に使用されるAPI、JAX-RSのサポート、Spring Frameworkとの統合などを提供します。

このチュートリアルはApache CXFのシリーズの最初であり、フレームワークの基本的な特徴を紹介しています。 ソースコードでJAX-WS標準APIのみを使用し、自動生成されたWSDLメタデータやCXFのデフォルト構成など、Apache CXFを舞台裏で活用します。

2. Mavenの依存関係

Apache CXFを使用するために必要な主要な依存関係はorg.apache.cxf:[.s1]cxfrtfrontendjaxws. This provides a JAX-WS implementation to replace the built-in JDK one:です。


    org.apache.cxf
    cxf-rt-frontend-jaxws
    3.1.6

Notice that this artifact contains a file named javax.xml.ws.spi.Provider inside the META-INF/services directory. Java VM looks at the first line of this file to determine the JAX-WS implementation to make use of. In this case, content of the line is org.apache.cxf.jaxws.spi.ProviderImpl、ApacheCXFによって提供される実装を指します。

このチュートリアルでは、サーブレットコンテナを使用してサービスを公開しないため、必要なJava型定義を提供するには別の依存関係が必要です。


    org.apache.cxf
    cxf-rt-transports-http-jetty
    3.1.6

これらの依存関係の最新バージョンについては、Maven中央リポジトリのcxf-rt-frontend-jaxwscxf-rt-transports-http-jettyを確認してください。

3. Webサービスエンドポイント

サービスエンドポイントの構成に使用される実装クラスから始めましょう。

@WebService(endpointInterface = "com.example.cxf.introduction.example")
public class exampleImpl implements example {
    private Map students
      = new LinkedHashMap();

    public String hello(String name) {
        return "Hello " + name;
    }

    public String helloStudent(Student student) {
        students.put(students.size() + 1, student);
        return "Hello " + student.getName();
    }

    public Map getStudents() {
        return students;
    }
}

ここで注意すべき最も重要なことは、@WebServiceアノテーションにendpointInterface属性が存在することです。 この属性は、Webサービスの抽象コントラクトを定義するインターフェイスを指します。

エンドポイントインターフェイスで宣言されたすべてのメソッドシグネチャを実装する必要がありますが、インターフェイスを実装する必要はありません。

ここで、exampleImpl実装クラスは、次のエンドポイントインターフェイスを実装して、インターフェイスの宣言されたすべてのメソッドが実装されていることを明確にしますが、これはオプションです。

@WebService
public interface example {
    public String hello(String name);

    public String helloStudent(Student student);

    @XmlJavaTypeAdapter(StudentMapAdapter.class)
    public Map getStudents();
}

デフォルトでは、Apache CXFはデータバインディングアーキテクチャとしてJAXBを使用します。 ただし、JAXBはgetStudentsメソッドwe need an adapter to convert the Map to a Java class that JAXB can useから返されるMapのバインディングを直接サポートしていないためです。

さらに、コントラクト要素を実装から分離するために、Studentをインターフェースとして定義し、JAXBもインターフェースを直接サポートしないため、これに対処するためにもう1つのアダプターが必要です。 実際、便宜上、Studentをクラスとして宣言する場合があります。 インターフェイスとしてこのタイプを使用することは、適応クラスの使用のもう1つのデモンストレーションです。

アダプターについては、以下のセクションで説明します。

4. カスタムアダプタ

このセクションでは、適応クラスを使用して、JAXBを使用したJavaインターフェースとMapのバインディングをサポートする方法を説明します。

4.1. インターフェイスアダプタ

これは、Studentインターフェースの定義方法です。

@XmlJavaTypeAdapter(StudentAdapter.class)
public interface Student {
    public String getName();
}

このインターフェイスは、Stringを返すメソッドを1つだけ宣言し、JAXBバインディングを適用できるタイプとの間で自身をマップするための適応クラスとしてStudentAdapterを指定します。

StudentAdapterクラスは次のように定義されます。

public class StudentAdapter extends XmlAdapter {
    public StudentImpl marshal(Student student) throws Exception {
        if (student instanceof StudentImpl) {
            return (StudentImpl) student;
        }
        return new StudentImpl(student.getName());
    }

    public Student unmarshal(StudentImpl student) throws Exception {
        return student;
    }
}

適応クラスは、XmlAdapterインターフェースを実装し、marshalおよびunmarshalメソッドの実装を提供する必要があります。 marshalメソッドは、バインドされた型(Student、JAXBが直接処理できないインターフェイス)を値型(StudentImpl、JAXBで処理できる具象クラス)に変換します。 unmarshalメソッドは、逆のことを行います。

StudentImplクラスの定義は次のとおりです。

@XmlType(name = "Student")
public class StudentImpl implements Student {
    private String name;

    // constructors, getter and setter
}

4.2. Mapアダプター

exampleエンドポイントインターフェイスのgetStudentsメソッドは、Mapを返し、MapをJAXBで処理できるタイプに変換するための適応クラスを示します。 StudentAdapterクラスと同様に、この適応クラスは、XmlAdapterインターフェイスのmarshalおよびunmarshalメソッドを実装する必要があります。

public class StudentMapAdapter
  extends XmlAdapter> {
    public StudentMap marshal(Map boundMap) throws Exception {
        StudentMap valueMap = new StudentMap();
        for (Map.Entry boundEntry : boundMap.entrySet()) {
            StudentMap.StudentEntry valueEntry  = new StudentMap.StudentEntry();
            valueEntry.setStudent(boundEntry.getValue());
            valueEntry.setId(boundEntry.getKey());
            valueMap.getEntries().add(valueEntry);
        }
        return valueMap;
    }

    public Map unmarshal(StudentMap valueMap) throws Exception {
        Map boundMap = new LinkedHashMap();
        for (StudentMap.StudentEntry studentEntry : valueMap.getEntries()) {
            boundMap.put(studentEntry.getId(), studentEntry.getStudent());
        }
        return boundMap;
    }
}

StudentMapAdapterクラスは、Map<Integer, Student>StudentMap値型との間でマップし、次のように定義します。

@XmlType(name = "StudentMap")
public class StudentMap {
    private List entries = new ArrayList();

    @XmlElement(nillable = false, name = "entry")
    public List getEntries() {
        return entries;
    }

    @XmlType(name = "StudentEntry")
    public static class StudentEntry {
        private Integer id;
        private Student student;

        // getters and setters
    }
}

5. 展開

5.1. Serverの定義

上記のWebサービスをデプロイするために、標準のJAX-WS APIを使用します。 Apache CXFを使用しているため、フレームワークはいくつかの追加作業を行います。 WSDLスキーマを生成および公開します。 サービスサーバーの定義方法は次のとおりです。

public class Server {
    public static void main(String args[]) throws InterruptedException {
        exampleImpl implementor = new exampleImpl();
        String address = "http://localhost:8080/example";
        Endpoint.publish(address, implementor);
        Thread.sleep(60 * 1000);
        System.exit(0);
    }
}

テストを容易にするためにサーバーがしばらくアクティブになったら、システムリソースを解放するためにサーバーをシャットダウンする必要があります。 long引数をThread.sleepメソッドに渡すことにより、必要に応じてサーバーの稼働時間を指定できます。

5.2. Serverの展開

このチュートリアルでは、org.codehaus.mojo:[.s1]exec-maven-pluginプラグインを使用して、上記のサーバーをインスタンス化し、そのライフサイクルを制御します。 これは、Maven POMファイルで次のように宣言されています。


    org.codehaus.mojo
    exec-maven-plugin
    
        com.example.cxf.introduction.Server
    

mainClass構成は、Webサービスエンドポイントが公開されているServerクラスを参照します。 このプラグインのjavaゴールを実行した後、 can check out the WSDL schema automatically generated by Apache CXF by accessing the URL http://localhost:8080/example?wsdl.

6. テストケース

このセクションでは、前に作成したWebサービスの検証に使用されるテストケースを作成する手順を説明します。

テストを実行する前に、Webサービスサーバーを起動するためにexec:javaゴールを実行する必要があることに注意してください。

6.1. 準備

最初のステップは、テストクラスのいくつかのフィールドを宣言することです。

public class StudentTest {
    private static QName SERVICE_NAME
      = new QName("http://introduction.cxf.example.com/", "example");
    private static QName PORT_NAME
      = new QName("http://introduction.cxf.example.com/", "examplePort");

    private Service service;
    private example exampleProxy;
    private exampleImpl exampleImpl;

    // other declarations
}

次の初期化ブロックは、テストを実行する前に、javax.xml.ws.Serviceタイプのserviceフィールドを開始するために使用されます。

{
    service = Service.create(SERVICE_NAME);
    String endpointAddress = "http://localhost:8080/example";
    service.addPort(PORT_NAME, SOAPBinding.SOAP11HTTP_BINDING, endpointAddress);
}

JUnitの依存関係をPOMファイルに追加した後、以下のコードスニペットのように@Beforeアノテーションを使用できます。 このメソッドは、すべてのテストの前に実行され、exampleフィールドを再インスタンス化します。

@Before
public void reinstantiateexampleInstances() {
    exampleImpl = new exampleImpl();
    exampleProxy = service.getPort(PORT_NAME, example.class);
}

exampleProxy変数はWebサービスエンドポイントのプロキシですが、exampleImplは単なるJavaオブジェクトです。 このオブジェクトは、プロキシを介したリモートエンドポイントメソッドの呼び出しの結果とローカルメソッドの呼び出しの結果を比較するために使用されます。

QNameインスタンスは、名前空間URIとローカル部分の2つの部分で識別されることに注意してください。 QNameタイプのService.getPortメソッドのPORT_NAME引数が省略された場合、Apache CXFは、引数の名前空間URIがエンドポイントインターフェイスのパッケージ名であると逆の順序で想定し、そのローカル部分は、Portが付加されたインターフェース名であり、これはPORT_NAME.とまったく同じ値です。したがって、このチュートリアルでは、この引数を省略できます。

6.2. テスト実装

このサブセクションで説明する最初のテストケースは、サービスエンドポイントでのhelloメソッドのリモート呼び出しから返された応答を検証することです。

@Test
public void whenUsingHelloMethod_thenCorrect() {
    String endpointResponse = exampleProxy.hello("example");
    String localResponse = exampleImpl.hello("example");
    assertEquals(localResponse, endpointResponse);
}

リモートエンドポイントメソッドがローカルメソッドと同じ応答を返すことは明らかです。つまり、Webサービスは期待どおりに機能します。

次のテストケースは、helloStudentメソッドの使用を示しています。

@Test
public void whenUsingHelloStudentMethod_thenCorrect() {
    Student student = new StudentImpl("John Doe");
    String endpointResponse = exampleProxy.helloStudent(student);
    String localResponse = exampleImpl.helloStudent(student);
    assertEquals(localResponse, endpointResponse);
}

この場合、クライアントはStudentオブジェクトをエンドポイントに送信し、代わりに学生の名前を含むメッセージを受信します。 前のテストケースと同様に、リモート呼び出しとローカル呼び出しの両方からの応答は同じです。

ここで最後に示すテストケースは、より複雑です。 サービスエンドポイント実装クラスで定義されているように、クライアントがエンドポイントでhelloStudentメソッドを呼び出すたびに、送信されたStudentオブジェクトはキャッシュに格納されます。 このキャッシュは、エンドポイントでgetStudentsメソッドを呼び出すことで取得できます。 次のテストケースは、studentsキャッシュの内容が、クライアントがWebサービスに送信したものを表していることを確認します。

@Test
public void usingGetStudentsMethod_thenCorrect() {
    Student student1 = new StudentImpl("Adam");
    exampleProxy.helloStudent(student1);

    Student student2 = new StudentImpl("Eve");
    exampleProxy.helloStudent(student2);

    Map students = exampleProxy.getStudents();
    assertEquals("Adam", students.get(1).getName());
    assertEquals("Eve", students.get(2).getName());
}

7. 結論

このチュートリアルでは、JavaでWebサービスを操作するための強力なフレームワークであるApache CXFを紹介しました。 実行時にフレームワークの特定の機能を利用しながら、標準のJAX-WS実装としてのフレームワークのアプリケーションに焦点を当てました。

これらすべての例とコードスニペットの実装は、a GitHub projectにあります。