거북이-https://velog.io/@violet_evgadn 이전완료

URL과 Spring MVC 연동 과정 본문

웹 개발/Spring(이론)

URL과 Spring MVC 연동 과정

VioletEvgadn 2023. 2. 27. 15:46

공부하게 된 이유

URL 및 HTTP Protocol을 공부하다보니 궁금한 점이 생겼다.

앞에서 URL에 대해 설명할 때 URL은 "서버 자원이 어디에 위치하는지"를 명시하는 값이다. 따라서 파일명을 입력해야 한다.

 

하지만 우리는 Spring MVC를 사용할 때 아래와 같이 활용한다

@GetMapping("/sample/samplePage")

어떻게 이것이 가능한 걸까?

Client가 "www.sample.com/sample/samplePage"로 접속할 때 서버는 어떻게 이 URL이 "www.sample.com/sample/samplePage/index.html"을 반환하는 것이 아닌 @GetMapping("/sample/samplePage")로 들어가 로직을 수행할 수 있는 것일까?

 

네트워크와의 관계성은 떨어지긴 하지만 궁금하여 찾아보기로 하였다.


Servlet

◎ Servlet이란?

Tomcat의 동작 과정에 대해 알아보기 전 Servlet에 대해 알아볼 필요가 있다.

 

Servlet이란 Web Programming에서 Client의 요청(Request)을 처리하고 결과를 다시 Client에게 전송하는 자바 프로그래밍 기술이다. 이 때 Servlet 클래스의 구현 규칙을 따라야 한다.

 

Servlet에는 몇 가지 특징이 존재한다.

 

먼저 Client 요청에 의해 동적으로 작동한다.

 

두 번째로 Java Thread를 이용해 동작한다는 것이다.

이 점이 꽤나 중요한데 만약 동시에 Servlet에 Request가 도착할 경우 1개 스레드에서 순서대로 처리시키는 것이 아니라 동시에 처리한다는 것이다. 이 때문에 자바 웹 프로그램을 구축할 때 Request가 동시에 도착하였을 때 문제가 생기지 않는지 잘 확인해야 하는 것이다.

 

세 번째로 HTML을 사용하여 Request에 대한 응답을 보내는데 이 때 Java 코드에 HTML이 들어가있다.

 

마지막으로 Servlet은 TCP를 통해 Request 및 Respond를 주고 받으므로 UDP보다 처리 속도가 느리다.

 

◎ Servlet Method

서블릿 라이프 사이클을 위한 3가지 필수적인 메소드가 존재하는데 init(), service(), destory()이다.

 

init()은 서블릿 생명 주기 중 "초기화 단계"에서 호출된다.

클라이언트의 요청이 Servlet Container로 들어올 경우 컨테이너는 해당 Servlet이 메모리에 있는지를 확인한다.

만약 메모리에 Servlet이 없을 경우 init() 메소드를 호출하여 매개변수를 초기화한 뒤 메모리에 적재한다.

Servlet에서 사용하는 매개변수들은 1번만 초기화시키면 되므로 init() 메서드는 처음 한 번만 실행된다.

 

만약 실행 중 서블릿이 변경될 경우 destroy()를 통해 기존 Servlet을 파괴시킨 뒤 다시 init()을 실행시켜 새로운 Servlet을 메모리에 적재한다.

 

service()은 클라이언트의 Request가 들어올 때 마다 호출되는 함수이다.

위에서 말했듯 각각의 요청들은 별도로 나누어진 스레드에서 처리되며 service() 메소드를 통해 HTTP Method의 종류(GET, POST 등)를 판별하고 이에 맞는 메소드(doGet(), doPost() 등)로 Requeset를 전달한다.

 

destroy()는 서블릿 객체가 파괴되어야 할 때(Servlet을 종료시키거나 재시작시켜야 할 경우) 호출된다.

이 함수를 실행시킴으로써 서블릿이 가지고 있던 자원을 반환시킬 수 있다.

 

◎ Servlet Container란?

Servlet의 개념을 이해했다면 Servlet Container는 이해가 쉬울텐데, 단순히 서블릿을 관리해주는 Container이다.

 

Servlet은 만들어졌다고 스스로 동작하지는 않는다.

마치 우리가 자바 코드를 짰더라도 이를 실행시키지 않으면 해당 기능을 사용할 수 없는 것과 마찬가지이다.

 

우리가 자바 코드를 JDK나 JRE를 통해 실행시키듯 Servlet 또한 Servlet 기능을 실행시킬 주체가 필요하다.

그리고 Servlet Container가 이러한 Servlet 실행 역할을 수행하는 것이다.

 

Servlet Container는 Web Server와 Socket으로 통신하며 Request를 받고 이에 대한 처리 결과를 Response로 준다.

대표적인 Servlet Container가 Tomcat 되시겠다.

 

이 Servlet Container는 다양한 역할을 수행한다.

 

먼저 Web Server와 통신하며 Servlet Life Cycle을 관리한다.

 

두 번째로 MultiThread를 지원하고 관리한다.

이전에 말했듯 Servlet은 Thread을 이용하여 동작한다. 즉, Request 1개가 올 때마다 새로운 Thread가 생성되어야 하며 Response를 내보낸 이후에는 Thread가 소멸되어야 한다.

이런 다중 쓰레드를 생성 및 관리해주는 역할을 Servlet Container가 수행함으로써 쓰레드의 안정성 걱정 없이 쓰레드를 사용할 수 있게 된다.

 

마지막으로 보안 관리를 수행한다.

서블릿 컨테이너를 사용할 경우 보안에 관련된 내용을 Servlet Container의 Config 파일(주로 XML)에 기록하기 때문에 자바 코드를 수정하는 일 없이 보안 관리가 가능해진다.

 

위 모든 내용을 정리하자면 아래와 같다.

일반적으로 Web Server는 URL에 적힌 정적인 페이지(파일)만 제공할 수 있다.

하지만 웹 서버는 CGI 어플리케이션을 활용하여 동적인 페이지를 제공할 수 있으며 이를 도와주는 애플리케이션이 Servlet인 것이다.

Servlet Container들은 Servlet들을 관리하고 Web Server와 Socket을 통해 통신하며 동적인 페이지를 위한 Servlet Life Cycle을 실행시키는 것이다.

 

◎ Servlet Life Cycle

1. Client가 Web Server에 HTTP Request를 보냄

 

2. Web Server는 Socket을 통해 Forward Request 형태로 Servlet Container에게 Request를 전달

 

3. Servlet Container는 HttpServletRequest, HttpServletResponse 2개 객체를 생성한다.

 

4. web.xml은 URL을 분석하여 어떤 Servlet에서 Request를 처리해야할지 찾는다.

 

5-1(파란색). 만약 Servlet이 메모리 상에 존재하지 않을 경우 init() 함수를 실행시켜 Servlet을 초기화시킨 뒤 메모리에 로드시키고 5-2 과정을 실행시킨다.

5-2(보라색). service() 메서드를 실행시킴으로써 Request를 처리할 Thread를 새로 생성하고 Request의 POST, GET 여부에 따라 doGet() 또는 doPost()를 호출한다.

 

6. doGet() 혹은 doPost() 메서드를 통해 동적 페이지를 생성한 뒤 HttpServletResponse 객체에 Response를 보낸다.

 

7. Servlet Container는 Reponse를 Web Server측에 보내고 HttpServletRequest, HttpServletResponse 두 객체를 소멸시킨다.

 

8. Web Server는 Server Container측으로 부터 받은 Response를 Response Message로 만든 뒤 Client에게 보낸다.

 

Servlet은 Request별로 스레드를 생성시킨 뒤 분리된 스레드 내부에서 이를 처리한다고 언급했다.

이 때 Servlet 내부에 존재하는 JVM이 분리된 자바 스레드 내부에서 Request 처리하는 것을 지원한다.

그리고 서블릿 컨테이너는 JVM이 처리한 결과를 올바른 장소에 동적으로 반환해준다.

 

마지막으로 위 이미지 상에는 단계를 조금 더 이해하기 쉽게 하기 위하여 Web Server와 Servlet Container를 나눠두었지만 사실은 Web Server 내부에 Servlet Container가 존재하는 형식이라는 것을 알고 가자.


Spring MVC와 Tomcat 동작 방식

◎ JSP

Spring MVC와 Tomcat 연동 과정에서 필수는 아니지만 알면 좋은 개념이 JSP이다.

JSP란 Java Server Page의 약자로써 Java 코드가 들어있는 HTML 코드이다.

 

위에서 말했듯 원래라면 Servlet 내부에 자바 코드 뿐만이 아닌 HTML 코드가 포함되어 있어야 한다.

문제는 그럴 경우 아래 코드처럼 HTML 관련 코드가 너무 길어지고 관리도 힘들어진다.

writer.println("<html>");
writer.println("<head>");
writer.println("</head>");
writer.println("<body>");
writer.println("<h1>Human</h1>");
writer.println("Name : " + user.getName() + "<br/>");
writer.println("Age : " + user.getAge() + "<br/>");
writer.println("Birthday : " + user.getBirthday() + "<br/>");
writer.println("</body>");
writer.println("</html>");
writer.close();

 

이런 문제를 해결하기 위해 Servlet과 반대되는 개념으로 HTML 내부에 Java Code를 넣은 JSP라는 기술이 도입되었다.

 

그렇다면 JSP는 어떻게 동작할까?

일단 Servlet에서 Thread를 생성하는 과정까지는 동일하다. 이후 Servlet에서 데이터를 처리한 뒤 JSP Container라는 곳에 방문하게 된다.

 

JSP Container는 JSP 파일을 읽고 Generate 작업을 통해 JSP 파일을 Servlet 파일(.java) 파일로 변환한다.

이렇게 생성된 Servlet 파일은 다시 class 파일로 컴파일 되어 실행된다.

최종적으로 이 class 파일은 HTML을 반환하는 코드로 변환되게 되며 이 결과물이 Web Server를 통해 사용자에게 전달되는 것이다.

 

자바 Spring MVC 패턴에서 JSP를 사용하는 경우로 예시로 들면 이해가 더욱 쉬울 것이다.

예를 들어 Mapping된 @RequestMapping 메서드가 "return index"를 통해 index.jsp를 반환한다고 가정하자.

그렇다면 일단 Request를 처리하는 메서드가 모두 실행될 것이다.

이후 "return index"를 만나 index.jsp라는 JSP 파일로 이동한다. 그리고 index.jsp를 Servlet 파일(Java 파일)로 변환한 뒤 컴파일 시켜 class 파일로 만들고 실행시킨다. 그렇다면 index.jsp에 포함된 HTML 코드 및 자바 코드를 통해 만들어지는 HTML 파일을 반환하는 자바 코드가 생성된 이후 실행될 것이며, 최종적으로 Servlet 1개가 HTML 코드를 반환하는 것처럼 동작하는 것이다.

 

물론 JSP를 사용할 경우 JSP를 변환시키고 컴파일시켜 실행시키는 추가 과정이 존재하므로 Servlet 1개를 실행하여 HTML 페이지를 반환하는 것보다는 느리지만 편의성면에서는 매우 큰 강점을 가진다고 할 수 있다.

또한 첫 구동에서 JSP를 class 파일로 생성해두면 두 번째부터는 변환과정 및 컴파일 과정을 생략할 수 있으므로 Servlet 1개를 실행한것과 거의 동일하게 동작한다.

 

추가로 서버에 실행 파일을 배포시킬 경우 Java Source Code를 변경하면 컴파일 시킨 뒤 서버를 재시작시켜야 변경 사항이 적용되지만 JSP는 수정 즉시 서버에 반영되는 것을 볼 수 있다.

이는 JSP의 소스를 수정하고 수정된 JSP를 방문할 경우 다시 Servlet 파일로 변환한 뒤 컴파일 하여 실행하는 과정이 일어나기 때문이다.

즉, JSP Container 측에서 다시 컴파일하여 변경된 class 파일을 사용해 HTML 코드를 반환하기 때문에 서버 재시작 없이 바로 변경사항이 적용되는 것이다.

Servlet Container는 기존에 컴파일된 class 파일만 사용하므로 Servlet Container를 재시작하여 새로운 컴파일 파일을 먹여줘야 변경 사항이 적용되는 것이다.

 

◎ Tomcat과 Spring MVC 프로젝트를 통해 동적인 페이지를 반환하는 과정

1. Web Server에 HTTP Request가 도착함

 

2. Tomcat의 Coyote가 HTTP Request를 Tomcat(Servlet Container)으로 받아옴

 

3. Tomcat이 HttpServletRequest, HttpServletResponse 객체를 생성함

 

4. web.xml을 통해 Spring MVC 프로젝트 Servlet에서 Request를 처리한다는 것을 분석

 

5. Spring 프로젝트의 Front Controller인 DispatcherServlet 클래스가 요청을 받아 service() 메서드 호출

  • GET, POST 방식에 따라 doGet() 혹은 doPost()로 분기하여 처리
  • DispatcherServlet이 생성되어 있지 않은 경우 init() 먼저 실행

 

7. HandlerMapping을 통해 Mapping Controller를 조회

 

8. HandlerAdapter를 사용하여 7번 과정에서 찾은 Controller에 Request 전달

 

9. (주로 Controller → Service → DAO → DB → DTO → Service → Controller 순서로) 개발자가 구현한 로직이 수행됨

 

10. 개발자가 구현한 로직을 모두 수행한 Controller는 Model(모델) 요청에 맞는 View(뷰) 정보를 담아 DispatcherServlet에게 전송

 

11. DispatcherServlet은 View 정보 및 데이터를 ViewResolver에게 전달

 

12. ViewResolver는 View 정보에 맞는 JSP 파일을 찾고 JSP Container에게 JSP 처리를 요청

 

13. JSP Container는 JSP 파일 및 전달받은 데이터를 통해 완성된 HTML 코드를 ViewResolver에게 반환

 

14. ViewResolver는 완성된 HTML 파일을 DispatcherServlet에게 전달

 

15. 완성된 HTML 파일(정적 파일)을 DispatcherServlet이 HttpServletResponse 객체에 담음

 

15. HttpServletResponse는 Web Server에게 Response를 보내고 HttpServletResponse와 HttpServlerRequest 객체는 소멸

 

16. Web Server는 완성된 정적 페이지(HTML 파일)를 Response Message에 담아 Client(브라우저)에게 보냄

 

이런 과정을 거쳐 원래라면 정적인 파일의 경로를 입력하는 URL로 동적인 파일을 얻어올 수 있는 것이다.

'웹 개발 > Spring(이론)' 카테고리의 다른 글

Gradle이란  (0) 2022.11.25
WAR와 JAR  (0) 2022.10.17
Spring Web 계층  (0) 2022.09.19
DTO 클래스  (0) 2022.09.19
Spring의 데이터 처리 방법  (0) 2022.09.19
Comments