Spring Boot解説第19回(基本編:Controllerとは その2 ~@ResponseBodyと@ModelAttribute~)

  • 11
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

こんにちはこんばんは!
株式会社情創 技術開発局
リオオリンピックの熱が去って焼き肉が食べたくなった@YAKINIKUです。

今回は引き続き、Controller について解説していきます。

1.@ResponseBodyによる戻り値の設定

前回説明しましたが、@ResponseBodyを付けたメソッドは戻り値がそのままレスポンスのコンテンツになります。
また、@RestControllerを付けた Controller のメソッドは@ResponseBodyを付けなくても、戻り値がコンテンツになります。
これは、JSON や XML などを返す場合に使用されることが多いです。

前回宣言した通り、本ブログでは Controller は ModelAndView クラスを返すのが基本になります。
しかし、JSON や XML を返す必要がある場合は例外的に、@ResponseBodyを使います。

以降は、@ResponseBodyの記述方法について説明していきます。

1-1.HttpServletResponse でコンテンツを返す

戻り値を void にしても、HttpServletResponse を引数に加えれば、レスポンスのコンテンツを直接書き込むことができます。

@RequestMapping("/text")
@ResponseBody
public void text(HttpServletResponse res) throws IOException {
    res.getWriter().write("text content");
}

動作自体は戻り値を String にした場合と変わりませんが、この記述方法は後述するファイルのダウンロードにも使用できます。

1-2.コンテンツタイプを指定する

コンテンツタイプは@RequestMappingの produces 属性で指定できます。
org.springframework.http.MediaType クラスにコンテンツタイプの定数が定義されているので、こちらを使いましょう。

@RequestMapping(value="/xml", produces=MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public String xml() {
    return "<root><content>xml</content></root>";
}

1-3.HTTP ステータスやレスポンスヘッダを指定する

HTTP ステータスやコンテンツタイプ以外のレスポンスヘッダを指定したい場合は、戻り値を ResponseEntity<T> にします。
ResponseEntity はボディ、ヘッダ、ステータスを持つクラスで、型 T にはボディの型を指定します。

@RequestMapping("/responseEntity")
@ResponseBody
public ResponseEntity<String> responseEntity() {
    HttpHeaders headers = new HttpHeaders();
    headers.add("Connection", "Keep-Alive");
    HttpStatus status = HttpStatus.OK;
    return new ResponseEntity<String>("text content", headers, status);
}

1-4.JSON を返す

JSON を返す場合は、戻り値は任意のクラスにします。
そうすることで、Spring Boot が Jackson という JSON 処理ライブラリを使用して、JSON 形式に変換してくれます。

例えば String 型のメンバ変数 id と name をもつ User クラスを作り、そのリストを返す場合、メソッドは以下のように記述できます。

@RequestMapping("/json")
@ResponseBody
public List<User> json() {
    return Arrays.asList(new User("1", "aaa"), new User("2", "bbb"));
}

このメソッドによって、以下のような JSON データが生成されます。

[{"id":"1","name":"aaa"},{"id":"2","name":"bbb"}]

1-5.ファイルをダウンロードさせる

ファイルをダウンロードさせるには、以下のように HttpServletResponse を使用する方法があります。

@RequestMapping("/file1")
@ResponseBody
public void file1(HttpServletResponse res) throws IOException {
    File file = new File("test.xml");
    res.setContentLength((int) file.length());
    res.setContentType(MediaType.APPLICATION_XML_VALUE);
    FileCopyUtils.copy(new FileInputStream(file), res.getOutputStream());
}

また、戻り値に org.springframework.core.io.Resource を使用する方法もあります。
こちらは Content-Length を自動で設定してくれるので、より簡潔に記述できます。

@RequestMapping(value = "/file2", produces = MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public Resource file2() {
    return new FileSystemResource("test.xml");
}

2.リクエストパラメータの取得

ここからは前回説明できなかったリクエストパラメータの取得について説明していきます。

これまでは Controller が View に返す内容について触れてきましたが、当然 form などkからリクエストパラメータを受け取る処理も必要になります。

受け取ったデータは扱いやすいようにオブジェクトにバインドしますが、前回紹介した@ModelAttributeアノテーションを使うことで、リクエスト時に自動的にバインドを行います。

本項では@ModelAttributeと BindingResult について解説します。
なお本ブログでは、@ModelAttributeはメソッドの引数に記述し、必ず BindingResult を使用してエラーの有無を検出します。

2-1.@ModelAttribute

@ModelAttributeは指定したクラスにリクエストパラメータをバインドします。
@ModelAttributeを付けたメソッドは、リクエストのたびに@RequestMappingメソッドの前に呼ばれます。

以下のような id と name を submit する場合を例にして説明します。

<form action="modelAttribute" method="post">
    <input type="text" name="id"/>
    <input type="text" name="name"/>
</form>

これを第○○回で作成した TestBean クラスで受け取ります。
このとき、変数名が submit されるパラメータ名と一致している必要があります。

package com.qiita.demo.domain.tbl;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class TestBean implements java.io.Serializable {
    private int id;
    private String name;
}

@ModelAttributeメソッドはこの TestForm クラスを new して返します。

@ModelAttribute
TestBean testBean() {
    return new TestBean();
}

なお、@ModelAttributeによってバインドされたクラスは、自動的に Model に追加されます。
このとき、デフォルトでクラス名の頭文字を小文字にしたものが属性名になりますが、@ModelAttribute("属性名")とすれば指定することもできます。

また、@ModelAttribute@RequestMappingメソッドの引数に直接付けることもできます。

@RequestMapping("/modelAttribute")
public ModelAndView modelAttribute(@ModelAttribute TestBean testBean, ModelAndView mav) {
    mav.setViewName("modelAttribute");
    return mav;
}

@ModelAttributeメソッドを用意するより、こちらのほうが一般的のようです。

2-2.BindingResult

@ModelAttribute を使用してバインドを行う場合、そのクラスの後ろに BindingResult というクラスを引数にすることで、バインドが失敗したかどうかを判定できます。
逆に BindingResult が引数にない場合、バインドのエラーがあった際にメソッドそのものが呼ばれません。

@RequestMapping("/modelAttribute")
public ModelAndView modelAttribute(@ModelAttribute TestBean testBean, BindingResult result, ModelAndView mav) {
    if (result.hasErrors()) {
        //バインドエラー時
        mav.setViewName("error");
        return mav;
    }
    mav.setViewName("success");
    return mav;
}

このとき、BindingResult は必ず@ModelAttributeを付けた引数の次に記述しなければならないことに注意してください。
異なる位置に記述していた場合、エラーが発生してメソッドが呼ばれなくなってしまいます。

では今回はこの辺で終わりにします。
最後まで読んでいただきありがとうございました。
それではみなさんまた会いましょう!

参考URL