[Spring MVC] 동기 비동기 / @Controller @RestController

Meow.paw·2023년 2월 6일
1

동기 (Synchronous)와 비동기(Asynchronous)

  • 동기 방식은 서버에서 요청을 보냈을 때 응답이 돌아와야 다음 동작을 수행할 수 있다. 즉 A작업이 모두 진행 될때까지 B작업은 대기해야한다.
  • 비동기 방식은 반대로 요청을 보냈을 때 응답 상태와 상관없이 다음 동작을 수행 할 수 있다. 즉 A작업이 시작하면 동시에 B작업이 실행된다. A작업은 결과값이 나오는대로 출력된다.

동기적 처리 (Synchronous)

위와 같은 코스를 작성하고 실행해 보면 예상되는 결과는 1st, 2nd, 3rd일 것이다.

예상했던 것처럼 1st, 2nd, 3rd가 차례대로 찍히는 것을 알 수 있다.

이처럼 코드가 위에서부터 아래로 내려오면서 하나가 끝나면 다음 코드가 실행되는 방식을 동기적 처리 (Synchronous)라고 한다.

비동기적 처리(Asynchronous)

비동기적 처리는 어떤것을 의미하는지 코드로 직접 보면

setTimeout() 메소드를 사용했다.

setTimeout()메소드의 첫번째 인자는 콜백함수를 사용하였고, 두번째 인자는 지연시간이다.

두번째 인자가 0이기 때문에 바로 실행될거라 예상하고,

1st, 2nd, 3rd가 찍힐것이라 예상하였다.

하지만 결과값은 1st, 3rd, 2nd가 찍혔다.

이는 setTimeout()메소드가 비동기적 API이기 때문이다.

위의 코드를 컴퓨터의 입장에서 해석해보면 다음과 같다.

  • 첫번째 줄에서 console.log("1st");를 만나고 콘솔에 1st를 찍는다.
  • 두번째 줄에서 setTimeout() 메소드를 만나서 해당 매소드가 비동기적 매소드이기 때문에 이를 처리하는 다른 프로그램에 맡긴다.
  • 그러고 나서 곧바로 console.log("3rd")를 콘솔에 찍는다.
  • setTimeOut() 메소드를 처리하는 프로그램은 비동기적 API를 제외한 모든 코드가 실행 된 이후결과를 콘솔에 찍는다.

비동기적 코드의 실행 결과는 동기적 코드가 전부 실행 되고나서 값을 반환한다.

동기는 디자인이 비동기보다 간단하고 직관적일수 있지만 결과가 주어질 때 까지 아무것도 못하고 대기해야하는 문제가 있다. 비동기는 동기보다 복잡하지만 결과가 주어지는데 시간이 걸려도 그 시간동안 다른 작업을 할 수 있어서 보다 효율적일 수 있다.

1. @Controller 이해하기

[ Controller로 View 반환하기 ]

전통적인 Spring MVC의 컨트롤러인 @Controller는 주로 View를 반환하기 위해 사용합니다. 아래와 같은 과정을 통해 Spring MVC Container는 Client의 요청으로부터 View를 반환합니다.Client는 URI 형식으로 웹 서비스에 요청을 보낸다.DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.HandlerMapping을 통해 요청을 Controller로 위임한다.Controller는 요청을 처리한 후에 ViewName을 반환한다.DispatcherServlet은 ViewResolver를 통해 ViewName에 해당하는 View를 찾아 사용자에게 반환한다.
Controller가 반환환 뷰의 이름으로부터 View를 렌더링하기 위해서는 ViewResolver가 사용되며, ViewResolver 설정에 맞게 View를 찾아 렌더링합니다.[ Controller로 Data 반환하기 ]하지만 Spring MVC의 컨트롤러를 사용하면서 Data를 반환해야 하는 경우도 있습니다. 컨트롤러에서는 데이터를 반환하기 위해 @ResponseBody 어노테이션을 활용해주어야 합니다. 이를 통해 Controller도 Json 형태로 데이터를 반환할 수 있습니다.Client는 URI 형식으로 웹 서비스에 요청을 보낸다.DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.HandlerMapping을 통해 요청을 Controller로 위임한다.Controller는 요청을 처리한 후에 객체를 반환한다.반환되는 객체는 Json으로 Serialize되어 사용자에게 반환된다.컨트롤러를 통해 객체를 반환할 때에는 일반적으로 ResponseEntity로 감싸서 반환을 합니다. 그리고 객체를 반환하기 위해서는 viewResolver 대신에 HttpMessageConverter가 동작합니다. HttpMessageConverter에는 여러 Converter가 등록되어 있고, 반환해야 하는 데이터에 따라 사용되는 Converter가 달라집니다. 단순 문자열인 경우에는 StringHttpMessageConverter가 사용되고, 객체인 경우에는 MappingJackson2HttpMessageConverter가 사용되며, 데이터 종류에 따라 서로 다른 MessageConverter가 작동하게 됩니다. Spring은 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해 적합한 HttpMessageConverter를 선택하여 이를 처리합니다. MessageConverter가 동작하는 시점은 HandlerAdapter와 Controller가 요청을 주고 받는 시점이다. 그림의 4번에서는 메세지를 객체로, 6번에서는 객체를 메세지로 변환하는데 메세지 컨버터가 사용된다.

[ @Controller 예제 코드 ]

@Controller
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping(value = "/users")
    public @ResponseBody ResponseEntity<User> findUser(@RequestParam("userName") String userName){
        return ResponseEntity.ok(userService.findUser(user));
    }

    @GetMapping(value = "/users/detailView")
    public String detailView(Model model, @RequestParam("userName") String userName){
        User user = userService.findUser(userName);
        model.addAttribute("user", user);
        return "/users/detailView";
    }
}

위 예제의 findUser는 User 객체를 ResponseEntity로 감싸서 반환하고 있고, User를 json으로 반환하기 위해 @ResponseBody라는 어노테이션을 붙여주고 있습니다. detailView 함수에서는 View를 전달해주고 있기 때문에 String을 반환값으로 설정해주었습니다. (물론 이렇게 데이터를 반환하는 RestController와 View를 반환하는 Controller를 분리하여 작성하는 것이 좋습니다.)

2. @RestController 이해하기

[ RestController ]

@RestController는 @Controller에 @ResponseBody가 추가된 것입니다. 당연하게도 RestController의 주용도는 Json 형태로 객체 데이터를 반환하는 것입니다. 최근에 데이터를 응답으로 제공하는 REST API를 개발할 때 주로 사용하며 객체를 ResponseEntity로 감싸서 반환합니다.

이러한 이유로 동작 과정 역시 @Controller에 @ReponseBody를 붙인 것과 완벽히 동일합니다.Client는 URI 형식으로 웹 서비스에 요청을 보낸다.DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.HandlerMapping을 통해 요청을 Controller로 위임한다.Controller는 요청을 처리한 후에 객체를 반환한다.반환되는 객체는 Json으로 Serialize되어 사용자에게 반환된다.

[ @RestController 예제 코드 ]

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping(value = "/users")
    public User findUser(@RequestParam("userName") String userName){
        return userService.findUser(user);
    }

    @GetMapping(value = "/users")
    public ResponseEntity<User> findUserWithResponseEntity(@RequestParam("userName") String userName){
        return ResponseEntity.ok(userService.findUser(user));
    }
}

findUser는 User 객체를 그대로 반환하고 있습니다. 이러한 경우의 문제는 클라이언트가 예상하는 HttpStatus를 설정해줄 수 없다는 것입니다. 예를 들어 어떤 객체의 생성 요청이라면 201 CREATED를 기대할 것이지만 객체를 그대로 반환하면 HttpStatus를 설정해줄 수 없습니다. 그래서 객체를 상황에 맞는 ResponseEntity로 감싸서 반환해주어야 합니다

동기 (Synchronous)와 비동기(Asynchronous)

  • 동기 방식은 서버에서 요청을 보냈을 때 응답이 돌아와야 다음 동작을 수행할 수 있다. 즉 A작업이 모두 진행 될때까지 B작업은 대기해야한다.
  • 비동기 방식은 반대로 요청을 보냈을 때 응답 상태와 상관없이 다음 동작을 수행 할 수 있다. 즉 A작업이 시작하면 동시에 B작업이 실행된다. A작업은 결과값이 나오는대로 출력된다.

동기적 처리 (Synchronous)

위와 같은 코스를 작성하고 실행해 보면 예상되는 결과는 1st, 2nd, 3rd일 것이다.

예상했던 것처럼 1st, 2nd, 3rd가 차례대로 찍히는 것을 알 수 있다.

이처럼 코드가 위에서부터 아래로 내려오면서 하나가 끝나면 다음 코드가 실행되는 방식을 동기적 처리 (Synchronous)라고 한다.

비동기적 처리(Asynchronous)

비동기적 처리는 어떤것을 의미하는지 코드로 직접 보면

setTimeout() 메소드를 사용했다.

setTimeout()메소드의 첫번째 인자는 콜백함수를 사용하였고, 두번째 인자는 지연시간이다.

두번째 인자가 0이기 때문에 바로 실행될거라 예상하고,

1st, 2nd, 3rd가 찍힐것이라 예상하였다.

하지만 결과값은 1st, 3rd, 2nd가 찍혔다.

이는 setTimeout()메소드가 비동기적 API이기 때문이다.

위의 코드를 컴퓨터의 입장에서 해석해보면 다음과 같다.

  • 첫번째 줄에서 console.log("1st");를 만나고 콘솔에 1st를 찍는다.
  • 두번째 줄에서 setTimeout() 메소드를 만나서 해당 매소드가 비동기적 매소드이기 때문에 이를 처리하는 다른 프로그램에 맡긴다.
  • 그러고 나서 곧바로 console.log("3rd")를 콘솔에 찍는다.
  • setTimeOut() 메소드를 처리하는 프로그램은 비동기적 API를 제외한 모든 코드가 실행 된 이후결과를 콘솔에 찍는다.

비동기적 코드의 실행 결과는 동기적 코드가 전부 실행 되고나서 값을 반환한다.

동기는 디자인이 비동기보다 간단하고 직관적일수 있지만 결과가 주어질 때 까지 아무것도 못하고 대기해야하는 문제가 있다. 비동기는 동기보다 복잡하지만 결과가 주어지는데 시간이 걸려도 그 시간동안 다른 작업을 할 수 있어서 보다 효율적일 수 있다.

1. @Controller 이해하기

[ Controller로 View 반환하기 ]

전통적인 Spring MVC의 컨트롤러인 @Controller는 주로 View를 반환하기 위해 사용합니다. 아래와 같은 과정을 통해 Spring MVC Container는 Client의 요청으로부터 View를 반환합니다.Client는 URI 형식으로 웹 서비스에 요청을 보낸다.DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.HandlerMapping을 통해 요청을 Controller로 위임한다.Controller는 요청을 처리한 후에 ViewName을 반환한다.DispatcherServlet은 ViewResolver를 통해 ViewName에 해당하는 View를 찾아 사용자에게 반환한다.
Controller가 반환환 뷰의 이름으로부터 View를 렌더링하기 위해서는 ViewResolver가 사용되며, ViewResolver 설정에 맞게 View를 찾아 렌더링합니다.[ Controller로 Data 반환하기 ]하지만 Spring MVC의 컨트롤러를 사용하면서 Data를 반환해야 하는 경우도 있습니다. 컨트롤러에서는 데이터를 반환하기 위해 @ResponseBody 어노테이션을 활용해주어야 합니다. 이를 통해 Controller도 Json 형태로 데이터를 반환할 수 있습니다.Client는 URI 형식으로 웹 서비스에 요청을 보낸다.DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.HandlerMapping을 통해 요청을 Controller로 위임한다.Controller는 요청을 처리한 후에 객체를 반환한다.반환되는 객체는 Json으로 Serialize되어 사용자에게 반환된다.컨트롤러를 통해 객체를 반환할 때에는 일반적으로 ResponseEntity로 감싸서 반환을 합니다. 그리고 객체를 반환하기 위해서는 viewResolver 대신에 HttpMessageConverter가 동작합니다. HttpMessageConverter에는 여러 Converter가 등록되어 있고, 반환해야 하는 데이터에 따라 사용되는 Converter가 달라집니다. 단순 문자열인 경우에는 StringHttpMessageConverter가 사용되고, 객체인 경우에는 MappingJackson2HttpMessageConverter가 사용되며, 데이터 종류에 따라 서로 다른 MessageConverter가 작동하게 됩니다. Spring은 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해 적합한 HttpMessageConverter를 선택하여 이를 처리합니다. MessageConverter가 동작하는 시점은 HandlerAdapter와 Controller가 요청을 주고 받는 시점이다. 그림의 4번에서는 메세지를 객체로, 6번에서는 객체를 메세지로 변환하는데 메세지 컨버터가 사용된다.

[ @Controller 예제 코드 ]

@Controller
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping(value = "/users")
    public @ResponseBody ResponseEntity<User> findUser(@RequestParam("userName") String userName){
        return ResponseEntity.ok(userService.findUser(user));
    }

    @GetMapping(value = "/users/detailView")
    public String detailView(Model model, @RequestParam("userName") String userName){
        User user = userService.findUser(userName);
        model.addAttribute("user", user);
        return "/users/detailView";
    }
}

위 예제의 findUser는 User 객체를 ResponseEntity로 감싸서 반환하고 있고, User를 json으로 반환하기 위해 @ResponseBody라는 어노테이션을 붙여주고 있습니다. detailView 함수에서는 View를 전달해주고 있기 때문에 String을 반환값으로 설정해주었습니다. (물론 이렇게 데이터를 반환하는 RestController와 View를 반환하는 Controller를 분리하여 작성하는 것이 좋습니다.)

2. @RestController 이해하기

[ RestController ]

@RestController는 @Controller에 @ResponseBody가 추가된 것입니다. 당연하게도 RestController의 주용도는 Json 형태로 객체 데이터를 반환하는 것입니다. 최근에 데이터를 응답으로 제공하는 REST API를 개발할 때 주로 사용하며 객체를 ResponseEntity로 감싸서 반환합니다.

이러한 이유로 동작 과정 역시 @Controller에 @ReponseBody를 붙인 것과 완벽히 동일합니다.Client는 URI 형식으로 웹 서비스에 요청을 보낸다.DispatcherServlet이 요청을 위임할 HandlerMapping을 찾는다.HandlerMapping을 통해 요청을 Controller로 위임한다.Controller는 요청을 처리한 후에 객체를 반환한다.반환되는 객체는 Json으로 Serialize되어 사용자에게 반환된다.

[ @RestController 예제 코드 ]

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @GetMapping(value = "/users")
    public User findUser(@RequestParam("userName") String userName){
        return userService.findUser(user);
    }

    @GetMapping(value = "/users")
    public ResponseEntity<User> findUserWithResponseEntity(@RequestParam("userName") String userName){
        return ResponseEntity.ok(userService.findUser(user));
    }
}

findUser는 User 객체를 그대로 반환하고 있습니다. 이러한 경우의 문제는 클라이언트가 예상하는 HttpStatus를 설정해줄 수 없다는 것입니다. 예를 들어 어떤 객체의 생성 요청이라면 201 CREATED를 기대할 것이지만 객체를 그대로 반환하면 HttpStatus를 설정해줄 수 없습니다. 그래서 객체를 상황에 맞는 ResponseEntity로 감싸서 반환해주어야 합니다

일반적으로 우리는 이 조합을 많이 씁니다

RESTful은 b2b에서만 고려하도록 합니다

profile
냥냥냥

0개의 댓글