◆ TURNING · REST

@RestController

View 가 빠진 흐름도

학습 목표

  • @Controller@RestController 의 차이를 안다
  • @ResponseBody 가 어떤 마법을 하는지 이해한다
  • 자바 객체가 JSON 으로 자동 변환되는 과정을 안다
  • REST API 를 만드는 토대를 갖춘다

⚠️ 지금까지의 한계

@Controller 의 흐름

지금까지 우리가 만든 컨트롤러는 다음 패턴이었습니다:


@Controller
public class BoardController {
    @GetMapping("/board/view")
    public String view(@RequestParam int num, Model model) {
        model.addAttribute("board", service.selectOne(num));
        return "board/view";    // → JSP 로 그림
    }
}

결과는 항상 「HTML 페이지」 — JSP 가 그려준 화면.

문제는 — 모바일 앱이 우리 서비스를 쓰려면? JSON 만 받고 싶을 때는? 다른 시스템과 연동하려면?

🛠️ @RestController 의 등장

@RestController = @Controller + @ResponseBody

모든 메서드의 반환값을 JSON 으로 자동 변환해서 응답 바디로 보내는 컨트롤러. View 단계가 흐름에서 빠집니다.

@Controller (기존) Controller → Model → ViewResolver → JSP → HTML 응답 @RestController (REST) Controller → 객체 반환 → Jackson → JSON 응답 (View 없음)

한 글자 차이의 큰 변화 — 기존 @Controller


@Controller
public class BoardController {

    @GetMapping("/board/view")
    public String view(
        @RequestParam int num,
        Model model
    ) {
        Board b = service.selectOne(num);
        model.addAttribute("board", b);
        return "board/view";
        //     ↑ JSP ViewName
    }
}

// → 응답: HTML 페이지 ( /board/view?num=3 )

한 글자 차이의 큰 변화 — 새 @RestController


@RestController
public class BoardApiController {

    @GetMapping("/api/boards/{num}")
    public Board view(
        @PathVariable int num
    ) {
        return service.selectOne(num);
        //     ↑ 객체 그대로 반환
    }
}

// → 응답: JSON

👉 @Controller@RestController, Model 매개변수와 ViewName 반환이 사라지고 도메인 객체를 그대로 반환.

JSON 응답의 모습

클라이언트 GET /api/boards/3 ↓ Controller.view(3) 실행 ↓ service.selectOne(3) 호출 ↓ Board { num: 3, title: "첫 글", content: "안녕하세요", writer: "hong", createdAt: 2024-... } ↓ Jackson 자동 변환 { "num": 3, "title": "첫 글", "content": "안녕하세요", "writer": "hong", "createdAt": "2024-08-15T10:30:00" } ↓ 응답 Content-Type: application/json

Jackson — JSON 자동 변환의 마법사

Jackson 은 자바 진영의 표준 JSON 라이브러리. Spring MVC 의 기본 의존성에 포함되어 있어서 별도 설정 없이 사용 가능합니다.

  • 자바 객체 → JSON 문자열 (Serialize)
  • JSON 문자열 → 자바 객체 (Deserialize)
  • List·Map·중첩 객체도 자동
  • 날짜·시간 형식 등 옵션 가능

@ResponseBody — @RestController 의 진짜 정체


// @RestController 는 사실 다음 두 어노테이션의 합집합
@Controller
@ResponseBody
public class BoardApiController { ... }

// 위는 아래와 동일
@RestController
public class BoardApiController { ... }

👉 @ResponseBody = 「반환값을 ViewName 으로 해석하지 말고, 응답 바디에 직접 넣어라」.

@ResponseBody — 메서드 단위 혼용


// 메서드 단위로도 가능 — 일부만 REST 스타일
@Controller
public class MixedController {

    @GetMapping("/page")
    public String page() { return "page"; }   // JSP

    @GetMapping("/api/data")
    @ResponseBody
    public Data data() { return service.get(); }  // JSON
}

👉 한 컨트롤러 안에서도 메서드별로 화면용·API 용을 섞어 쓸 수 있습니다.

JSON 받기 — @RequestBody


@PostMapping("/api/boards")
public Board create(@RequestBody Board b) {
    //               ↑ JSON → 자바 객체 자동 변환
    return service.insert(b);
}

👉 @RequestBody 는 요청 바디의 JSON 을 자바 객체로 거꾸로 변환합니다. @ResponseBody 의 정확한 짝.

@RequestBody — 변환 흐름

클라이언트 fetch: fetch('/api/boards', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ title: '새 글', content: '안녕하세요' }) }); 요청 바디 (HTTP): { "title": "새 글", "content": "안녕하세요" } ↓ Jackson 거꾸로 변환 매개변수: Board b { title = "새 글", content = "안녕하세요", num = 0, writer = null (안 보낸 필드는 기본값) }

@RequestBody 의 필수 조건

클라이언트 측
  • 요청 헤더에 Content-Type: application/json
  • 요청 바디에 JSON 형식 문자열
  • 일반 form 제출은 @RequestBody 매핑 안 됨 (form encoded ↔ JSON 다름)
서버 측
  • VO 에 기본 생성자 필요 (Jackson 이 새 객체를 만들기 위해)
  • setter 또는 Lombok @Data 필요
  • JSON 의 키 이름과 자바 필드 이름이 일치 (Jackson 자동 매핑)

응답·요청 정리

방향어노테이션변환
서버 → 클라이언트
(응답)
@ResponseBody
(또는 @RestController 자동)
자바 객체 → JSON
클라이언트 → 서버
(요청 바디)
@RequestBodyJSON → 자바 객체

👉 두 어노테이션이 짝. @RestController 안에서 매개변수에 @RequestBody 만 따로 붙이면 됨.

전체 컨트롤러 예시 — 조회


@RestController
@RequestMapping("/api/boards")
public class BoardApiController {

    @Autowired
    private BoardService service;

    @GetMapping
    public List<Board> list() {            // JSON 배열 응답
        return service.selectList();
    }

    @GetMapping("/{num}")
    public Board view(@PathVariable int num) {  // JSON 객체 응답
        return service.selectOne(num);
    }
}

👉 GET 두 개. 반환 타입이 곧 JSON 모양 (배열 ↔ 객체).

전체 컨트롤러 예시 — 등록·수정·삭제


    @PostMapping
    public Board create(@RequestBody Board b) {  // JSON 받음
        service.insert(b);
        return b;                                 // JSON 응답
    }

    @PutMapping("/{num}")
    public Board update(@PathVariable int num,
                        @RequestBody Board b) {
        b.setNum(num);
        return service.update(b);
    }

    @DeleteMapping("/{num}")
    public void delete(@PathVariable int num) {
        service.delete(num);
    }
}

👉 변경 메서드 3종. @RequestBody 로 JSON 을 받아서 객체로 응답.

흐름도 — View 가 사라진다

@Controller 흐름 ───────────────── 요청 ↓ Controller ↓ Service 호출 ↓ Service 결과를 Model 에 담기 ↓ return ViewName ↓ ViewResolver → JSP → HTML ↓ 응답 (HTML) @RestController 흐름 ────────────────── 요청 ↓ Controller ↓ Service 호출 ↓ return 객체 ↓ Jackson → JSON ↓ 응답 (JSON) ※ Model · ViewResolver · JSP 가 흐름에서 빠짐

브라우저로 테스트

  1. Tomcat 시작
  2. 크롬 주소창: http://localhost:8080/.../api/boards
  3. 화면에 JSON 배열 그대로 출력
    
    [{"num":1,"title":"첫 글",...}, {"num":2,...}]
    
  4. F12 → Network → 응답 헤더 확인:
    Content-Type: application/json;charset=UTF-8

POST 테스트는 어떻게?

브라우저 주소창은 GET 만 가능. POST 테스트는 다음 방법:

  • F12 Console 에서 fetch:
    fetch('/api/boards', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({title: '테스트', content: '안녕'})
    }).then(r => r.json()).then(console.log);
  • Postman — REST API 테스트 표준 도구
  • curl 명령:
    curl -X POST -H "Content-Type: application/json" \
      -d '{"title":"테스트","content":"안녕"}' \
      http://localhost:8080/.../api/boards

@Controller 와 @RestController 공존


// 사람 보기 — JSP 화면
@Controller
@RequestMapping("/board")
public class BoardController {
    @GetMapping("/list")
    public String list(Model m) {
        m.addAttribute("boards", service.selectList());
        return "board/list";
    }
}

// 다른 시스템 / 모바일 앱이 사용 — JSON
@RestController
@RequestMapping("/api/boards")
public class BoardApiController {
    @GetMapping
    public List<Board> list() {
        return service.selectList();
    }
}

👉 같은 Service 를 두 컨트롤러가 호출. 데이터는 같고, 응답 형식만 다름.

Jackson 의 자동 변환 규칙

자바 타입JSON 표현
String"문자열"
int / long / double숫자
booleantrue / false
nullnull
List · 배열[ ... ]
Map · 객체{ "key": value }
LocalDateTime"2024-08-15T10:30:00" (ISO 8601)

👉 옵션으로 형식 변경 가능. 본 과정은 기본값으로 충분.

주의 — Jackson 함정

민감 정보 노출

VO 에 password 필드가 있고 그대로 JSON 변환 → 비밀번호 응답에 포함됨!


public class Member {
    private String id;
    @JsonIgnore     // ⭐ 이 필드는 JSON 에 포함 안 함
    private String pwd;
    private String nick;
}
순환 참조

Board 가 Member 를 참조하고, Member 가 Board 의 List 를 참조하면 → 무한 루프 → StackOverflow.
해결: @JsonManagedReference + @JsonBackReference 또는 DTO 분리.

RESTful URL 디자인

동작전통 (옛 스타일)RESTful
목록GET /board/listGET /api/boards
상세GET /board/view?num=3GET /api/boards/3
작성POST /board/writePOST /api/boards
수정POST /board/updatePUT /api/boards/3
삭제POST /board/deleteDELETE /api/boards/3

👉 RESTful = 「리소스(boards)」 를 URL 로, 「동작」 을 HTTP 메서드로. 다음 차시에서 자세히.

실험 — 직접 JSON 응답 보기

  1. 기존 BoardController 를 BoardApiController 로 복사
  2. @Controller → @RestController 변경
  3. @RequestMapping("/board") → @RequestMapping("/api/boards")
  4. return ViewName → return 객체 (Model 사용 X)
  5. 브라우저에서 새 URL 접속 — JSON 응답 확인
  6. F12 Network 의 Response 탭에서 정확한 JSON 모양 확인

다음 차시 미리보기

  • HTTP 메서드 어노테이션 — Get/Post/Put/Delete Mapping 자세히
  • ★ v8 REST 컨트롤러로 변환 — v6 게시판을 통째로 REST 로
  • ★ v9 비동기 댓글 — JS 가 우리 REST API 를 fetch 로 호출
  • ★ v10 자잘한 비동기 + 첨부파일
  • ★ v∞ 디버깅 워크숍 — 4 주 학습의 마무리

🔄 Before / After

전 차시 끝

Ajax / JSON 의 개념은 알았다. 서버 쪽 구현은 막연.

이번 차시 끝

@RestController 한 줄로 JSON API 를 만들 수 있다. 응답·요청 모두 JSON 자동 변환.

📊 한 그림 정리

이번 차시의 데이터 흐름

클라이언트
@RestController
Service
DB
Jackson
JSON 변환
JSON 응답
View · ViewResolver 가 사라지고 Jackson 박스가 자리 — REST 의 본질

정리

오늘 들고 가는 것

  • @RestController = @Controller + @ResponseBody
  • 객체 반환 → Jackson 이 자동 JSON 변환
  • @RequestBody = JSON → 자바 객체 자동 변환
  • View · ViewResolver · JSP 가 흐름에서 빠짐
  • 같은 Service 를 두 컨트롤러가 공유 가능

다음: HTTP 메서드 어노테이션 — RESTful URL 디자인.