hatenob

プログラムって分からないことだらけ

WildflyでJAX-WSの非同期コール

JAX-WSを使ってサービスコールをする際には、3つの呼出し方が選べる。

  • 同期
  • 非同期コールバック
  • 非同期ポーリング

あくまで、「コンシューマが選択できるもの」としてこの3つを試したみた。
もちろん、プロバイダ側を非同期型にする(この場合、コンシューマ側は同期型になるが処理結果ではなく処理受付結果が返るようなイメージになる)こともできるけれど、ここでは「プロバイダ側は同期、非同期を意識していないのだけれど、呼出側の形態として非同期で呼びたい」という場合のこと。
Wildflyで試してみた。

プロバイダの実装

上記の通り、プロバイダの実装は同期・非同期によらない。

@WebService(name = "Hello")
@SOAPBinding(style = Style.RPC)
public class Hello {
    private static final Logger log = Logger.getLogger(Hello.class);

    @WebMethod
    @WebResult(name = "result")
    public Res say() {
        Res res = new Res();
        res.setMessage("hello");
        res.setSuccess(true);
        res.setNums(BigDecimal.ONE);
        res.setTs(new Timestamp(System.currentTimeMillis()));

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return res;
    }
}

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Res {
    private String message;
    private boolean success = false;
    private BigDecimal nums;
    @XmlJavaTypeAdapter(TimestampAdapter.class)
    private Timestamp ts;
    ...
}

「say」と言われたら「hello」を返すという非常に単純なもの。
非同期の動きを確認したいので3秒スリープを入れているだけ。

コンシューマの実装

Portインタフェース

まず、Portインタフェースを用意する。
同じ機能を呼び出すのだけれど、あくまでコンシューマが呼び分けるだけなので、コールバックでもポーリングでも同じオペレーション名になる。

@WebService(name = "Hello")
@SOAPBinding(style = Style.RPC)
public interface HelloPort {
    @WebResult(name = "result")
    public Res say();

    @WebMethod(operationName = "say")
    public Response<Res> sayAsync();

    @WebMethod(operationName = "say")
    public Future<Res> sayAsync(
            @WebParam(name = "asyncHandler") AsyncHandler<Res> callback);
}

メソッド名は、元のメソッド名に「Async」を付ける。どうやらこれがお約束らしく、最初は別の名前にしてたら動かなかったところ、名前を変えたら動いた。仕様なのか、だとしたら何の仕様かといったことはひとまず棚にあげた。まずはありのままを受け入れる。

コンシューマ

コンシューマはService.createしてあとは普通。それぞれの非同期型の戻りに応じたハンドリングをする。

Service service = Service.create(new URL(WSDL_URL), new QName(NAMESPACE, SERVICE_NAME));
HelloPort port = service.getPort(new QName(NAMESPACE, PORT_NAME), HelloPort.class);

log.info("------------------- sync --------------------");
Res r = port.say();
log.info(r.toString());

log.info("------------------- callback --------------------");
Future<Res> fr = port.sayAsync(new AsyncHandler<Res>() {
    @Override
    public void handleResponse(Response<Res> res) {
        try {
            log.info(res.get().toString());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
});

log.info("------------------- polling --------------------");
Response<Res> rr = port.sayAsync();
while (rr.isDone()) {
    try {
        TimeUnit.SECONDS.sleep(4);
    } catch (InterruptedException e) {
        e.printStackTrace();
        return;
    }
}
try {
    log.info(rr.get().toString());
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

コールバック型の場合はFutureインタフェースを戻り型にする。
コールバックハンドラを用意する。
ポーリング型の場合はResponseインタフェースを戻り型にする。

呼び分けとしてはたったこれだけで、同期でも非同期でも呼び出すことができる。
非同期の場合でも、プロバイダからすると単にリクエストを受けて処理をしてレスポンスを返しているだけなので同期に見える。
この呼び分けはコンシューマ側のリクエストスレッドをメインスレッドから切り離し、リクエスト・レスポンスが別スレッドで動くイメージ。