入る前に
- 私は韓国人として、日本語の勉強とコンピュータの勉強を同時に行うために、ここに文章を書きます
- 翻訳機の助けを借りて書かれた文章なので、誤りがあるかもしれません
- 間違った部分についてのフィードバックはいつでも歓迎です
- 私と異なる意見もいつでも歓迎です
- 日本の開発者たちとコミュニケーションを取りたいです!
主題紹介
SpringでHTTP応答を設定するResponseEntityと@ResponseStatusオプションの違いについて調べました。
ResponseEntity
@PostMapping()
public ResponseEntity<String> startWeatherCrawlToday(){
return ResponseEntity.ok("hello");
}
上記のコードはHTTPステータスコードを200で返し、応答本文にhelloを含んでいます。
@ ResponseStatus
@ResponseStatus(HttpStatus.OK)
@PostMapping()
public String startWeatherCrawlToday(){
return "hello";
}
上記のコードも同様にHTTPステータスコード200を返し、応答本文にhelloを含んでいます。
互いに何が違うのでしょうか?そして、どちらを使うべきでしょうか?
ResponseStatusを開くと、次のようなコメントが出てきます。
順を追って解釈してみましょう。
ResponseStatusを調べる
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "見つかりません")
ステータスコードと理由を付けることができるという話です。
ステータスコードはハンドラーメソッドが呼び出されるときにHTTP応答に適用されますが、{@code ResponseEntity}や{@code "redirect:"}のような他の方法で設定されたステータス情報を上書きすることはありません。
@ResponseStatusよりResponseEntityを使用したステータス情報が優先的に適用されるという意味です。
本当にそうなのか、一度確認してみましょう。
@ResponseStatus(HttpStatus.NOT_FOUND)
@PostMapping()
public ResponseEntity<String> test(){
return ResponseEntity.ok("hello");
}
以下の写真のようなコードを作成し、呼び出してみました。(便宜のためにswaggerを使用しました。)
helloが正常に返されることが分かります。
なぜなら、ResponseEntityがメソッド内部で明示的に応答を設定するため、@ ResponseStatusよりも優先的に適用されるからです。ResponseEntityに関する内容は後で続けて扱います。
警告: このアノテーションを例外クラスに使用したり、このアノテーションの{@code reason}属性を設定する際には、{@code HttpServletResponse.sendError}メソッドが使用されます。
先ほど最初に扱った内容の延長線上です。
@ResponseEntityを例外クラスに付けると、その例外が発生したときに特定のHTTPステータスコードとメッセージが応答として送信されます。
また、reasonにステータスコードと共に理由を含めることができます。
この場合、Spring内部でHttpServletResponse.sendErrorを使用してステータスコードとメッセージが伝達されます。
/**
* Sends an error response to the client using the specified status code and clears the output buffer. The server
* defaults to creating the response to look like an HTML-formatted server error page containing the specified
* message, setting the content type to "text/html", leaving cookies and other headers unmodified. If an error-page
* declaration has been made for the web application corresponding to the status code passed in, it will be served
* back in preference to the suggested msg parameter.
* <p>
* If the response has already been committed, this method throws an IllegalStateException. After using this method,
* the response should be considered to be committed and should not be written to.
*
* @param sc the error status code
* @param msg the descriptive message
*
* @exception IOException If an input or output exception occurs
* @exception IllegalStateException If the response was committed
*/
void sendError(int sc, String msg) throws IOException;
/**
* Sends an error response to the client using the specified status code and clears the buffer. This is equivalent
* to calling {@link #sendError(int, String)} with the same status code and <code>null</code> for the message.
*
* @param sc the error status code
*
* @exception IOException If an input or output exception occurs
* @exception IllegalStateException If the response was committed before this method call
*/
void sendError(int sc) throws IOException;
HttpServletResponse.sendErrorに入ると、次のような内容を見ることができます。
void sendError(int sc, String msg) throws IOException;
-
これを通じて、ステータスコードとメッセージを使用してクライアントにエラーを送信します。(メッセージは省略されることがあります)
-
両方のメソッドは出力バッファをクリアし、応答がコミットされた状態ではIllegalStateExceptionを発生させます
-
ステータスコードに対するエラーページの宣言がある場合、そのエラーページが返されます
-
応答は完了したと見なされるため、それ以降はもう応答を作成できません
-
これに関連する内容が続きますので、引き続き説明します
{@code HttpServletResponse.sendError}を使用する場合、応答は完了したものと見なされ、それ以上作成されるべきではありません。また、サーブレットコンテナは通常、HTMLエラーページを作成するため、{@code reason}の使用はREST APIには適していません。このような場合、{@code @ResponseStatus}を使用せず、戻り値の型として{@link org.springframework.http.ResponseEntity}を使用することをお勧めします。
前述のコメントでも述べたように、サーブレットコンテナは通常HTMLエラーページを生成します。
これはREST API形式とは合いません。
なぜなら、通常REST APIはJSON形式の応答を使用するからです。
そのため、このような場合は @ ResponseStatusの代わりにResponseEntityを使用することが推奨されています。
ResponseEntityを使用することで、ステータスコードと応答本文を明示的に設定でき、JSONにエラーメッセージを含めるなど、よりREST APIに適した応答が可能だからです。
@GetMapping("/resource/{id}")
public ResponseEntity<String> getResource(@PathVariable String id) {
Resource resource = findResourceById(id);
if (resource == null) {
// JSON形式のエラーメッセージと共に404ステータスコードが返されます。
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body("{\"error\": \"Resource not found\"}");
}
- ResponseEntity使用例
ResponseEntityについて調べる
それでは、ResponseEntityはどのようなものでしょうか?
{@link HttpEntity}を拡張して、{@link HttpStatusCode}ステータスコードを追加します。
- {@code RestTemplate}および{@code @Controller}メソッドで使用されます
HttpEntityを拡張してHTTPステータスコードを追加したクラスです。
SpringのRestTemplateと@Controllerで使用されます。
{@code RestTemplate}では、このクラスが
{@link org.springframework.web.client.RestTemplate#getForEntity getForEntity()}および
{@link org.springframework.web.client.RestTemplate#exchange exchange()}によって返されます。
続く説明です。getForEntityとexchangeメソッドがResponseEntityオブジェクトを返すと言っています。
* // getForEntity 例
* ResponseEntity<String> entity = template.getForEntity("https://example.com", String.class);
* String body = entity.getBody();
* MediaType contentType = entity.getHeaders().getContentType();
* HttpStatus statusCode = entity.getStatusCode();
*
* // exchange 例
* ResponseEntity<String> exchangeEntity = template.exchange("https://example.com", HttpMethod.GET, null, String.class);
* String exchangeBody = exchangeEntity.getBody();
* MediaType exchangeContentType = exchangeEntity.getHeaders().getContentType();
* HttpStatus exchangeStatusCode = exchangeEntity.getStatusCode();
* </pre>
このクラスはまた、Spring MVCで{@code @Controller}メソッドの戻り値として使用することもできます。
@RequestMapping("/handle")
public ResponseEntity<String> handle() {
URI location = ...;
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation(location);
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World", responseHeaders,
HttpStatus.CREATED);
}
ResponseEntityを戻り値として使用できるという内容です。
または、静的メソッドを通じてアクセス可能なビルダーを使用します。
@RequestMapping("/handle")
public ResponseEntity<String> handle() {
URI location = ...;
return ResponseEntity.created(location)
.header("MyResponseHeader", "MyValue")
.body("Hello World");
}
このようにビルダーパターンを通じて使用することもできます。
終わりに
結論
Rest APIの環境では、ResponseEntityを使用することが適切です。
また、ResponseEntityを使うと
@PostMapping()
public ResponseEntity<String> createOrUpdateResource(@RequestBody Resource resource) {
boolean exists = resourceService.exists(resource.getId());
if (exists) {
resourceService.update(resource);
return ResponseEntity.ok("update success!");
} else {
resourceService.create(resource);
return ResponseEntity.status(HttpStatus.CREATED).body("create success!");
}
}
このように、応答コードをはるかに柔軟に設定できます。
やはりResponseEntityを使うのが正しいというのが…私の結論です。