Spring MVC Controller와 Restful Web Service Controller의 가장 큰 차이점은 HTTP Response Body가 생성되는 방식입니다.
기존 MVC Controller는 View를 활용하여 주로 view(화면)을 return 합니다.
하지만 Restful 웹 서비스 Controller는 객체를 반환하기만 하면 객체 데이터는 JSON/XML 형식의 HTTP 응답을 직접 작성하게 됩니다.
아래는 대표적인 Controller의 예시이다.
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/hi/*")
public class Controller {
@RequestMapping("/hello")
public String Hello(){
return "hello";
}
}
아래는 Controller를 통한 view (hello.jsp)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
</head>
<body>
hello world
</body>
</html>
간략한 Spring MVC의 흐름은 다음과 같습니다.
1. Client는 URI 형식으로 web service에 요청(request)를 보냅니다.
2. 요청(request)는 Handler Mapping과 그 type을 찾는 DispatcherServlet에 의해 인터셉트 됩니다.
3. 요청(request)는 Controller에 의해 처리되고 응답은 DispatcherServlet으로 리턴된 후 DispatcherServlet은 View로 디스패치 됩니다.
Spring MVC WorkFlow는 모델앤 뷰(ModelAndView) 객체가 Controller에서 Client로 전달되는 것을 알 수 있습니다.
단,@ResponseBody 어노테이션을 사용하면 View를 return하지 않고 Controller에서 직접 Data를 return할 수 있습니다.
(Spring 4.0 version 부터는 @RestController 어노테이션을 통해 더 쉽게 사용할 수 있습니다.)
아래는 간략한 RestController 이다.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.spring02.model.board.dto.BoardVO;
@RestController
@RequestMapping("/basic/*")
public class ControllerRest {
// json객체 리턴
@RequestMapping("/sendVO2")
public BoardVO sendVO2(){
BoardVO vo = new BoardVO();
vo.setBno(1);
vo.setWriter("DoubleS");
vo.setContent("게시글 내용입니다");
vo.setRecnt(1);
vo.setTitle("게시글 1");
vo.setUserName("DoubleS");
return vo;
}
// json 객체 배열 리턴
@RequestMapping("/sendList")
public List<BoardVO> sendList(){
// ArrayList 객체 생성
List<BoardVO> items = new ArrayList<>();
for(int i=1; i <=10; i++){
BoardVO vo = new BoardVO(); //vo 객체 생성
vo.setBno(i);
vo.setWriter("DoubleS"+i);
vo.setContent("게시글 내용입니다"+i);
vo.setRecnt(i);
vo.setTitle("게시글"+i);
vo.setUserName("DoubleS"+i);
items.add(vo); // 리스트에 vo추가
}
return items; // 리스트를 리턴함
}
// json객체를 map에 저장하여
@RequestMapping("/sendMap")
public Map<Integer, BoardVO> sendMap(){
// Map<Key자료형, Value자료형>
Map<Integer, BoardVO> map = new HashMap<Integer, BoardVO>();
for(int i=1; i <=10; i++){
BoardVO vo = new BoardVO(); //vo 객체 생성
vo.setBno(i);
vo.setWriter("DoubleS"+i);
vo.setContent("게시글 내용입니다"+i);
vo.setRecnt(i);
vo.setTitle("게시글"+i);
vo.setUserName("DoubleS"+i);
map.put(i, vo); // 맵에 vo추가
}
return map;
}
}
Spring4.0에서는 @RestController 어노테이션을 선언해주면
컨트롤러 클래스의 각 메서드마다 @ResponseBody을 추가할 필요가 없어졌고, 모든 메서드는 @ResponseBody 애노테이션이 기본으로 작동이 됩니다.
RestController는 별도의 View를 제공하지 않는 형태로 서비스를 실행하기 때문에, 때로는 결과데이터가 예외적인 상황에서 문제가 발생할 수 있습니다.
ResponseEntity는 개발자가 직접 결과 데이터와 HTTP 상태 코드를 직접 제어할 수 있는 클래스로 개발자는 404나 500같은 HTTP 상태 코드를 전송하고 싶은 데이터와 함께 전송할수 있기 때문에 좀더 세밀한 제어가 필요한 경우 사용할 수 있습니다.
아래는 ResponseEntity의 예시입니다.
@GetMapping("/hello")
ResponseEntity<String> hello() {
return new ResponseEntity<>("Hello World!", HttpStatus.OK);
}
@GetMapping("/age")
ResponseEntity<String> age(
@RequestParam("yearOfBirth") int yearOfBirth) {
if (isInFuture(yearOfBirth)) {
return new ResponseEntity<>(
"Year of birth cannot be in the future",
HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(
"Your age is " + calculateAge(yearOfBirth),
HttpStatus.OK);
}
또한 사용자 설정의 HTTP 헤더를 설정할 수 있습니다.
@GetMapping("/customHeader")
ResponseEntity<String> customHeader() {
HttpHeaders headers = new HttpHeaders();
headers.add("Custom-Header", "foo");
return new ResponseEntity<>(
"Custom header set", headers, HttpStatus.OK);
}
또한 BodyBuilder 상태(HttpStatus 상태) 및 BodyBuilder 상태(int 상태) 메서드를 사용하여 HTTP 상태를 설정할 수 있습니다.
마지막으로 ResponseEntity BodyBuilder.body(T body) 를 사용하여 HTTP 응답 본문을 설정할 수 있습니다.
@GetMapping("/age")
ResponseEntity<String> age(@RequestParam("yearOfBirth") int yearOfBirth) {
if (isInFuture(yearOfBirth)) {
return ResponseEntity.badRequest()
.body("Year of birth cannot be in the future");
}
return ResponseEntity.status(HttpStatus.OK)
.body("Your age is " + calculateAge(yearOfBirth));
}
@GetMapping("/customHeader")
ResponseEntity<String> customHeader() {
return ResponseEntity.ok()
.header("Custom-Header", "foo")
.body("Custom header set");
}
기존 엔티티를 파라미터로 받아서 사용하기엔 많은 부담이 있을 수 있다. 적합한 form의 DTO를 만들어서 사용하는것이 올바른 방법일것이다.
또한 엔티티가 화면 종속적인 코드들로 증가되서는 안된다. 최대한 엔티티가 순수하게 유지될 수 있게, 핵심 비지니스 로직에만 종속되게 만들어야 유지보수성을 높일 수 있다. 또한 API 환경에서 API 스펙에 엔티티가 추가되면 많은 문제를 초래할 수 있기에 각 화면에 맞는 DTO를 생성하여 개발하는것을 권장한다.
https://2ham-s.tistory.com/279
https://www.baeldung.com/spring-response-entity