1. 概要

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

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

このチュートリアルは、Apache CXFに関するシリーズの最初のものであり、フレームワークの基本的な特性を紹介しています。 ソースコードでJAX-WS標準APIのみを使用し、自動生成されたWSDLメタデータやCXFのデフォルト構成などのバックグラウンドでApacheCXFを利用します。

2. Mavenの依存関係

ApacheCXFを使用するために必要な主な依存関係は次のとおりです。 org.apache.cxf:cxf–rt–frontend– jaxws。 これにより、組み込みのJDKを置き換えるJAX-WS実装が提供されます。

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxws</artifactId>
    <version>3.1.6</version>
</dependency>

このアーティファクトには、META-INF/servicesディレクトリ内にjavax.xml.ws.spi.Providerという名前のファイルが含まれていることに注意してください。 Java VMは、このファイルの最初の行を調べて、使用するJAX-WS実装を決定します。 この場合、行の内容はoです。 rg.apache.cxf.jaxws.spi.ProviderImpl 、ApacheCXFによって提供される実装を参照します。

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

<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http-jetty</artifactId>
    <version>3.1.6</version>
</dependency>

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

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

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

@WebService(endpointInterface = "com.baeldung.cxf.introduction.Baeldung")
public class BaeldungImpl implements Baeldung {
    private Map<Integer, Student> students 
      = new LinkedHashMap<Integer, Student>();

    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<Integer, Student> getStudents() {
        return students;
    }
}

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

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

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

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

    public String helloStudent(Student student);

    @XmlJavaTypeAdapter(StudentMapAdapter.class)
    public Map<Integer, Student> getStudents();
}

デフォルトでは、ApacheCXFはデータバインディングアーキテクチャとしてJAXBを使用します。 ただし、JAXBはgetStudentsメソッドから返されるMapのバインドを直接サポートしていないため、MapをJAXBのJavaクラスに変換するためのアダプターが必要です。 を使用できます。

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

アダプターは、すぐ下のセクションに示されています。

4. カスタムアダプタ

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

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

Studentインターフェースの定義は次のとおりです。

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

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

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

public class StudentAdapter extends XmlAdapter<StudentImpl, Student> {
    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. マップアダプター

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

public class StudentMapAdapter 
  extends XmlAdapter<StudentMap, Map<Integer, Student>> {
    public StudentMap marshal(Map<Integer, Student> boundMap) throws Exception {
        StudentMap valueMap = new StudentMap();
        for (Map.Entry<Integer, Student> 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<Integer, Student> unmarshal(StudentMap valueMap) throws Exception {
        Map<Integer, Student> boundMap = new LinkedHashMap<Integer, Student>();
        for (StudentMap.StudentEntry studentEntry : valueMap.getEntries()) {
            boundMap.put(studentEntry.getId(), studentEntry.getStudent());
        }
        return boundMap;
    }
}

The StudentMapAdapter クラスマップ地図との間 StudentMap 次のように定義された値型:

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

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

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

        // getters and setters
    }
}

5. 展開

5.1. サーバー定義

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

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

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

5.2. サーバーの展開

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

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <configuration>
        <mainClass>com.baeldung.cxf.introduction.Server</mainClass>
    </configuration>
</plugin>

mainClass 構成は、Webサービスエンドポイントが公開されているServerクラスを参照します。 このプラグインのjavaゴールを実行した後、URL http:// localhost:8080 / baeldung?wsdl。にアクセスすることで、ApacheCXFによって自動的に生成されたWSDLスキーマをチェックアウトできます。 ]

6. テストケース

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

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

6.1. 準備

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

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

    private Service service;
    private Baeldung baeldungProxy;
    private BaeldungImpl baeldungImpl;

    // other declarations
}

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

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

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

@Before
public void reinstantiateBaeldungInstances() {
    baeldungImpl = new BaeldungImpl();
    baeldungProxy = service.getPort(PORT_NAME, Baeldung.class);
}

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

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

6.2. テストの実装

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

@Test
public void whenUsingHelloMethod_thenCorrect() {
    String endpointResponse = baeldungProxy.hello("Baeldung");
    String localResponse = baeldungImpl.hello("Baeldung");
    assertEquals(localResponse, endpointResponse);
}

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

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

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

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

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

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

    Student student2 = new StudentImpl("Eve");
    baeldungProxy.helloStudent(student2);
        
    Map<Integer, Student> students = baeldungProxy.getStudents();       
    assertEquals("Adam", students.get(1).getName());
    assertEquals("Eve", students.get(2).getName());
}

7. 結論

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

これらすべての例とコードスニペットの実装は、GitHubプロジェクトにあります。