Skip to content
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

[6주차] 공통 과제 #7

Open
tmdcheol opened this issue Aug 6, 2024 · 5 comments
Open

[6주차] 공통 과제 #7

tmdcheol opened this issue Aug 6, 2024 · 5 comments
Assignees

Comments

@tmdcheol
Copy link
Contributor

tmdcheol commented Aug 6, 2024

과제 설명

  • tmdcheol/spring-summer-coding main branch clone 하여 코드 받기
  • 아래의 기능을 수행하는데 발생하는 모든 동작흐름를 최대한 자세하게 정리

목표

  • 지금까지 공부한 내용을 정리해보며, 그 흐름을 이해하는 것이 목표

기능

image
  1. 입금 폼 랜더링
  2. { 이름 : 땅울림, 보너스 선택 : 정률 보너스, 입금할 금액 : 10000} 을 선택하고 ‘입금’ 버튼 클릭
  • 위의 기능이 수행되는데 발생하는 모든 동작흐름을 지금까지 본인이 습득한 내용에 기반하여 최대한 자세하게 정리하기
  • 정리하면서 더 알아보고 싶은 내용이 있다면 직접 찾아보고 보충학습하기
  • 웹 브라우저 요청부터 시작하여 server가 요청을 받고 응답하는 과정을 정리

참고 : @PostConstruct 어노테이션을 통해 테스트용 맴버 데이터 지정된 상태

@tmdcheol tmdcheol self-assigned this Aug 6, 2024
@InSooBeen
Copy link

입금 폼 렌더링


홈화면에서 입금 아이콘을 눌러 입금 폼에 접근하려는 클라이언트 요청이 들어왔다고 가정한다.

1단계 : 웹 브라우저가 DispatcherServlet에 요청을 전송한다.


입금 아이콘을 누르면 웹 브라우저가 “/core/deposit”이라는 요청 경로를 DispatcherServlet에게
전송한다.

2단계 : DispatcherServlet이 HandlerMapping 빈 객체에 컨트롤러 검색을 요청한다.

DispatcherSerlvet은 해당 요청을 처리할 컨트롤러를 검색하지 않고, RequestMappingHandelrMapping 빈 객체에게 컨트롤러 검색을 요청한다.

RequestMappingHandlerMapping 빈 객체는 웹 브라우저에서 전달한 “/core/deposit” 요청경로를
처리할 수 있는 DepositController를 DispatcherServlet에 리턴한다.

3단계 : DispatcherServlet이 HandlerAdapter 빈 객체에 컨트롤러 객체의 요청 처리를 위임한다.

4단계 : HandlerAdapter 빈 객체가 컨트롤러의 알맞은 메서드를 호출해서 요청을 처리하고, 결과를 ModelAndView로 변환하여 리턴한다.

링크를 통해 URL에 접속하면, 해당 요청 GET방식으로 이루어진다.

RequestMappingHandlerAdapter 빈 객체는 GET 방식의 “/core/deposit” 요청경로를 처리할 수 있는 DepositController의 depositForm() 메서드를 실행한다.

depositForm 메서드는 조회된 모든 멤버 목록을 Model 객체에 members라는 이름으로 추가하고,
”core/deposit/depositForm”이라는 뷰 이름을 리턴한다.

즉, 뷰 이름을 문자열로 반환하고 모델 데이터는 Model 객체를 통해 전달한다고 볼 수 있다.

4단계-내부동작 : ModelAndView 객체로 변환되어 처리된다.

depositForm 메서드를 보면 메서드 자체에서 ModelAndView 객체를 리턴하지는 않는다.

스프링 MVC에서는 Model과 View를 반환하는 메서드가 내부적으로 ModelAndView 객체로 변환되어
DispatcherServlet에 전달되기 때문이다.

5단계 : DispatcherServlet이 ViewResolver를 이용해 결과를 보여줄 View를 검색한다.

스프링 부트에서는 다음의 ViewResolver를 사용한다.

  • Thymeleaf : ThymeleafViewResolver
  • JSP : InternalResourceViewResolver
  • FreeMarker : FreeMarkerViewResolver
  • Groovy : GroovyMarkupViewResolver

기본적으로는 ThymeleafViewResolver가 자동으로 설정되며, 템플릿 위치와 파일 확장자는 다음과
같이 설정된다.

  • 템플릿 위치 : classpath: /templates/
  • 템플릿 파일 확장자 : .html

즉, ThymeleafViewResolver는 “core/deposit/depositForm”이라는 뷰 이름을 이용해

classpath:/templates/core/deposit/depositForm.html이라는 최종 템플릿 경로를 생성하고, 이 경로를 기반으로 ThymeleafView 객체를 생성해 리턴한다.

6단계 : DispatcherServlet이 최종 응답 HTML을 웹 브라우저에 리턴한다.

{ 이름 : 땅울림, 보너스 선택 : 정률 보너스, 입금할 금액 : 10000}을 선택 후 입금

버튼 클릭


입금 화면에서 “회원 선택: 땅울림, 보너스 선택: 정률 보너스, 입금할 금액: 10000”을 선택해 입금 버튼을 클릭했다고 가정한다.

1단계 : 웹 브라우저가 DispatcherServlet에 요청을 전송한다.

depositForm.html에서 “/core/deposit” 경로로 POST 방식을 이용해 데이터를 전송하려는 요청을 DIspatcherServlet에 보낸다.

2단계 : DispatcherServlet이 HandlerMapping 빈 객체에 컨트롤러 검색을 요청한다.

RequestMappingHandlerMapping 빈 객체는 웹 브라우저에서 전달한 “/core/deposit” 요청경로를
처리할 수 있는 DepositController를 DispatcherServlet에 리턴한다.

3단계 : DispatcherServlet이 HandlerAdapter 빈 객체에 컨트롤러 객체의 요청 처리를 위임한다.

4단계 : HandlerAdapter 빈 객체가 컨트롤러의 알맞은 메서드를 호출해서 요청을 처리하고, 결과를 ModelAndView로 변환하여 리턴한다.

depositForm.html에서 폼 데이터 처리를 POST 방식으로 하고 있기 때문에 POST 방식의 “/core/deposit” 요청 경로를 처리할 수 있는 deposit() 메서드를 실행한다.

deposit 메서드는 @ModelAttribute을 이용해 DepositForm 타입의 커맨드 객체에 요청 파라미터 데이터들을 복사한다.

deposit 인터페이스를 구현한 DepositServiceImpl 클래스에서는 재정의한 deposit 메서드에서 커맨드 객체에서 복사한 요청 파라미터 값을 이용하는데, memberId에 해당하는 member를 찾고,
calculateFinalDepositMoney 메서드를 이용해 입금 금액을 구한뒤, 해당 member에 입금 내역을 반영한다.

이후 “redirect:/core”이라는 리다이렉트 메세지를 작성한다.

5단계 : DispatcherServlet은 클라이언트에게 HTTP 응답을 보낸다.

이때, 302 상태 코드와 함께 Location 헤더가 포함되어, 클라이언트가 /core URL로 새 요청을 보내도록 지시한다.

6단계 : 웹 브라우저가 DispatcherServlet에 “/core”에 대해 새로운 요청을 보낸다.

웹 브라우저는 리다이렉트 응답을 수신한 후, Location 헤더에 지정된 URL로 새로운 GET 요청을 보낸다. 이때는 새로운 요청이므로, /core 주소에 대한 요청을 보내게 된다.

7단계 : DispatcherServlet이 HandlerMapping 빈 객체에 컨트롤러 검색을 요청한다.

RequestMappingHandlerMapping 빈 객체는 웹 브라우저에서 전달한 “/core” 요청경로를
처리할 수 있는 HomeController를 DispatcherServlet에 리턴한다.

8단계 : DispatcherServlet이 HandlerAdapter 빈 객체에 컨트롤러 객체의 요청 처리를 위임한다.

9단계 : HandlerAdapter 빈 객체가 컨트롤러의 알맞은 메서드를 호출해서 요청을 처리하고, 결과를 ModelAndView로 변환하여 리턴한다.

RequestMappingHandlerAdapter 빈 객체는 GET 방식의 “/core/home” 요청경로를 처리할 수 있는 HomeController의 home() 메서드를 실행한다.

home() 메서드는 “core/home” 이라는 뷰 이름을 리턴한다.

스프링 MVC에서는 View를 반환하는 home() 메서드가 내부적으로 ModelAndView 객체로 변환되어
DispatcherServlet에 전달된다.

10단계 : DispatcherServlet이 ViewResolver를 이용해 결과를 보여줄 View를 검색한다.

ThymeleafViewResolver는 “core/home”이라는 뷰 이름을 이용해

classpath:/templates/core/home.html이라는 최종 템플릿 경로를 생성하고, 이 경로를 기반으로 ThymeleafView 객체를 생성해 리턴한다.

11단계 : DispatcherServlet이 최종 응답 HTML을 웹 브라우저에 리턴한다.

웹 브라우저의 http 요청


url을 통해 특정 경로에 접속할 때

EX) localhost:8080/core URL로 접속하려는 경우

웹 브라우저에서 URL을 통해 특정 경로에 접속하는 경우, HTTP는 GET방식의 요청을 서버에 전송한다

GET 방식 : 요청 방식 서버로 부터 데이터를 요청하며, 주로 리소스를 조회하는 경우에 사용된다.

1단계 : 요청 메서드 포함
2단계 : 요청 리소스의 URL 포함
3단계 : HTTP 프로토콜의 버전 명시
4단계 : 요청 헤더 (RequestHeader)

요청 헤더는 클라이언트가 서버에 추가 정보를 전달하는 부분이다.
주요 헤더는 다음과 같다.

Host: 요청하는 호스트의 이름과 포트 번호를 포함
User-Agent: 요청을 보낸 클라이언트의 정보 (브라우저 종류 및 버전 등)
Accept: 클라이언트가 수용할 수 있는 MIME 타입을 명시
Content-Type: POST 요청 등에서 전송하는 데이터의 MIME 타입을 지정
Authorization: 인증 정보를 포함

실제로, 크롬에서 스프링 Application을 실행한 뒤 localhost:8080/core로 접속했을 때 메세지를 확인해보면 다음과 같이 나온다. 메세지를 보면 요청 메서드, 요청 리소스 URL, HTTP 버전 등의 내용이 포함되어 있다.

HTTP가 서버에 요청을 보낸 후 처리가 되면 이에 대한 응답을 보내는데, HTTP 응답 헤더는 서버가 클라이언트에게 보내는 메타데이터로, 응답의 특성이나 처리 방법에 대한 정보를 포함한다.
주요 응답 헤더에는 HTTP 버전, 상태 코드, Date, Content-Type 등이 포함된다.

또한, 입금 아이콘을 통해 입금 폼에 접근하면 다음과 같은 메세지를 확인할 수 있다.

HTTP 응답 헤더는 다음과 같이 나타난다.

폼을 작성한 뒤 전송을 했을때

EX) 입금 폼에서 {이름: 땅울림, 보너스 선택: 고정 보너스, 입금할 금액:10000}을 선택한 뒤 입금했을 때

html을 작성할 때, 전송하면 POST 방식으로 처리하도록 설정했기 때문에 요청 메서드가 POST로 변경된 것을 볼 수 있다.

또한, GET 방식과는 다르게, POST 방식은 RequestBody가 존재하는 것을 확인할 수 있다.

코드에서는 입금 폼을 작성하고 제출하면 /core로 리다이렉트 했었기 때문에, 상태코드는 302가 된 것을 알 수 있다.

또한, 리다이렉션 응답을 생성하는 경우에는, Location 정보가 포함되며, 클라이언트가 요청을 보내야하는 새로운 URL을 지정한다.

이 경우에는 요청을 보내야하는 새로운 URL로 http://localhost:8080/core가 지정되었으므로 다시 요청이 발생한다.

따라서, 위의 요청을 서버가 처리하고 다음과 같은 응답 메세지를 보내면서, 요청이 종료된다.
⇒ 다시 /core 경로로 화면을 띄우면서 종료된다.

@haewonee
Copy link

저번 발표에서 스프링 MVC 핵심 구성 요소를 이미지를 사용하여 발표했었다. 이번 과제도 그 그림을 따라 진행해보고자 한다.

스프링 MVC가 웹 요청을 처리하려면 DispatcherServlet을 통해서 웹 요청을 받아야 한다. -> 스프링 부트 프로젝트에서는 별도의 web.xml 파일을 이용해 DispatcherServlet을 등록하지 않고도 자동으로 이를 등록해준다.

이 파일은 SpringintroApplication.java 파일이다.
@SpringBootApplication 애노테이션을 붙이면 HandlerMapping과 HandlerAdapter가 자동으로 설정된다. -> 우리는 별도의 복잡한 설정을 할 필요가 없어졌다.

홈페이지에서 입금기능을 선택한다고 가정하자.

++ 현재 클라이언트와 서버는 SYN, SYN+ACK, ACK 과정을 거쳐 이미 연결된 상태라고 가정하자. 연결이 끊겨 있으면 HTML 요청을 보낼 수가 없다.

localhost:8080/core/deposit을 누르고 엔터를 누른 상태이다.


이 요청은 위 그림처럼 HTTP Get방식으로 DispatcherServlet에 전달된다.

DispatcherServlet은 HandlerMapping에게 컨트롤러 검색을 요청한다.

HandlerMapping은 요청URL과 매핑된(@RequestMapping) 위의 DepositController를 찾고 이를 DispatcherServlet에 반환해준다.

DispatcherServlet은 HandlerMapping이 찾아준 컨트롤러 객체를 처리하기 위해 HandlerAdapter 에게 요청 처리를 위임한다.

HandlerAdapter은 GET요청이 들어왔기에 @GetMapping 애노테이션이 사용된 depositForm 함수를 호출하여 회원목록을 List로 가져오고 이를 Model(뷰에 전달할 데이터)에 담아준다. 그리고 렌더링할 뷰의 이름(core/deposit/depositForm)을 반환해준다.
-> ModelandView 처리

ModelAndView는 컨트롤러가 리턴한 뷰(논리적 뷰) 이름을 담고 있는데 ViewResolver는 이 논리적 뷰 이름을 실제 뷰로 변환한다.
즉 ViewResolver는 해당 HTML 파일을 찾은 후, 그 파일을 렌더링해서 HTML로 변환한다. 변환된 HTML은 클라이언트로 보낸다.

위의 그림처럼 서버는 클라이언트에 렌더링된 HTML을 응답으로 보낸다.
클라이언트는 받은 HTML을 parsing한다. HTML에 있는 이미지나 영상 등 추가적으로 처리할 요청이 있다면 이를 서버측에 요청한다.

클라이언트와 서버의 전체적인 흐름이다.

지금까지는 아무런 정보를 입력하지 않고 딱 입금 페이지에 들어오는 과정을 봤다.

{ 이름 : 땅울림, 보너스 선택 : 정률 보너스, 입금할 금액 : 10000} 을 선택하고 ‘입금’ 버튼 클릭하는 과정을 설명하고자 한다.

++ 그림은 생략하고 설명

{ 이름 : 땅울림, 보너스 선택 : 정률 보너스, 입금할 금액 : 10000} 을 선택하고 ‘입금’ 버튼 클릭하면

  • 요청은 위 그림처럼 HTTP POST방식으로 DispatcherServlet에 전달된다.
    -> 헤더에는 보너스 정책(정률), 입금할 금액, ID등의 정보가 담긴다.
  • 아까 GET방식과 같이 DispatcherServlet은 HandlerMapping에게 컨트롤러 검색을 요청한다.


HandlerMapping은 요청URL과 매핑된(@RequestMapping) 위의 DepositController를 찾고 이를 DispatcherServlet에 반환해준다.

DispatcherServlet은 HandlerMapping이 찾아준 컨트롤러 객체를 처리하기 위해 HandlerAdapter 에게 요청 처리를 위임한다.

HandlerAdapter은 POST요청이 들어왔기에 @PostMapping 애노테이션이 사용된 **deposit 함수를 호출하고 DepositForm 객체에 amount, policy, ID와 같은 정보를 담아준다 **

  • 호출된 메서드는 depositService.deposit(memberId, amount, policy)를 호출하여 입금 처리를 해준다.


DepositService를 인터페이스로 하는 DepositServiceImpl는 다음을 수행하게 된다.
deposit 함수는 findById로 회원 조회를 하고 그 밑에 있는 calculateFinalDepositMoney함수를 사용하여 입금액+보너스액을 계산한다.
현재 예시에서는 10000이 입금액이므로 10000+1000 = 11000이 입금액이 된다.
member.deposit(finalDepositMoney)를 써서 계산된 입금액 정보를 추가해준다.

  • 다시Controller 코드로 돌아가서보면 deposit함수가 return redirect:/core"를 하는 것을 볼 수 있는데 이는 입금동작이 끝난 후 /core/deposit 화면이 아닌 /core 화면으로 리다이렉트 해준 것이다.

  • DispatcherServlet은 리다이렉션 정보를 바탕으로 클라이언트에 위 그림처럼 응답을 반환한다.
    302는 요청은 처리되었으나 클라이언트가 다른 URL로 요청을 보내야한다는 의미이다.
    -> 이 응답을 클라이언트가 받으면 클라이언트는 헤더에 명시된 URL로 GET 요청을 보내면 된다.


결과적으로 입금버튼을 누르면 위 그림처럼 리다이렉션으로 인해 다른 화면을 띄우게 된다.

@jlhyunii
Copy link




입금 폼 랜더링

0. 클라이언트 요청

웹 브라우저에서 입금 버튼을 눌러 입금 폼에 접근하고자 한다.


1. 요청 URL과 매칭되는 컨트롤러 검색

home.html

  • 요청 URL : "/core/deposit". 이는 DispatcherServlet에 전송된다.

  • DispatcherServlet는 RequestMappingHandlerMapping이라는 빈 객체에게 컨트롤러 검색을 요청한다.

  • HandlerMapping은 클라이언트의 요청 경로를 이용해서 이를 처리할 컨트롤러 빈 객체를 DispatcherServlet에 전달한다.

DepositController.java

  • 웹 요청 경로가 "/core/deposit"이므로 등록된 컨트롤러 빈 중에서 "/core/deposit" 요청 경로를 처리할 컨트롤러를 리턴한다.

2. 처리 요청

DispatcherServlet은 RequestMappingHandlerMapping이 찾아준 컨트롤러 객체를 처리할 수 있는 HandlerAdapter 빈에게 요청 처리를 위임한다.


3. HandlerAdapter

HandlerAdapter는 컨트롤러의 알맞은 메서드를 호출해서 요청을 처리하고 그 결과를 ModelAndView라는 객체로 변환해서 DispatcherServlet에 리턴한다.

DepositController.java

클라이언트가 /core/deposit 경로로 GET 요청을 보내면,

  • 'depositForm()' 메서드로 Mapping된다.

  • 'memberService.findAllMembers()'를 호출하여 모든 회원 목록을 List< Member> 형태로 가져온다.

  • 가져온 'members' 리스트를 'model.addAttribute("members", members)'를 통해 Model 객체에 추가한다.

  • 리턴되는 "core/deposit/depositForm"은 뷰 이름을 의미한다.


4. JSP를 위한 ViewResolver

DispatcherServlet은 결과를 보여줄 뷰를 찾기 위해 ViewResolver 빈 객체를 사용한다.

  • 컨트롤러의 실행 결과를 받은 DispatcherServlet은 ViewResolver에게 뷰 이름에 해당하는 View 객체를 요청한다.

  • InternalResourceViewResolver는 "prefix + 뷰이름 + suffix"에 해당하는 경로를 뷰 코드로 사용하는 InternalResourceView 타입의 View 객체를 리턴한다.

prefix : /templates/
suffix : .html
최종 경로 : /templates/core/deposit/depositForm.html


5. 응답 생성

DispatcherServlet은 ViewResolver가 리턴한 View 객체에게 응답 결과 생성을 요청하고, 이를 웹 브라우저에 전송한다.


HTTP


1. 크롬에서 "localhost:8080/core/deposit"으로 접속했을 때의 메세지

  • 메서드 GET : 리소스 조회

2. 입금 폼에 접근하는 요청을 성공적으로 처리했을 때의 메세지

  • 200 OK : 요청 성공


{이름 : 땅울림, 보너스 선택 : 정률 보너스, 입금할 금액 : 10000} 을 선택하고 ‘입금’ 버튼 클릭

0. 클라이언트 요청

{이름 : 땅울림, 보너스 선택 : 정률 보너스, 입금할 금액 : 10000}을 선택하고 ‘입금’ 버튼을 클릭한다.


1. 요청 URL과 매칭되는 컨트롤러 검색

  • 요청 URL : "/core/deposit". 이는 DispatcherServlet에 전송된다.

  • 전송 방식 : POST 방식으로 전송된다.

DepositController.java

  • 웹 요청 경로가 "/core/deposit"이므로 등록된 컨트롤러 빈 중에서 "/core/deposit" 요청 경로를 처리할 컨트롤러를 리턴한다.

2. 처리 요청

DispatcherServlet은 RequestMappingHandlerMapping이 찾아준 컨트롤러 객체를 처리할 수 있는 HandlerAdapter 빈에게 요청 처리를 위임한다.


3. HandlerAdapter

HandlerAdapter는 컨트롤러의 알맞은 메서드를 호출해서 요청을 처리하고 그 결과를 ModelAndView라는 객체로 변환해서 DispatcherServlet에 리턴한다.

DepositController.java

클라이언트가 /core/deposit 경로로 POST 요청을 보내면,

  • 'deposit' 메서드로 Mapping된다.

  • @ModelAttribute 애노테이션 : 스프링이 요청 파라미터를 이용해 'DepositForm' 객체를 자동으로 생성하고, 이를 'depositForm' 매개변수로 주입시킨다.

  • 즉, 'getMemberId()', 'getAmount()', 'getPolicy()' 메서드를 통해 사용자가 입력한 데이터를 'depositService.deposit()' 메서드에 전달한다.

  • 리턴되는 "redirect:/core"는 클라이언트를 "/core" 경로로 다시 이동시키는 것을 의미한다.


HTTP


1. {이름 : 땅울림, 보너스 선택 : 정률 보너스, 입금할 금액 : 10000}을 선택하고 ‘입금’ 버튼을 클릭했을 때의 메세지

  • 메서드 POST : 요청 내역 처리

2. 요청을 완료하기 위해 유저 에이전트의 추가 조치로 302 Found를 확인할 수 있는 메세지

  • 302 Found : 리다이렉트시 요청 메서드가 GET으로 변하고, 본문 제거 가능

  • 일시적인 리다이렉션

  • URL이 이미 POST->GET으로 리다이렉트 되어 새로 고침 해도 GET으로 결과 화면만 조회


4. 클라이언트의 "/core" 경로에 대한 새로운 요청


5. 요청 URL과 매칭되는 컨트롤러 검색

  • 요청 URL : "/core". 이는 DispatcherServlet에 전송된다.

  • 전송 방식 : GET 방식으로 전송된다.

HomeController.java

  • 웹 요청 경로가 "/core"이므로 등록된 컨트롤러 빈 중에서 "/core" 요청 경로를 처리할 컨트롤러를 리턴한다.

6. 처리 요청

DispatcherServlet은 RequestMappingHandlerMapping이 찾아준 컨트롤러 객체를 처리할 수 있는 HandlerAdapter 빈에게 요청 처리를 위임한다.


7. HandlerAdapter

HandlerAdapter는 컨트롤러의 알맞은 메서드를 호출해서 요청을 처리하고 그 결과를 ModelAndView라는 객체로 변환해서 DispatcherServlet에 리턴한다.

HomeController.java

클라이언트가 /core 경로로 GET 요청을 보내면,

  • 'home()' 메서드로 Mapping된다.

  • 리턴되는 "core/home"은 뷰 이름을 의미한다.


8. JSP를 위한 ViewResolver

DispatcherServlet은 결과를 보여줄 뷰를 찾기 위해 ViewResolver 빈 객체를 사용한다.

  • 컨트롤러의 실행 결과를 받은 DispatcherServlet은 ViewResolver에게 뷰 이름에 해당하는 View 객체를 요청한다.

  • InternalResourceViewResolver는 "prefix + 뷰이름 + suffix"에 해당하는 경로를 뷰 코드로 사용하는 InternalResourceView 타입의 View 객체를 리턴한다.

prefix : /templates/
suffix : .html
최종 경로 : /templates/core/home.html

9. 응답 생성

DispatcherServlet은 ViewResolver가 리턴한 View 객체에게 응답 결과 생성을 요청하고, 이를 웹 브라우저에 전송한다.


@jgw1202
Copy link

jgw1202 commented Aug 12, 2024


홈 화면에서 "입금" 버튼을 누르면 입금 화면으로 이동합니다.

입금 화면에서는 등록되어있는 회원 선택, 고정/정률 보너스 선택, 입금 금액 입력을 작성한 후 "입금" 버튼을 눌러 입금하는 구조를 띄고 있습니다.

먼저 사용자는 홈 화면에서 입금 버튼을 누르면 /core/deposit 요청 경로를 디스패쳐 서블릿에게 전달한 후 해당 컨트롤러를 탐색하게 됩니다.

  • DepositController.java
package landvibe.springintro.core.deposit.web;

import landvibe.springintro.core.deposit.service.DepositService;
import landvibe.springintro.core.member.Member;
import landvibe.springintro.core.member.service.MemberService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
@RequestMapping("/core/deposit")
public class DepositController {
    private final MemberService memberService;
    private final DepositService depositService;

    public DepositController(MemberService memberService, DepositService depositService) {
        this.memberService = memberService;
        this.depositService = depositService;
    }

    @GetMapping
    public String depositForm(Model model) {
        List<Member> members = memberService.findAllMembers();
        model.addAttribute("members", members);
        return "core/deposit/depositForm";
    }

    @PostMapping
    public String deposit(@ModelAttribute DepositForm depositForm) {
        depositService.deposit(depositForm.getMemberId(), depositForm.getAmount(), depositForm.getPolicy());
        return "redirect:/core";
    }
}

사용자가 페이지에서 "입금" 버튼을 클릭합니다.

클릭 이벤트로 인해 브라우저는 /core/deposit 경로로 GET 요청을 보냅니다.

서버는 이 GET 요청을 DepositController의 depositForm 메서드로 전달합니다. 이 메서드는 @RequestMapping("/core/deposit") 어노테이션에 의해 매핑됩니다.
depositForm 메서드는 MemberService를 사용하여 모든 회원 목록을 조회하고, 이를 모델에 추가합니다. 그런 다음, depositForm.html 템플릿을 반환하여 입금 폼을 사용자에게 렌더링합니다.

서버는 depositForm.html 페이지를 브라우저로 반환합니다.

th:each, th:value, th:text는 Thymeleaf 템플릿 엔진에서 사용되는 속성입니다.
HTML 요소에 동적으로 데이터를 바인딩하는 데 사용됩니다.
이들 속성은 특히 Thymeleaf의 반복 처리와 데이터 바인딩에 유용합니다.

폼 데이터 제출

사용자가 웹 폼에서 다음과 같은 정보를 입력했다고 가정합니다:

회원 이름: "홍길동"
보너스 정책: "정률 보너스"
입금할 금액: 10,000원

사용자가 "입금" 버튼을 클릭하면, 폼 데이터가 POST 요청으로 서버의 /core/deposit URL로 전송됩니다.

서버의 DepositController에서 요청 처리

@PostMapping 애너테이션은 /core/deposit 경로로의 POST 요청을 처리할 메서드를 지정합니다.
이 애너테이션을 통해 deposit 메서드는 POST 요청을 처리할 수 있습니다.

Spring MVC는 요청 본문에서 폼 데이터를 추출하여 DepositForm 객체를 생성하고, 이 객체의 필드에 폼 데이터 값을 바인딩합니다.
예를 들어, DepositForm 객체는 다음과 같은 값을 가집니다:
memberId: 선택한 회원의 ID (예: 1)
amount: 입력한 금액 (10,000)
policy: 선택한 보너스 정책 (예: "rateBonusPolicy")

DepositController.deposit 메서드는 DepositForm 객체의 데이터를 사용하여 DepositService의 deposit 메서드를 호출합니다.

  • DepositServiceImpl.java - deposit 메소드
 @Override
    public void deposit(Long memberId, Long amount, String policy) {
        Member member = memberRepository.findById(memberId);
        Money finalDepositMoney = calculateFinalDepositMoney(member.getRole(), policyMap.get(policy), amount);
        member.deposit(finalDepositMoney);
    }

호출되는 메서드의 매개변수는 memberId, amount, policy입니다.

DepositService에서 입금 처리

memberRepository.findById(memberId)를 사용하여 데이터베이스에서 해당 회원을 조회합니다.
조회된 Member 객체는 입금 처리에 사용됩니다.

  • MemoryMemberRepository.java - findById 메소드
  @Override
    public Member findById(Long id) {
        return store.get(id);
    }

위에서 봤던 deposit 메소드에서,
policyMap.get(policy)를 통해 선택한 보너스 정책(BonusPolicy 객체)을 가져옵니다.
policy.calculate(member.getRole(), amount)를 호출하여 보너스 금액을 계산합니다.
계산된 보너스 금액과 입력된 입금 금액을 합산하여 최종 입금 금액을 결정합니다.

Money finalDepositMoney = new Money(amount + bonusAmount)를 사용하여 최종 입금 금액을 설정합니다.
member.deposit(finalDepositMoney)를 호출하여 해당 회원의 계좌에 입금합니다.

사용자 리다이렉션

DepositController.deposit 메서드는 처리 후 "redirect:/core"를 반환합니다.
이 반환 값은 브라우저에게 /core URL로 리다이렉트하라는 지시를 의미합니다.

브라우저는 /core URL로 새 요청을 보내고, 서버는 이에 대한 응답을 생성하여 사용자에게 반환합니다.

@hongsujin2eeZyo
Copy link

입금 폼을 제출하는 과정에서 일어나는 모든 동작을 웹 브라우저에서 서버에 요청을 보내고 서버가 응답하는 이 과정은 웹 애플리케이션의 요청-응답으로 이루어져있으며, 주요 단계를 다음과 같이 정리할 수 있음.

1. 브라우저에서 입금 폼 제출 (클라이언트 측)

클라이언트(사용자)는 이름, 보너스 선택, 입금할 금액을 입력하고, 입금 버튼을 클릭합니다. 이 정보들은 HTML 폼의 필드에 포함되어 있다.
사용자가 ‘입금’ 버튼을 클릭하면, "/core/deposit" 요청경로를 DispatcherServlet에 전달을 하고 해당 컨트롤러를 탐색한다.
브라우저는 폼 데이터를 서버에 전송한다. 이때 폼은 POST 메서드를 사용하여 서버의 특정 엔드포인트로 데이터를 전송한다.

2. HTTP 요청 생성 및 전송

브라우저의 역할:
브라우저는 사용자가 제출한 폼 데이터를 JSON 형식으로 변환한 뒤 HTTP 요청을 생성한다.
이 요청은 HTTP 헤더와 함께 전송된다.

네트워크 전송:
브라우저는 이 요청을 네트워크를 통해 서버로 전송한다. 이 과정은 인터넷 프로토콜 스택(TCP/IP)을 통해 처리된다.

3. 서버에서 요청 수신 및 처리 (서버 측)

요청 수신:
서버는 클라이언트로부터 POST 요청을 수신합니다. 서버의 특정 컨트롤러가 이 요청을 처리할 준비가 되어 있어야 한다.

요청 매핑:
서버는 해당 URL과 HTTP 메서드에 매핑된 메서드를 찾아 요청을 전달한다. Spring MVC를 사용하는 경우, 이 메서드는 @PostMapping과 같은 어노테이션으로 매핑된다.

폼 데이터 파싱 및 유효성 검사:
서버는 요청 본문에서 폼 데이터를 추출하고, 이를 Java 객체로 매핑한다. 이 과정에서 유효성 검사가 이루어질 수 있음. 예) 입금할 금액이 0보다 작으면 오류 발생

서비스 호출: 서버의 컨트롤러는 비즈니스 로직을 처리하기 위해 서비스 계층을 호출한다.
이때, @PostConstruct 어노테이션을 통해 사전에 설정된 테스트용 데이터가 존재할 수 있다. 이 데이터는 애플리케이션 초기화 시점에 서비스에 로드된다.

4. 비즈니스 로직 처리

보너스 계산: 정률 보너스를 선택한 경우, 입금 금액의 일정 비율을 추가하는 로직이 수행될 수 있다.

데이터베이스 업데이트: 입금 처리 후, 서버는 데이터베이스를 업데이트하여 사용자의 잔액을 조정한다.

응답 생성: 비즈니스 로직이 완료되면, 서버는 성공 또는 실패 응답을 생성한다.

5. 서버에서 클라이언트로 응답 전송

응답 생성: 서버는 HTTP 응답을 생성하고, 이 응답에는 상태 코드(예: 200 OK)와 메시지가 포함된다.

네트워크 전송: 응답은 다시 네트워크를 통해 클라이언트로 전송된다.

6. 클라이언트에서 응답 처리

브라우저가 응답 수신: 브라우저는 서버로부터의 응답을 수신하고, 이 응답에 따라 화면을 갱신하거나 사용자에게 알림을 표시한다.

UI 업데이트: 예를 들어, “입금 성공” 메시지가 표시되거나, 잔액이 갱신된 내용을 반영하여 사용자 인터페이스가 업데이트된다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants