╱╱╭╮╱╱╱╱╱╱╭━━━╮╱╱╱╭╮╱╭╮╱╱╱╱╱╱ ╱╱┃┃╱╱╱╱╱╱┃╭━╮┃╱╱╱┃┃╱┃┃╱╱╱╱╱╱ ╱╱┃┣━━┳━━╮┃┃╱┃┣━╮╱┃╰━╯┣━━┳━╮╱ ╭╮┃┃╭╮┃┃━┫┃╰━╯┃╭╮╮┃╭━╮┃╭╮┃╭╮╮ ┃╰╯┃╭╮┃┃━┫┃╭━╮┃┃┃┃┃┃╱┃┃╭╮┃┃┃┃ ╰━━┻╯╰┻━━╯╰╯╱╰┻╯╰╯╰╯╱╰┻╯╰┻╯╰╯

Java/Spring Boot

[Spring Boot] Controller Component 개념 2 (@ResponseBody, @RestController, @RequestMapping, @RequestBody, @POST 요청 )

재안안 2022. 7. 17. 04:01

이번에는 저번에 말했듯 또 다른 Controller Component

@RestController에 대해 알아 볼 것이다.

 

그런데 알아보기전 사전의 복습이 필요하다.

왜냐하면 지난번에 알아봤던 @Controller를 통해서 @RestController가 무엇인지 알아 볼 것이기 때문이다. 

 

복습

 

지난번에 @Controller는 view 요청을 handle 할 때 사용 한다고 했다.

 

@Controller 내부의 메소드가 어떤 값을 반환하면 그 값은 View Resolver에게 전달되어

view 파일의 절대경로가 만들어진다고 했다.

 

@Controller method

 

해당 메소드는 문자열 "index" 를 반환한다.

 

만약 이때, index.html을 render하고 싶은게 아니라 "index" 문자열 자체를 응답해 줘야 하는 경우도 있다.

 

응답으로 "index"의 이름을 갖고있는 파일을 찾아보라고 할 수도 있다. (가정)

이때는 "index" 문자열 자체를 반환해야한다.

아무튼 요청에 대한 응답으로

render view가 아닌 send data를 해야하는 경우가 많기 때문이다.

 

이렇게 요청에 대한 응답으로 데이터를 보내주고 싶을 경우 @ResponseBody를 사용한다.

 

 

@ResponseBody

 

@ResponseBody 또한 그냥 메소드에 달아주면 된다.

 

새로운 메소드를 만들었고 해당 메소드 또한 @Controller 내부에 있다.

 

testResponseBody 메소드

 

input이라는 이름의 Parameter가 Request 객체 내부에 존재하면 input을 가져와서 반환하는 메소드이다.

 

해당 url에 postman을 통해 요청을 보내보겠다.

 

postman GET 요청

 

응답은 아래와 같이 왔다.

 

postman 응답 1

 

요청 url에 보냈던 hi 그대로 돌아왔다. (input=hi)

 

postman 응답 2

 

응답의 Content-Type을 확인해보니 text/plain이다.

 

@ResponseBody가 달려있으면 해당 메소드의 응답 속성은 Content-Type: text/plain이다.

 

@ResponseBody가 응답의 content-type을 바꿔주는 것은 아니다.

해당 어노테이션이 달려있는 메소드의 반환값은 View Resolver로 가지 않기 때문에

순수 문자열, 데이터로 반환이 되는 것이다.

 

View Resolver로 가지 않으면 prefix와 suffix를 만날 일도 없으며 content-type도 변하지 않는다.

 

@ResponseBody를 사용하면 응답으로 데이터를 반환할 수 있다는 것을 확인했다.

 

그런데 사실 @ResponseBody 어노테이션은 단독으로 잘 사용되지 않는다.

 

그 이유는 @RestController에 있다.

 

 

@RestController

 

이유는 간단하다.

 

@RestController 내부의 메소드에는 자동으로 @ResponseBody가 붙기 때문이다.

 

 

@RestController를 보면 @Controller@ResponseBody가 상위라는 것을 알 수 있다.

 

 

그래서 @Controller는 view 요청을 handle 할 때 사용하고

@RestController는 데이터 요청을 handle 할 때 사용한다.

 

사용 목적이 다르니 응답의 content-type에 따라 Controller를 분리한 것이다.

 

 

이제 @RestConteroller를 사용해 보겠다.

 

그런데 그냥 사용하면 배우는게 없다. 별 차이점이 없기 때문이다.

그동안 계속 GET 요청을 사용했는데 이번엔 POST 요청을 해보겠다.

 

Controller Conponent

 

@RestController를 사용하고 POST 요청을 받을 거라고 말은 쉽게 했는데 머가 되게 많다. 천천히 하나하나 알아보겠다.

이는 해당 Controller는 이미 Domain까지 연결을 해놔서 그렇다.

Repository는 다음에 알아 볼 것이니 관련된 개념은 최대한 빼고 설명하겠다.

 

어노테이션

해당 클래스는 어노테이션을 3개나 달고있다.

 

@RestController

    해당 어노테이션은 해당 클래스를 IoC 컨테이너에 Bean으로 등록해주기도 하고

    내부 메소드들에게 @ResponseBody를 달아주기도 한다.

@RequestMapping

    해당 어노테이션은 요청 url과 객체를 mapping 시킬 때 사용한다.

    해당 예제에서 BoardController 클래스와 mapping되는 url은 "/api/v1/board"이다.

@RequiredArgsConstructor

    해당 어노테이션은 lombok에서 제공하는 것이다.

    final 필드 boardRepository의 IoC DI를 위한 어노테이션이다. (저번에 Service Component에서 알아봤다.)

 

필드

BoardRepository의 객체를 담는 필드가 존재하며 private이고 final 키워드가 붙어있다.

해당 필드는 BoardController 생성시 IoC 컨테이너에서 자동으로 주입된다.

 

메소드

해당 예제에서는 addBoard() 하나이다.

 

    addBoard 메소드는 @PostMapping 어노테이션을 달고 있다.

    POST 요청임을 명시하면서 해당 메소드와 mapping되는 url은 "/api/v1/board/content"이다.

 

    addBoard 메소드는 CreateBoardDto 객체를 인자로 받는다.

    그리고 매개변수에 @RequestBody 어노테이션이 달려있다.

    이때 잘 봐야한다 @ResponseBody가 아닌 @RequestBody이다.

    @RequestBody는 요청 데이터가 JSON 타입일 때 붙여주어야한다.

 

    해당 메소드 내부에선 boolean 타입의 변수 responseData를 false로 초기화 했고

    boardService의 createBoard()의 반환 값을 responseData에 저장한다.

    responseData의 값에 따라 해당 메소드의 반환 값이 다르다.

 

CreateBoardDto는 이렇게 생겼다.

CreateBoardReqDto 정의

 

 

만약 해당 url로 알맞은 데이터와 POST 요청이 들어온다면 addBoard 메소드는 실행 될 것이다.

 

바로 postman을 통해 사용해 보겠다.

 

postman 요청

 

postman 요청

 

해당 요청은 POST 요청이고 입력창은 Body의 JSON이 선택되어있다.

요청과 함께 JSON 데이터를 보낸 것이다.

 

요청은 이렇게 보냈고 아래와 같은 응답이 왔다.

 

postman 응답 1

 

postman 응답2

 

그리고 이제 DB를 확인해보면 잘 저장된 것을 확인 할 수 있다.

(해당 Controller는 이미 Repository와 연결이 되었다고 했다)

 

Database 확인

 

순서대로 title, usercode, content, create_date이다.

 

postman에서 요청한 그대로 등록이 되었다.

잘보면 요청시간과 DB 등록시간이 1초 차이나는데 되게 찰나의 순간에 요청을 보낸 거같다.

 

 

설명

 

우선 응답의 content-type을 확인하면 @RestController에서 응답을 했으니 text/plain인 것을 확인 할 수 있다.

서버로부터 돌아온 메세지("게시글 작성 성공")가 문자열이니 당연하다.

 

addBoard 메소드의 매개변수가 설명 할 것이 많다.

 

요청에서는 JSON 데이터를 보냈는데

addBoard가 받는 매개변수는 CreateBoardReqDto 객체이다.

CreateBoardReqDto => CBRD로 줄여서 말하겠다.

 

우선 결과를 통해 JSON 데이터가 CBRD 객체로 변환되어서 인자로 들어갔다는 것을 간접적으로 알 수 있다.

매개변수에 달아준 것은 @RequestBody 어노테이션 밖에 없지만 사실 많은 작업들이 수행 되었다.

 

사실 @RequestBody를 통해 요청의 JSON 데이터를 자바 객체로 변환하는 것은 아니다.

Jackson이라는 라이브러리를 통해 변환을 한다.

 

Jackson Binder

 

이 친구가 해준다.

 

Jackson

 

그런데 이때 @RequestBody 어노테이션이 이 친구한테 요청 Body 내부의 JSON을 전달해 주는 것이다.

추가적으로 Jackson 라이브러리는 스프링에 내장 되어있기 때문에 따로 다운로드 받을 필요는 없다.

 

@RequestBody를 통해 JSON을 Jackson에게 전달해주고

Jackson이 JSON을 자바객체로 변환하면 스프링이 해당 자바 객체를 CBRD의 필드에 주입하는 것이다.

 

예를들면,

 

JSON => POJO

Jackson이 JSON을 아래와 같이 바꿔준다.

 

    String title = "스프링 부트 첫 게시글 테스트";

    Int usercode = 1;

    String content = "스프링 부트는 재밌어\n엔터 사용 가능";

 

POJO => CBRD(이것도 POJO이긴 하다)

이제 스프링이 이름이 같은 필드끼리 setter를 통해 mapping한다. (setter 메소드 정의해놔야함)

 

    CBRD.setTitle(title);

    CBRD.setUsercode(usercode);

    CBRD.setContent(content);

 

이때 만약 mapping되는 객체가 없다면 null값이 들어간다.

 

지금까지 요청 데이터가 JSON일 때 @RequestBody 어노테이션을 달아야하는 이유에 대해 알아보았다.

 

정리하자면, JSON의 변환 과정에서 알 수 있듯 보통 @RequestBody는 JSON 데이터를 다룰 때 만 사용한다.

 

Request 객체의 Body에서 데이터를 가져올 때 인데 이는 POST 요청이라는 소리고

POST 요청에서는 보통 JSON 객체를 담기 때문이다.

 

 

mapping되는 객체가 없다면 null값?

 

null 값이 들어간다는게 무슨말인지를 설명하기 위해 또다른 요청을 보내봤다.

 

System.out.println(CBRD)

addBoard에 출력 메소드를 추가하고 이상한 요청을 보내봤다.

 

usercode123 : 1

 

출력은 아래와 같이 나온다.

 

usercode = 0

 

 

 

addBoard()

 

이제 다시 addBoard 메소드로 돌아가겠다.

 

addBoard 메소드

 

그러면 이제 addBoard에 매개변수로 들어갈 객체(CBRD)가 생성된다.

 

그럼 이제 addBoard는 해당 객체를 가지고 boardService.createBoard()를 실행 시킨다.

 

createBoard 메소드는 Repository의 영역인데

 

간단히 말하자면 DB에 게시글 등록이 성공하면 true, 실패하면 false를 반환한다.

 

createBoard 실행 후 boardService.createBoard의 반환 값을 responseData에 저장하고

responseData를 통해 메세지를 생성하여 반환한다.

 

그리고 요청에 대한 응답으로 "게시글 작성 성공"이 왔던 것이다.

 

끝으로

 

여담으로 usercode123 했을 때도 "게시글 작성 성공"이 돌아오긴했다.

usercode가 int형이라서 null이지만 0으로 취급되어서 SQL 쿼리 인덱스는 맞아서 인거같다. (문법상 오류x)

 

아 그리고 JSON 데이터로 응답해야 할 때 Jackson이 없으면 아래와 같이 하면 된다

    String JSON = "{\n" +
            "    \"title\" : \"스프링 부트 첫 게시글 테스트\",\n" +
            "    \"usercode123\" : 1,\n" +
            "    \"content\" : \"스프링 부트는 재밌어\\n엔터 사용 가능\"\n" +
            "}";

만들어서 ResponseBody에 넣어주면 된다

 

여기까지 Controller Component에 대해 알아보았다.

다음에는 Domain Layer와 관련된 Repository에 대해 알아볼 것이다.

 

이때 Repository로 MyBatis를 사용할 것이다.