APIの基本(サービス)
pom.xml
<!-- REST API 基本規格 (Jakarta RESTful Web Services 3.1) -->
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- JSONバインディング (Javaオブジェクト ⇔ JSON 相互変換) -->
<dependency>
<groupId>jakarta.json.bind</groupId>
<artifactId>jakarta.json.bind-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<!-- JSON処理 API (JSONの直接操作・パース用) -->
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.1.3</version>
<scope>provided</scope>
</dependency>
APIサービス作成
APIは『エンドポイント』と言われるURLへのリクエストで発火します。
まずは大元のエンドポイントを『jakarta.ws.rs.core.Application』を継承したクラスに設定します。
今回は『API』の『サービス』側のクラスとなりますので、パッケージは『api.service』とします。
それではパッケージに『RestApiService』クラスを作成してみましょう。
package api.service;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
@ApplicationPath("/api")
public class RestApiService extends Application {}
大事なのは『@ApplicationPath("/api")』で、このアプリのエンドポイントは以下に決定した事です。
http://localhost:8080/sample-pj/api 実は、エンドポイントはまだ途中です。
次に用途によって変更したいエンドポイントを決定します。
例えば、契約に関するAPIであれば『ContractsApiService』クラスを作成します。
package api.service;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
@Path("/contract")
public class ContractsAPIService {
@GET
@Path("/keiyaku")
public String getKeiyakuName(@QueryParam("kei_no") String keiNo) {
System.out.println("----- getKeiyakuName -----");
return "00000" + keiNo;
}
}
つまり、このAPIサービスを利用する為には、以下のURLでアクセスする必要があります。
http://localhost:8080/sample-pj/api/contract/keiyaku また『@GET』が付与されているので、当然『GET通信』となります。
試しにビルドして、ターミナルから以下を叩いてみて下さい。
curl "http://localhost:8080/sample-pj/api/contract/keiyaku?kei_no=abcdefg"
import jakarta.ws.rs.POST;
~ 省略 ~
@Path("/contract")
public class ContractsAPIService {
@POST
@Path("/keiyaku")
public String getKeiyakuName(@FormParam("kei_no") String keiNo) {
~ 省略 ~
}
}
curl -X POST "http://localhost:8080/sample-pj/api/contract/keiyaku" -d "kei_no=abcdefg"
GET通信は問題ないが、POST通信で謎のエラーがでる場合は、もしかして『 不正遷移』設定をしていませんか?
この設定をしていると、リクエスト側に完全ランダムな文字列を要求する事になるため確実に落ちます。対策は当然『チェックしない』となりますので、Filter処理の直前で以下ソースを挟みトークンチェックをスキップさせます。 // トークンチェックの有無を「appinfo」から確認
private boolean checkAppInfo(String pageName) {
if (pageName == null || pageName.trim().isEmpty() || !pageName.endsWith(".xhtml")) {
return false;
}
~ 省略 ~
パラメータにDTOを渡す方法
そこでお約束の『DTO』にまとめる事でスッキリとしたコードとなります。 任意のパッケージに『KeiyakuApiDto』を作成しましょう。
package dto;
import jakarta.json.bind.annotation.JsonbProperty;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class KeiyakuApiDto {
@JsonbProperty("kei_no")
private String keiNo;
@NotBlank(message = "名前は必須です")
private String name;
}
つまりリクエスト側からは『kei_no』をKEYとして送信されるが、JAVA側は『keiNo』に格納しますよって意味です。
『@NotBlank』は、ビーンバリデーションの設定となりますが、単独では発火しません(後述で説明)
『@JsonProperty』と記述します。
因みにインポートするクラスは以下となります。
import com.fasterxml.jackson.annotation.JsonProperty;
⇒
public String getKeiyakuName(KeiyakuApiDto dto) {
curl.exe -X POST "http://localhost:8080/sample-pj/api/contract/keiyaku" -H "Content-Type: application/json" -d "{\"kei_no\": \"12345\", \"name\":\"hoge\"}" また、DTOと言えば『ビーンバリデーション』ですよね。
ビーンバリデーションを利用するには、PIサービス側のメソッドを更に修正します。
⇒
public String getKeiyakuName(@Valid KeiyakuApiDto dto) {
試しに以下を叩いてみましょう。
curl.exe -X POST "http://localhost:8080/sample-pj/api/contract/keiyaku" -H "Content-Type: application/json" -d "{\"kei_no\": \"12345\"}" どうでしたか?
以下の様に表示された方はいませんか?
[] 前は必須です]arg0.name] はい、文字化けです。
この現象は、RESTEasy標準のデフォルトエラー出力形式である[arg0.name 名前は必須です] が、コンパイルの段階で文字化けを起こして破壊された結果です。
要するにビーンバリデーションの大きな特徴である『処理がプログラマに来る前に』出力されるエラーの事です。 ここの処理で文字化けされると流石に困ります。
ただし解決策はちゃんとありますのでご安心を。
プロバイダクラスを作成し、エラー出力ルールを変更しましょう。 クラス名パッケージ名は自由ですが、以下の様に作成します。
package provider;
import jakarta.validation.ConstraintViolationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import java.util.HashMap;
import java.util.Map;
@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
@Override
public Response toResponse(ConstraintViolationException exception) {
Map<String, String> errors = new HashMap<>();
exception.getConstraintViolations().forEach(violation -> {
// パス(例: "createKeiyaku.dto.name" -> "name")からフィールド名だけを取り出す
String path = violation.getPropertyPath().toString();
String fieldName = path.substring(path.lastIndexOf('.') + 1);
errors.put(fieldName, violation.getMessage());
});
// 明示的に UTF-8 指定の JSON としてレスポンスを作成
return Response.status(Response.Status.BAD_REQUEST)
.type(MediaType.APPLICATION_JSON + ";charset=UTF-8")
.entity(errors)
.build();
}
}
ただひとつ安心して欲しいのは『ExceptionMapper』クラスは、Jakarta REST (JAX-RS) という REST API 専用の通信フレームワークの管轄下でのみ動作する仕組みです。
つまり他の処理には影響しません! さて、これでもう一度先ほどのコマンドでAPIリクエストを送信しましょう。
{"name":"名前は必須です"} ちゃんと期待値通りの返却がされたハズです。
JSONを返却する
では返却用のDTOを作成します。
package dto;
import lombok.Data;
@Data
public class ResponseApiDto {
private int code;
private String message;
}
package api.service;
import dto.KeiyakuApiDto;
import dto.ResponseApiDto;
import jakarta.validation.Valid;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@Path("/contract")
@Produces(MediaType.APPLICATION_JSON)
public class ContractsAPIService {
@POST
@Path("/keiyaku")
public Response getKeiyakuName(@Valid KeiyakuApiDto dto) {
System.out.println("----- getKeiyakuName -----");
ResponseApiDto res = new ResponseApiDto();
res.setCode(200);
res.setMessage("正常に処理が完了しました。");
return Response.status(Response.Status.OK).entity(res).build();
}
}
curl.exe -X POST "http://localhost:8080/sample-pj/api/contract/keiyaku" -H "Content-Type: application/json" -d "{\"kei_no\": \"12345\", \"name\":\"hoge\"}" 以下のJSON文字列が返却されます。
{"code":200,"message":"正常に処理が完了しました。"}
見てお分かりの通り『Response.status(Response.Status.OK)』とする事で、Headerには『HTTP/1.1 200 OK』が設定されます。
その他、以下の様にHeaderに値を設定できます。
return Response.status(Response.Status.BAD_REQUEST).entity(dto).build();
return Response.status(Response.Status.NOT_FOUND).entity(dto).build();
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(dto).build();





『JSON』を扱うモジュールは、シェアの面から見れば圧倒的に『Jackson』となります。
両方入れちぇえってのは、ライブラリの衝突など、厄介事に巻き込まれそうなので悪手となります。ただし『JSON-B』は、Jakarta EEの標準規格となります。
ちょっと悩ましいですね。
因みに『Jackson』モジュールを入れる場合の《pom.xml》は以下となります。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version> <!-- 2.15以上を推奨 -->
</dependency>
<!-- Jakarta EE (JAX-RS) と Jackson を連携させるプロバイダー -->
<dependency>
<groupId>com.fasterxml.jackson.jakarta.rs</groupId>
<artifactId>jackson-jakarta-rs-json-provider</artifactId>
<version>2.17.2</version>
</dependency>