View 가 빠진 흐름도
지금까지 우리가 만든 컨트롤러는 다음 패턴이었습니다:
@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 만 받고 싶을 때는? 다른 시스템과 연동하려면?
모든 메서드의 반환값을 JSON 으로 자동 변환해서 응답 바디로 보내는 컨트롤러. View 단계가 흐름에서 빠집니다.
@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
public class BoardApiController {
@GetMapping("/api/boards/{num}")
public Board view(
@PathVariable int num
) {
return service.selectOne(num);
// ↑ 객체 그대로 반환
}
}
// → 응답: JSON
👉 @Controller → @RestController, Model 매개변수와 ViewName 반환이 사라지고 도메인 객체를 그대로 반환.
Jackson 은 자바 진영의 표준 JSON 라이브러리. Spring MVC 의 기본 의존성에 포함되어 있어서 별도 설정 없이 사용 가능합니다.
// @RestController 는 사실 다음 두 어노테이션의 합집합
@Controller
@ResponseBody
public class BoardApiController { ... }
// 위는 아래와 동일
@RestController
public class BoardApiController { ... }
👉 @ResponseBody = 「반환값을 ViewName 으로 해석하지 말고, 응답 바디에 직접 넣어라」.
// 메서드 단위로도 가능 — 일부만 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 용을 섞어 쓸 수 있습니다.
@PostMapping("/api/boards")
public Board create(@RequestBody Board b) {
// ↑ JSON → 자바 객체 자동 변환
return service.insert(b);
}
👉 @RequestBody 는 요청 바디의 JSON 을 자바 객체로 거꾸로 변환합니다. @ResponseBody 의 정확한 짝.
Content-Type: application/json@RequestBody 매핑 안 됨 (form encoded ↔ JSON 다름)| 방향 | 어노테이션 | 변환 |
|---|---|---|
| 서버 → 클라이언트 (응답) | @ResponseBody(또는 @RestController 자동) | 자바 객체 → JSON |
| 클라이언트 → 서버 (요청 바디) | @RequestBody | JSON → 자바 객체 |
👉 두 어노테이션이 짝. @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 을 받아서 객체로 응답.
http://localhost:8080/.../api/boards
[{"num":1,"title":"첫 글",...}, {"num":2,...}]
Content-Type: application/json;charset=UTF-8브라우저 주소창은 GET 만 가능. POST 테스트는 다음 방법:
fetch('/api/boards', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({title: '테스트', content: '안녕'})
}).then(r => r.json()).then(console.log);curl -X POST -H "Content-Type: application/json" \
-d '{"title":"테스트","content":"안녕"}' \
http://localhost:8080/.../api/boards
// 사람 보기 — 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 를 두 컨트롤러가 호출. 데이터는 같고, 응답 형식만 다름.
| 자바 타입 | JSON 표현 |
|---|---|
| String | "문자열" |
| int / long / double | 숫자 |
| boolean | true / false |
| null | null |
| List · 배열 | [ ... ] |
| Map · 객체 | { "key": value } |
| LocalDateTime | "2024-08-15T10:30:00" (ISO 8601) |
👉 옵션으로 형식 변경 가능. 본 과정은 기본값으로 충분.
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 |
|---|---|---|
| 목록 | GET /board/list | GET /api/boards |
| 상세 | GET /board/view?num=3 | GET /api/boards/3 |
| 작성 | POST /board/write | POST /api/boards |
| 수정 | POST /board/update | PUT /api/boards/3 |
| 삭제 | POST /board/delete | DELETE /api/boards/3 |
👉 RESTful = 「리소스(boards)」 를 URL 로, 「동작」 을 HTTP 메서드로. 다음 차시에서 자세히.
Ajax / JSON 의 개념은 알았다. 서버 쪽 구현은 막연.
@RestController 한 줄로 JSON API 를 만들 수 있다. 응답·요청 모두 JSON 자동 변환.
다음: HTTP 메서드 어노테이션 — RESTful URL 디자인.