-
Notifications
You must be signed in to change notification settings - Fork 170
[Spring MVC] 이고은 미션제출합니다 #507
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ke-62
Are you sure you want to change the base?
Conversation
dooboocookie
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요, 고은.
리뷰어 루카입니다.
이번 미션은 Spring MVC 온보딩 같은 느낌이네요.
어떤 객체지향 설계처럼 의논을할만한 내용은 많지 않은 것 같아서 리뷰양이 많지는 않습니다.
다만, 백엔드 개발자로 API를 구현할 때 꼭 필요한 개념들을 내포하고 있는 미션이라고 생각이 들어요.
Spring이라는 엄청나게 큰 프레임워크를 스터디해나가는게 쉽지는 않겠습니다.
제가 남긴 질문에 대해서 충분히 사고해보고 탐색해보시구, 완벽하지 않더라도 본인이 이해한 내용을 공유해주세요.
제가 채워드릴 수 있는 공백이 있으면 저도 스터디해보고 말씀드리겠습니다.
| @GetMapping("/reservation") | ||
| public String reservationPage() { | ||
| return "reservation"; | ||
| } | ||
|
|
||
| @GetMapping("/reservations") | ||
| @ResponseBody | ||
| public List<Reservation> getReservations() { | ||
| return reservations; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 Comment
이번 PR에서 코멘트를 하나만 달 수 있다면 저는 이부분에 달 것 같아요.
일단 두가지 두 컨트롤러 메서드는 각각 아래와 같은 응답을 내린다는 것을 이해하고 있어야합니다.
- 예약 페이지
- 예약 목록
이를 통해, Spring MVC @RequestMapping에 대한 이해를 시작하실 수 있을 것 입니다.
학습 테스트를 해보셨다면 바로 아실 수 있겠지만,
고은이 이해한만큼만 저에게 다시한번 설명해주시면 더 이야기 나누기 좋을 것 같습니다.
Q. 각 API가 어떤 역할을 하고있는지와 Spring MVC에서 어떻게 동작하는지 설명해주세요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 공부한 바로는
각 API의 역할
@GetMapping("/reservation") → 예약 페이지 반환
사용자가 브라우저에서 직접 접근하는 화면(HTML)을 제공합니다.
reservation.html 템플릿이 렌더링됩니다.@GetMapping("/reservations") → 예약 목록 데이터 반환
예약 목록 데이터를 JSON으로 반환합니다.
Spring MVC와 @RequestMapping의 동작
DispatcherServlet이 HTTP 요청을 받습니다.
HandlerMapping이 @GetMapping(= @RequestMapping(method=GET))에 지정된 URL 패턴을 보고 어떤 컨트롤러 메서드를 실행할지 결정합니다.
메서드 실행 후 반환값 처리:@responsebody 없음 → ViewResolver가 뷰 이름을 해석해 HTML 페이지를 렌더링 합니다.
@responsebody 있음 → HttpMessageConverter가 Java 객체를 JSON으로 변환(직렬화) 합니다.
같은 컨트롤러 안에 있어도 URL 경로와 어노테이션에 따라 "페이지를 보여줄지, 데이터를 제공할지"가 결정된다는 점을 명시적으로 알 수 있게 된 것 같습니다..!
또한 @RequestMapping이 단순히 URL 매핑뿐 아니라 응답 형태까지 제어한다는 걸 이해하게 되었습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Approve
@responsebody 없음 → ViewResolver가 뷰 이름을 해석해 HTML 페이지를 렌더링 합니다.
@responsebody 있음 → HttpMessageConverter가 Java 객체를 JSON으로 변환(직렬화) 합니다.
굿.
잘 학습하셨습니다.
전자의 경우, 정적인 자원을 path로 탐색해서, 그를 응답으로 내려주고,
후자의 경우, 반환하는 Java 객체를 JSON으로 직렬화해서 응답으로 내려줍니다.
전자의 경우, View를 응답하는 경우에 많이 사용할테고,
후자의 경우, Server의 리소스에 대한 처리 결과를 내려주기 위해 많이 사용합니다.
전자는 BE-FE 구조가 나눠져있는 경우에는 server개발자가 사용할일은 많지 않겠는데요.
후자는 앞으로 굉장히 많이 사용하게될 방식입니다.
GET은 리소스에 대한 조회
POST는 리소스 저장
PUT은 리소스 변경
DELETE는 리소스 삭제
...
와 같이 RESTful API같은 방식으로 API를 제공하게 될겁니다.
전자와 후자의 목적의 차이를 분명히 이해하셨으면 좋겠어서, 이 부분에 코멘트를 드렸는데요.
ViewResolver같은 개념들을 잘 찾아보시면서 잘 학습하신 것 같아 다행입니다.
더 나아가서, Controller에서는 말씀하셨던것 처럼 응답 방식까지 결정하는데요.
그렇게 되려면 경우에 따라선 훨씬 더 많은 일을 할 수 밖에없습니다. url mapping, 예외 핸들링하여 적절한 예외 응답, 인증인가, validate, ...
이중 예외 핸들링에 대해 조금 사고를 해보겠습니다.
view 페이지를 조회하다가 예외가 발생했을 때와
resource를 조회하다가 예외가 발생했을 때,
그 API를 호출하는 클라이언트가 받아야되는 예외 응답은 어떻게 달라질까요??
확실한건 같진 않겠네요.
이에 대해서 앞으로 미션하면서 고민해보셨으면 좋겠고, 그 두가지 차이를 어떻게 경계를 지으면 좋을지 고민해보셔도 좋겠습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
또한 @RequestMapping이 단순히 URL 매핑뿐 아니라 응답 형태까지 제어한다는 걸 이해하게 되었습니다.
이 답변은 무슨 의도로 말씀하신 건지 잘 이해가되지 않습니다.
RequestMapping은 요청을 매핑하기 위한 어노테이션이 맞습니다.
| dependencies { | ||
| implementation 'org.springframework.boot:spring-boot-starter' | ||
| implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' | ||
| testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
| testImplementation 'io.rest-assured:rest-assured:5.3.1' | ||
| implementation 'org.springframework.boot:spring-boot-starter-web' | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 Comment
위에 @GetMapping 메서드에 대한 코멘트 다음으로 중요한 부분이 이부분이라 생각합니다.
build.gradle이라는 파일이 어떤 역할을 하고 계시는지 아시나요?
초록스터디 학습자료에 있는 내용입니다.
build.gradle에 작성된 스크립트에 따라 빌드 시 필요한 작업들이나 의존성들을 정의하는 공간입니다.
프로젝트가 커질수록 많은 의존성도 더 많아질 것입니다. 이렇게 설정을 나타내는 부분도 코드의 일부라고 생각하고 가독성이 좋게 만드시면 프로젝트를 유지보수하는데에도 큰 도움이 될것입니다.
그리고 gradle에 대한 이해를 올려보면서 적절한 의존성을 갖게하는 것도 백엔드 개발자의 필요한 덕목이겠죠.
Q. gradle의 역할과 동작에 스터디해보고 이해한만큼만 저에게 설명해주세요.
| dependencies { | |
| implementation 'org.springframework.boot:spring-boot-starter' | |
| implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' | |
| testImplementation 'org.springframework.boot:spring-boot-starter-test' | |
| testImplementation 'io.rest-assured:rest-assured:5.3.1' | |
| implementation 'org.springframework.boot:spring-boot-starter-web' | |
| } | |
| dependencies { | |
| implementation 'org.springframework.boot:spring-boot-starter' | |
| implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' | |
| implementation 'org.springframework.boot:spring-boot-starter-web' | |
| testImplementation 'org.springframework.boot:spring-boot-starter-test' | |
| testImplementation 'io.rest-assured:rest-assured:5.3.1' | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
처음 이 리뷰를 받고 gradle의 역할에 대해 고민해 보았을 때 build.gradle은 의존성 관리를 한다고 생각했습니다.
dependencies 블록에 선언된 라이브러리들을 프로젝트에 추가하는 역할을 한다고 생각했기 때문입니다.
따로 gradle의 역할과 동작에 스터디 해 보니 중요한 일들을 많이 처리하고 있음을 알게 되었습니다.
의존성 외에도,
1. 빌드 라이프사이클
clean, build, test 등의 태스크를 순차적으로 실행하여 소스 코드를 컴파일하고 실행 가능한 결과물(JAR, WAR)을 생성합니다.
2. 플러그인
org.springframework.boot 플러그인 등을 통해 Spring Boot 관련 설정을 자동으로 처리합니다.
의 역할을 하고 있는것을 알게 되었습니다.
프로젝트가 커질수록 많은 의존성도 더 많아질 것입니다. 이렇게 설정을 나타내는 부분도 코드의 일부라고 생각하고 가독성이 좋게 만드시면 프로젝트를 유지보수하는데에도 큰 도움이 될것입니다.
이 부분에 대해 고민을 해 보았는데, 의존성이 많아질수록 build.gradle도 복잡해지기 때문에, 관련된 의존성끼리 그룹화하거나 주석을 추가하여 가독성을 높이면 유지보수가 훨씬 수월해질 것 같습니다!
| public class Reservation { | ||
| private Long id; | ||
| private String name; | ||
| private String date; | ||
| private String time; | ||
|
|
||
| public Reservation(Long id, String name, String date, String time) { | ||
| this.id = id; | ||
| this.name = name; | ||
| this.date = date; | ||
| this.time = time; | ||
| } | ||
|
|
||
| public Long getId() { | ||
| return id; | ||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| public String getDate() { | ||
| return date; | ||
| } | ||
|
|
||
| public String getTime() { | ||
| return time; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public class Reservation {
public Long id;
public String name;
public String date;
public String time;
public Reservation(Long id, String name, String date, String time) {
this.id = id;
this.name = name;
this.date = date;
this.time = time;
}
}이렇게 변경해도 테스트가 통과합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package roomescape;
public class Reservation {
private Long id;
private String name;
private String date;
private String time;
public Reservation(Long id, String name, String date, String time) {
this.id = id;
this.name = name;
this.date = date;
this.time = time;
}
private Long getId() {
return id;
}
private String getName() {
return name;
}
private String getDate() {
return date;
}
private String getTime() {
return time;
}
}반면 이렇게 하면 테스트가 실패합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 Comment
이 상태로 /reservations API를 호출하면 브라우저에 데이터가 표시되지 않았습니다. getter 메서드를 추가하니 정상적으로 JSON 응답이 되었는데, Spring에서 객체를 JSON으로 변환할 때 내부적으로 어떤 방식으로 동작하는지 궁금합니다.
제가 생각했을 때, 아주 자연스럽고 꼭 들어야되는 궁금증이라고 생각합니다.
Spring이 너무 많은 일을 해주기 때문인데요.
- 어떤 컨트롤러 메서드가 어떤 요청에 대응해야될지 조립도해주고,
- Http 응답은 body에 java의 객체라는 개념을 담을 수 없어 JSON으로 직렬화 하는 과정이 필요하고,
- 컨트롤러 메서드의 어노테이션이나 리턴타입에 따라 그것을 어떤 수단을 통해서 JSON으로 직렬화해 응답 바디 내용을 만들고,
- 여러 헤더와 응답 바디를 HTTP 응답으로 만들어서 내려주고,
그래서 Spring같은 고수준의 프레임워크를 사용하시다 보면,
왜 되지? 왜 안되지? 이런 고민들이 계속 드실거에요.
사실 저에게 어떤 방식으로 동작하는지 질문을 해주셨습니다.
저는 이게 충분히 타당한 궁금증이고 할수있는 질문이라고 생각합니다.
근데 제입장에서는 질문에 답변을 드리기 약간은 모호한 부분이 있습니다.
뭘 궁금해하는지 확실히 알 수 없고 내가 뭘 말해줘야될지 모르겠기 때문인데요.
(아마 제가 앞선 코멘트에서 비슷한류의 질문을 드렸는데 그에 대한 답변을 찾으시면서 비슷한 당혹함을 느끼시지 않을까 싶네요 😁😅)
Spring도 대단한 재주가 있는 녀석은 아닙니다.
물론 리플렉션과 같은 정말 다양한 방법으로 역직렬화를 하고 객체의 구조를 파악합니다만,
기본적으로 생성자가 있어야 객체를 생성할 수 있고,
필드 그 자체나 관용적으로 쓰이는 getXXX이름의 메서드가 public으로 열려 있어야 그 필드를 직렬화하는 대상이라고 판단하는 것 같아요.
더 구체적인 근본적인 이유나 Spring의 의도는 더 디깅해봐야겠네요.
Q. Spring MVC에서 기본적으로 Java 객체를 Json으로 역직렬화하는 주체는 누구고, 어떤 방식을 사용할까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package roomescape;
public class Reservation {
private Long id;
private String name;
private String date;
private String time;
public Reservation(Long id, String name, String date, String time) {
this.id = id;
this.name = name;
this.date = date;
this.time = time;
}
private Long getId() {
return id;
}
private String getName() {
return name;
}
private String getDate() {
return date;
}
private String getTime() {
return time;
}
}
이 코드는 private 필드라 외부에서 get메서드를 사용할 수 없어 테스트 코드가 실패한다고 생각이 되는데 제 생각이 맞을까요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
리뷰어님이 주신 질문들과 코멘트에 대해 공부하다 보니 Spring이 얼마나 많은 일을 자동으로 처리하는지 알게 되었습니다. 컨트롤러 매핑, JSON 직렬화, HTTP 응답 생성까지 여러 단계가 있고, 각 단계마다 깊이 있는 내용이 있다는 걸 이해했습니다.
이걸 이해한 뒤 제 질문을 다시 보니 질문이 너무 광범위해서 답변하기 어려우셨을 것 같습니다. "어떤 방식으로 동작하는지"라는 질문이 Jackson의 동작 원리를 묻는 건지, Spring MVC의 MessageConverter 메커니즘을 묻는 건지, 아니면 HTTP 응답 생성 과정 전체를 묻는 건지 명확하지 않았네요. 😅
Q. Spring MVC에서 기본적으로 Java 객체를 JSON으로 직렬화하는 주체는 누구고, 어떤 방식을 사용할까요?
질문에 대한 답
직렬화 주체
Spring MVC에서는 Jackson 라이브러리의 ObjectMapper가 Java 객체를 JSON으로 직렬화하고, 이는 MappingJackson2HttpMessageConverter를 통해 처리됩니다.
직렬화 방식
Jackson은 리플렉션을 사용해 객체의 구조를 파악하고, public 필드나 JavaBeans 규약을 따르는 getXxx() 형식의 public 메서드를 통해 필드 값에 접근해서 JSON으로 변환한다고 이해했습니다.
아직 모든 세부 동작 원리를 완벽히 이해하지는 못했지만, 앞으로 학습하면서 Spring의 내부 동작에 대해 점차 더 깊이 이해할 수 있을 것 같습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Approve
위에 주신 질문이 고은이 제 질문에 대한 답변을 작성하면서 답이 됐을것이라 생각해서 따로 답을 드리진 않겠습니다.
|
최대한 빠르게 리뷰에 답변을 드리려고 했으나, 더 깊이 있게 고민하고 소통하고 싶어 조금 시간이 걸렸습니다. 루카님께서 주신 질문들에 대해 제가 이해한 내용을 정리해 답변을 드렸는데, 혹시 부족하거나 보완이 필요한 부분이 있다면 편하게 말씀 부탁드립니다! 😊 |
dooboocookie
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다.
다음 미션으로 넘어가시죠!
안녕하세요! 경환님. 이번에 처음 인사드리는 것 같네요 잘 부탁드립니다!
이번 미션은 step1,2 구현하는 것이였고
Step1. 홈 화면
Step2. 예약 조회
입니다!
제 코드에 대해 설명해 드리자면
궁금한 점
초기에 예약 데이터를 저장하기 위한 객체를 생성할 때 아래와 같이 생성자만 있고 getter 메서드가 없는 클래스를 만들었습니다:
이 상태로 /reservations API를 호출하면 브라우저에 데이터가 표시되지 않았습니다. getter 메서드를 추가하니 정상적으로 JSON 응답이 되었는데, Spring에서 객체를 JSON으로 변환할 때 내부적으로 어떤 방식으로 동작하는지 궁금합니다.
항상 리뷰해 주셔서 감사합니🙇♀️