1
0

ResponseEntityと@ResponseStatusの違いを調べました

Posted at

入る前に

  • 私は韓国人として、日本語の勉強とコンピュータの勉強を同時に行うために、ここに文章を書きます
  • 翻訳機の助けを借りて書かれた文章なので、誤りがあるかもしれません
  • 間違った部分についてのフィードバックはいつでも歓迎です
  • 私と異なる意見もいつでも歓迎です
  • 日本の開発者たちとコミュニケーションを取りたいです!

主題紹介

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を含んでいます。
互いに何が違うのでしょうか?そして、どちらを使うべきでしょうか?

image.png

ResponseStatusを開くと、次のようなコメントが出てきます。
順を追って解釈してみましょう。


ResponseStatusを調べる

メソッドまたは例外クラスに返されるステータス{@link #code}と{@link #reason}を表示します。
image.png

@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "見つかりません")

ステータスコードと理由を付けることができるという話です。


ステータスコードはハンドラーメソッドが呼び出されるときにHTTP応答に適用されますが、{@code ResponseEntity}や{@code "redirect:"}のような他の方法で設定されたステータス情報を上書きすることはありません。

@ResponseStatusよりResponseEntityを使用したステータス情報が優先的に適用されるという意味です。
本当にそうなのか、一度確認してみましょう。

image.png

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @PostMapping()
    public ResponseEntity<String> test(){
        return ResponseEntity.ok("hello");
    }

以下の写真のようなコードを作成し、呼び出してみました。(便宜のためにswaggerを使用しました。)

image.png

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はどのようなものでしょうか?

image.png

{@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&lt;String&gt; entity = template.getForEntity("https://example.com", String.class);
 * String body = entity.getBody();
 * MediaType contentType = entity.getHeaders().getContentType();
 * HttpStatus statusCode = entity.getStatusCode();
 *
 * // exchange 例
 * ResponseEntity&lt;String&gt; 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を使うのが正しいというのが…私の結論です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0