Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

몇가지 의존성이 있네요!

추가된 의존성은 명확하게 어떤 역할을 하고 있을까요? 간단하게 설명 부탁드릴게요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implementation 'org.springframework.boot:spring-boot-starter-web'
HTTP 요청/응답을 처리하기 위한 모든 기본 컴포넌트로써, @GetMapping, @ResponseBody를 사용할 때 사용됩니다!

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
Thymeleaf 템플릿 엔진으로써, ViewResolver(prefix·suffix)를 자동 설정하기 위해 사용됩니다.

return "reservation";

위 코드에서 반환되는 String이
.../reservation.html로 랜더링되게 해줍니다.

spring-boot-devtools
위 의존성은 코드 수정시에 자동 재시작을 도와줍니다!
템플릿 자동 새로고침을 도와주기 때문에 개발자 편의를 위해 사용됩니다.
사실 devtools를 적용해야 html에 코드 수정사항이 반영되는 걸로 역할을 오해하고 있었는데,
다시 찾아보니 개발 편의성을 위해 이용하는 툴이고 없어도 작동했네요..
그래도 남겨두도록 하겠습니다!

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

developmentOnly 'org.springframework.boot:spring-boot-devtools'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'
}
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/roomescape/HomeController.java
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

줄내림 마지막 부분에 없는데 확인 부탁드려요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정했습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package roomescape;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home";
}
}
3 changes: 3 additions & 0 deletions src/main/java/roomescape/Reservation.java
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 확인 부탁드릴게요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정했습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package roomescape;

public record Reservation(int id, String name, String date, String time) { }
33 changes: 33 additions & 0 deletions src/main/java/roomescape/ReservationController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package roomescape;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.ArrayList;
import java.util.List;

@Controller
public class ReservationController {

private final List<Reservation> reservations = new ArrayList<>();

public ReservationController() {
reservations.add(new Reservation(1, "브라운", "2023-01-01", "10:00"));
reservations.add(new Reservation(2, "브라운", "2023-01-02", "11:00"));
reservations.add(new Reservation(3, "브라운", "2023-01-03", "12:00"));
}

@GetMapping("/reservation")
public String reservationPage() {
return "reservation";
}

@GetMapping("/reservations")
@ResponseBody
public List<Reservation> findAll() {
return reservations;
}
Comment on lines +21 to +30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컨트롤러를 잘 작성해주셨네요👍👍

컨트롤러 내에서 @GetMapping, @ResponseBody 어노테이션을 사용하고 계시는데 내부에서는 어떻게 동작이 실행되는지 한번 생각해 보신 적 있으신가요? 이미 알고 계시다면 아래의 질문에 대한 답변을 적어주시고, 아니라면 한번 해당 어노테이션이 어떤 역할을 하는지 찾아보며 질문에 답해주시면 좋겠네요!

  • /reservation으로 오는 요청은 어떻게 String 타입의 "reservation"을 반환하는데 reservation.html 파일을 반환할까요?

  • /reservations 요청의 응답 Content-Typeapplication/json으로 결정되는 것은 어떤 어노테이션 덕분일까요? 그리고 그 과정은 어떻게 될까요?"

  • @GetMapping은 정확히 어떤 동작을 처리해주는 걸까요?

관련 키워드

  • Spring MVC 동작 원리
  • ViewResolver
  • @responsebody와 MessageConverter
  • HandlerMapping

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/reservation으로 오는 요청은 어떻게 String 타입의 "reservation"을 반환하는데 reservation.html 파일을 반환할까요?

해당 동작 방식은 다음과 같이 이해했습니다!

  1. 클라이언트가 @GetMapping("/reservation") 으로 /reservation을 요청하면,
  2. DispatcherServlet이 요청을 받고 HandlerMapping이 어떤 메서드를 호출할지 찾고, HandlerAdapter가 해당 메서드를 실행합니다.
  3. @responsebody가 없기 때문에 Spring MVC는 이를 뷰 이름(View Name)으로 해석합니다.
  4. ViewResolver가 prefix/suffix를 붙여 파일 경로로 변환합니다.

/reservation으로 오는 요청은 어떻게 String 타입의 "reservation"을 반환하는데 reservation.html 파일을 반환할까요?

  1. 위의 단계와 비슷한 방식으로 reservations를 반환하게 되면,
  2. @responsebody 때문에 ViewResolver가 아니라 HttpMessageConverter로 전달됨.
  3. 그렇게 되면 HttpMessageConverter가 List을 JSON으로 직렬화하고,
  4. Content-Type을 application/json으로 지정하고 JSON으로 응답합니다.

@GetMapping은 정확히 어떤 동작을 처리해주는 걸까요?

GetMapping은 url과 메서드를 연결해주는 역할을 합니다.
GetMapping내부의 값인 url을 읽어서,
"해당 url을 받으면 연결된 해당 메서드를 실행하라"
라는 의미로 받아들였습니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

답변을 보면서 제가 중복된 질문을 2번 드렸나 순간 의아했네요 😅

답변 감사합니다! 1, 2, 3번 질문 모두 스프링 MVC의 핵심 동작 원리를 잘 설명해주셨습니다.

특히 2번 답변에서 @ResponseBody 유무에 따라 ViewResolverHttpMessageConverter로 분기되는 과정을 정확하게 짚어주셨네요.

다만, 1번 답변에서 "클라이언트가 @GetMapping("/reservation") 으로 /reservation을 요청하면" 이라고 하신 부분이 조금 오해의 소지가 있을 것 같습니다. 아마 답변이 꼬이신 것 같아 제가 이해한 내용을 바탕으로 조금 더 명확하게 정리해볼게요!


1번 답변

먼저 클라이언트(브라우저)는 @GetMapping이라는 어노테이션의 존재 자체를 모릅니다.

클라이언트는 그저 서버에 HTTP GET /reservation 이라는 요청을 보낼 뿐입니다. 서버는 이 요청을 받고, HandlerMapping@GetMapping("/reservation") 어노테이션을 스캔해서 연결해주는 것입니다.

동현님이 적어주신 부분은, 클라이언트의 /reservation 요청을 서버의 HandlerMapping@GetMapping 어노테이션을 기준으로 처리할 메서드를 찾는다는 의미로 이해되지만, 주어가 섞이면서 설명이 약간 꼬인 것 같습니다. 혹시 몰라서 정리 해봤는데 참고해주시면 좋을 것 같습니다!

3번 답변

@GetMapping에 대해 조금 만 더 보충하자면, @GetMapping은 사실 @RequestMapping(method = RequestMethod.GET) 의 축약형어노테이션입니다. 이렇게 HTTP Method까지 명시적으로 지정해주는 어노테이션이라는 점을 함께 이해하시면 나중에 @PostMapping, @PutMapping 등 다른 어노테이션의 의미를 더 쉽게 이해하실 수 있을 것 같습니다!

}


15 changes: 15 additions & 0 deletions src/test/java/roomescape/MissionStepTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import static org.hamcrest.Matchers.is;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
Expand All @@ -16,4 +17,18 @@ public class MissionStepTest {
.then().log().all()
.statusCode(200);
}

@Test
void 이단계() {
RestAssured.given().log().all()
.when().get("/reservation")
.then().log().all()
.statusCode(200);

RestAssured.given().log().all()
.when().get("/reservations")
.then().log().all()
.statusCode(200)
.body("size()", is(3));
}
}