Spring MVCのコントローラでの戻り値いろいろ

Spring MVCのコントローラのメソッドで使える戻り値にどんなものがあるか、どういう使い方ができるかをざっくりまとめてみた。

# `@Controller`と`@RestController`の違い

先に`@Controller`と`@RestController`の違いを説明しておく。

Spring MVC ではコントローラクラスにアノテーションで`@Controller`または`@RestController`を付ける。

`@Controller`は主にWebページ用のコントローラで使用する。
Webページ用コントローラはJSPやテンプレートエンジンのViewに遷移してレスポンスのHTMLを生成するので、基本的にメソッドの戻り値はViewの遷移先を指定するのに使用する。

`@RestController`はJsonやXML等を返すWebAPI用のコントローラで使用する。
こちらはViewに遷移しないのでメソッドの戻り値はレスポンスのコンテンツになる。

# `@Controller`

## 戻り値をStringにする

Viewの遷移先を指定する一番単純なやり方。

```java
    @RequestMapping("/method1")
    public String method1() {
        return "/index.html";
    }
```

## 戻り値をModelAndViewにする

ModevAndViewを使用するとViewに渡したい情報を一緒に返すことができる。

```java
    @RequestMapping("/method2")
    public ModelAndView method2() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject(new User("紀伊", "太郎"));
        modelAndView.setViewName("/index.html");
        return modelAndView;
    }
```

## フォワード

戻り値で指定する遷移先に"forward:"を付けると他のコントローラメソッドに遷移する。

```java
    @RequestMapping("/forward1")
    public String forward1() {
        return "forward:method1";
    }
```

## リダイレクト

リダイレクトしたい場合は"redirect:"を付ける。

```java
    @RequestMapping("/redirect1")
    public String redirect1() {
        return "redirect:method1";
    }
```

外部URLを指定する事もできる。

```java
    @RequestMapping("/redirect2")
    public String redirect2() {
        return "redirect:https://www.google.co.jp/";
    }
```

## コントローラで直接コンテンツを返す

メソッドに`@ResponseBody`を付けると戻り値で直接レスポンスのコンテンツを返す事ができる。

```java
    @RequestMapping("/text1")
    @ResponseBody
    public String text1() {
        return "text content";
    }
```

戻り値をvoidにしてHttpServletResponseを引数で受け取るようにすれば、直接レスポンスを書き込む事もできる。

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

# `@RestController`

## 戻り値でテキストコンテンツを返す

`@RestController`を付けたコントローラのメソッドでは`@ResponseBody`を付けなくても戻り値がレスポンスのコンテンツになる。

```java
    @RequestMapping("/text1")
    public String text1() {
        return "text content";
    }
```

## HttpServletResponseでテキストコンテンツを返す

`@Controller`の場合と同じくHttpServletResponseで直接コンテンツを書き込める。

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

## コンテンツタイプを指定する

コンテンツタイプは`@RequestMapping`のproducesで指定する。

```java
    @RequestMapping(value = "/xml1", produces = "application/xml")
    public String xml1() {
        return "<a><b>content</b></a>";
    }
```

org.springframework.http.MediaTypeクラスにコンテンツタイプの定数が定義してあるのでこれを使うといい。

```java
    @RequestMapping(value = "/xml2", produces = MediaType.APPLICATION_XML_VALUE)
    public String xml2() {
        return "<a><b>content</b></a>";
    }
```

## HTTPステータスやレスポンスヘッダを指定する

HTTPステータスやコンテンツタイプ以外のレスポンスヘッダも指定したい時は、戻り値をResponseEntityにする。

```java
    @RequestMapping("/responseEntity")
    public ResponseEntity<String> responseEntity() {
        HttpHeaders headers = new HttpHeaders();
        headers.add("header1", "heaer1-value");
        HttpStatus status = HttpStatus.NOT_FOUND;
        return new ResponseEntity<>("text content", headers, status);
    }
```

## JSONを返す

メソッドの戻り値を任意のクラスにすれば、SpringMVCがJacksonを使用してうまいことやってくれる。

```java
    @RequestMapping("/json")
    public List<User> json() {
        return Arrays.asList(new User("紀伊", "太郎"), new User("紀伊", "花子"));
    }
```
```json
[
    {
        "lastName": "紀伊",
        "firstName": "太郎"
    },
    {
        "lastName": "紀伊",
        "firstName": "花子"
    }
]
```

## ファイルのダウンロード

よく見かけるサンプルではHttpServletResponseに直接書き込んでるものが多い。

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

戻り値を`org.springframework.core.io.Resource`インターフェースにするともっと簡単に実装できる。

```java
    @RequestMapping(value = "/file2", produces = MediaType.APPLICATION_XML_VALUE)
    public Resource file2() {
        return new FileSystemResource("pom.xml");
    }
```
戻り値がResourceだと`Content-Length`は自動的に設定してくれる。

`org.springframework.core.io.Resource`の実装クラスでよく使いそうなものをピックアップしておく。

* FileSystemResource
* ClassPathResource
* ServletContextResource
* ByteArrayResource
* InputStreamResource

# 最後に

ここに書いたのはあくまで自分が知っている範囲の事なので、他にもやり方は色々あると思う。
もっと詳しく知りたい方は[Spring Frameworkのドキュメント](http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/)等を参照してほしい。

また、時間があったらRequestMappingの指定方法やメソッドの引数についてもまとめてみたいと思う。