프로그래밍 공부/Spring

[Spring] @Controller와 @RestController

 String에서 컨트롤러를 지정하기 위한 어노테이션으로는 @Controller와 @RestController가 있다. 

기존 Spring MVC의 컨트롤러인 @Controller가 존재하는데도 왜 @RestController를 새로 만들어서 사용하고 있는 것인지에 대해서 정리해보고자 한다.

 

MVC 패턴이란?

먼저, Controller라는 개념이 등장한 MVC 패턴에 대해서 알아보자.

 

MVC 패턴은 Model - View - Controller의 약자로, 각 세가지 형태로 역할을 나누어 개발하는 방법론의 일환이다.

간단하게 설명하면, 각 형태는 다음과 같은 역할을 갖는다. 

  • View : 사용자에게 시각적으로 보여주는 부분으로, 사용자와 상호작용을 하는 부분
  • Model : 해당 애플리케이션이 처리하는 데이터를 다루는 부분
  • Controller : Model과 View를 연결하는 부분으로써, model에게는 데이터를 어떻게 처리할지, view에게는 어떠한 정보를 보여줄지 전달하는 역할.

이러한 MVC 패턴을 사용하는 이유는, Model, View, Controller로 각자의 역할에 집중하여 애플리케이션을 만들게되면 유지보수성이나 확장성, 유연성을 높일 수 있기 때문이다.

 

Controller는 왜 사용할까?

엄청나게 큰 대규모 서비스를 사용한다고 생각해보자. 그때 우리는 배달 서비스, 결제 서비스, 인증 서비스, 검색 서비스, 기타 등등의 굉장히 많은 서비스를 다루고, 또 유지보수를 진행해야 될 것이다.

 

그렇다면, 그렇게 많은 서비스를 한 클래스 내에서 다루는 것이 효과적인 방법은 아닐 것이다.

 

그렇기에, Controller라는 중간 제어자를 만들어 각각의 서비스에 맞는 역할에 따라 설계를 하고 애플리케이션을 만들게되면 개발이나 유지보수의 과정에 있어서 보다 효율적으로 사용할 수 있게 되는 것이다.

 

 

 

@Controller

Controller로 View 반환하기

출처 https://mangkyu.tistory.com/49

전통적인 Spring MVC의 컨트롤러인 @Controller는 주로 View를 반환하기 위해 사용한다. 이때 Model이나 ModelAndView를 return하는 방식을 사용하며, 전체적인 흐름은 다음과 같이 진행된다.

 

  1. Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
  2. DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.
  3. HandlerMapping을 통해 요청을 Controller로 위임한다.
  4. Controller는 요청을 처리한 후에 ViewName을 반환한다.
  5. DispatcherServlet은 ViewResolver를 통해 ViewName에 해당하는 View를 찾아 사용자에게 반환한다.

코드 예시

@Controller
@RequiredArgsConstructor
public class CrewController {
    private final CrewService crewService;

    @GetMapping(value = "/crew/detailView")
    public String detailView(Model model, @RequestParam("crewName") String crewName) {
        Crew crew = crewService.findCrew(crewName);
        model.addAttribute("crew", crew);
        return "/crew/detailView";
    }
}

 

 

그렇지만, ThymeLeaf와 같은 뷰 템플릿 엔진을 사용하지 않는 이상, 현재 대부분의 스프링 사용자들은 spring에서 View를 반환하기보다는 데이터를 주고받는데 사용할 것이다. 해당 방식에 대해서도 알아보자.

 

Controller로 Data 반환하기

출처 https://mangkyu.tistory.com/49

전체적인 흐름은 다음과 같다.

  1. Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
  2. DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.
  3. HandlerMapping을 통해 요청을 Controller로 위임한다.
  4. Controller는 요청을 처리한 후에 객체를 반환한다.
  5. 반환되는 객체는 Json으로 Serialize되어 사용자에게 반환된다.

이때 위에서는 viewResolver가 사용된 것 과는 달리, HttpMessageConverter가 동작된 것을 볼 수 있다. Spring에서는 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보를 조합하여 반환해야 할 데이터에 따라 작동하는 MessageConverter를 선택한다.

 

또한, 해당 body 내용을 매핑하기 위해 사용되는 어노테이션이 @ResponseBody이다.

@ResponseBody : 자바 객체를 HTTP 요청의 body 내용으로 매핑하는 역할

 

 

코드 예시

@Controller
@RequiredArgsConstructor
public class CrewController {
    private final CrewService crewService;

    @GetMapping(value = "/crew")
    public @ResponseBody ResponseEntity<Crew> findCrew(@RequestParam String crewName) {
        return ResponseEntity.ok(crewService.findCrew(crewName));
    }
}

 

@RestController

위에서 Controller로 Data변환하기의 예시를 다시 보자. 

 

@Controller
@RequiredArgsConstructor
public class CrewController {
    private final CrewService crewService;

    @GetMapping(value = "/crew")
    public @ResponseBody ResponseEntity<Crew> findCrew(@RequestParam String crewName) {
        return ResponseEntity.ok(crewService.findCrew(crewName));
    }
}

해당 예시에서는 @Controller에 @ResponseBody가 추가되어서 Json 형태로 객체 데이터를 반환하게 된다.

 

즉, @RestController는 해당 두 어노테이션, @Controller와 @ResponseBody가 합쳐진 것으로 이해하면 된다. 이로써 개발자들은 모든 메서드마다 @ResponseBody를 추가할 필요가 없어졌다! (동작 방식 역시 동일하다.)

 

@RestController
@RequiredArgsConstructor
public class CrewController {
    private final CrewService crewService;
    
    @GetMapping(value = "crew")
    public Crew findCrew(@RequestParam String crewName){
    	return crewService.findCrew(crewName);
    }

    @GetMapping(value = "/crew")
    public ResponseEntity<Crew> findCrewWithResponseEntity(@RequestParam String crewName) {
        return ResponseEntity.ok(crewService.findCrew(crewName));
    }
}

 

위의 findCrew메서드는 User객체를 그대로 반환하지만, HttpStatus(200, 404와 같은 상태)를 설정해줄 수 없다. 그렇기 때문에, ResponseEntity를 통해서 해당 HttpStatus를 설정한다고 한다. (더 다양한 방식이 존재하는 것으로 알고 있어서, 해당 부분은 더 공부하면 포스팅해보는 것으로...!)