Spring MVC 요청 처리 과정

- 클라이언트 요청 수신: 웹 브라우저를 통해 사용자가 웹 애플리케이션에 요청을 보낸다.
- 사용자가 웹 브라우저를 열고 로그인 페이지의 URL을 입력
- 이 요청은 특정 URL(예: "/login")로 서버에 전송
- 요청 처리 시작: 요청은 서블릿 컨테이너(예: 톰캣)에 도착하며, 서블릿 컨테이너는 요청을 처리하기 위해 DispatcherServlet을 호출한다.
- 톰캣의 모든 URL에 대한 내용을 web.xml에 추가되어있음
-
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0" metadata-complete="true"> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
- Dispatcher 역할: Dispatcher는 클라이언트의 요청을 받아들여 적절한 핸들러(컨트롤러)를 찾아주는 역할을 한다. 이렇게 찾은 핸들러에게 요청을 전달하여 처리를 진행한다. 따라서 Dispatcher는 요청을 분배하는 중요한 역할을 담당하며, 이러한 역할을 수행하는 구체적인 구현체가 DispatcherServlet이다. DispatcherServlet은 Spring 프레임워크에서 제공하는 Front Controller이다. 즉 Spring MVC에서 프론트 컨트롤러패턴( 중앙 집중식 컨트롤러를 사용하는 디자인 패턴 )을 구현하는 구체적인 구현체로서, 클라이언트의 요청을 받아들이고 적절한 핸들러를 찾아 Dispatcher에게 전달한다. 이후 Dispatcher는 해당 핸들러에게 요청을 전달하여 처리를 진행한다. 이를 통해 요청과 응답의 처리 흐름을 효율적으로 관리할 수 있다.
- Dispatcher는 해당 URL("/login")을 분석하여 이 요청을 처리할 적절한 컨트롤러(핸들러)를 선택
- 예를 들어, "/login" URL은 로그인 페이지를 제공하는 컨트롤러를 호출
-
<?xml version="1.0" encoding="UTF-8"?> <!-- dispathcer-servlet.xml / 매핑정보 --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- /index url요청이 오면 IndexController을 객체화해서 호출 --> <!-- id엔 url, 그리고 url을 가지고 컨테이너에서 클래스를 꺼낸다.패키지명까지 씀 --> <bean id="/index" class="com.newlecture.web.controller.IndexController"> <!-- collaborators and configuration for this bean go here --> </bean> </beans>
- Controller 처리: 컨트롤러는 비즈니스 로직을 실행하고, 사용자의 요청을 처리하기 위해 비즈니스 로직을 실행한다. 이 과정에서 컨트롤러는 필요한 데이터를 수집하고, 이를 model 객체에 담아서 준비한다. 이후 컨트롤러는 model 객체를 DispatcherServlet에 반환한다.
- 선택된 컨트롤러는 사용자의 요청에 따라 로그인 페이지를 보여주는 비즈니스 로직을 실행
- 사용자의 입력을 받아들이고, 로그인 정보를 검증하는 등의 작업을 수행
- 컨트롤러는 로그인 페이지를 보여주는 뷰를 생성하고, 그 결과를 DispatcherServlet에 반환
- 응답 생성: DispatcherServlet은 컨트롤러로부터 받은 결과를 적절한 뷰에 전달하여 화면에 표시할 수 있도록 한다.
- DispatcherServlet은 해당 뷰를 클라이언트에게 반환하여 사용자에게 보여줌
- 결과 표시: 뷰는 사용자에게 보여진다. 여기에서 사용자의 출력 데이터를 model이라고 하며 앞에서 보았듯 컨트롤러에서 뷰로 전달되는 데이터를 담고 있다. 뷰가 이를 템플릿 엔진을 통해 화면에 표시한다.
Dispatcher와 Controller를 분리하는 이유
Dispatcher는 요청을 받아 적절한 컨트롤러에게 전달하는 역할을 담당하고, Controller는 비즈니스 로직을 처리하는 역할을 담당한다. 이를 통해 각 컴포넌트가 한 가지 책임만을 가지고 있어 코드의 구조가 명확해지고, 유지보수가 용이해진다.
Controller 작성
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
dispatcher-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- dispathcer-servlet.xml / 매핑정보 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- /index url요청이 오면 IndexController을 객체화해서 호출 -->
<!-- id엔 url, 그리고 url을 가지고 컨테이너에서 클래스를 꺼낸다.패키지명까지 씀 -->
<!-- bean 부분은 컨테이너에 담겨진다. -->
<bean id="/index" class="com.newlecture.web.controller.IndexController">
<!-- collaborators and configuration for this bean go here -->
</bean>
</beans>
IndexController.java
public class IndexController implements Controller{
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 콘솔에 "index controller" 출력
System.out.println("index controller");
// ModelAndView 객체 생성
ModelAndView mv = new ModelAndView();
// "data"라는 키와 "Hello Spring MVC"라는 값을 추가
mv.addObject("data", "Hello Spring MVC");
// 뷰 이름을 "index.jsp"로 설정
mv.setViewName("index.jsp");
// ModelAndView 객체 반환
return mv;
}
}
implements Controller
- 이 부분은 IndexController 클래스가 Controller 인터페이스를 구현한다는 것을 나타낸다.
- Spring MVC에서 컨트롤러는 들어오는 HTTP 요청을 처리하고, 처리한 후 적절한 응답을 반환하는 Java 클래스이다.
- Controller 인터페이스를 구현함으로써 해당 클래스는 Spring MVC 프레임워크가 요청을 처리할 때 호출하는 handleRequest 메서드의 구현을 제공한다.
ModelAndView
- ModelAndView는 Spring MVC에서 제공하는 클래스로, 모델 데이터와 논리적인 뷰 이름을 모두 포함한다.
- 모델 데이터(Model Data): 모델 데이터는 사용자에게 보여줄 정보를 포함하는 객체, 예를 들어, 웹 페이지에 사용자의 이름, 이메일 주소 또는 기타 정보를 표시하려면 해당 정보를 모델 데이터에 담아야 한다.
- 뷰 이름(View Name): 뷰 이름은 사용자가 볼 실제 웹 페이지의 이름, 이것은 사용자에게 보여질 화면의 일종의 식별자이다.
- 컨트롤러는 모델 데이터를 ModelAndView에 추가하고, 사용자에게 보여질 뷰의 이름도 설정한다. 그런 다음 Spring MVC는 ModelAndView에 있는 모델 데이터를 해당하는 뷰에 전달하여 사용자에게 최종 결과를 표시한다.
- 제공된 코드에서는 "mv"라는 ModelAndView 객체를 생성하고 사용하여 모델 데이터를 저장하고(이 경우 키가 "data"이고 값이 "Hello Spring MVC"인 키-값 쌍), 논리적인 뷰 이름("index.jsp")을 설정하였다.
- 마지막으로, ModelAndView 객체가 handleRequest 메서드에서 반환되는데, 이는 Spring MVC 프레임워크에게 어떤 뷰를 렌더링하고 어떤 모델 데이터를 채울지 알려준다.
* 렌더링: 서버에서 동적으로 생성된 콘텐츠를 사용자가 볼 수 있는 형태로 화면에 표시하는 과정
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Hi ${data}</h1>
</body>
</html>
과정은 이렇다.
- web.xml 설정
- <url-pattern>/*</url-pattern>을 사용하면 모든 URL 요청이 DispatcherServlet으로 전송된다.
- DispatcherServlet 역할
- 모든 클라이언트 요청을 받아들이고 적절한 핸들러(컨트롤러)를 찾는다.
- 컨트롤러에 요청을 전달하여 처리를 진행한다.
- Dispatcher와 Controller
- Dispatcher는 클라이언트 요청을 분배하는 역할을 수행하며, 이를 위해 DispatcherServlet이 사용된다.
- Controller는 실제 비즈니스 로직을 실행하고, 요청을 처리한다.
- 문제점
- mv.setViewName("index.jsp");에서 설정한 뷰 이름은 실제 JSP 파일의 경로가 아닌 뷰 이름을 나타낸다.
- DispatcherServlet은 뷰 이름을 실제 JSP 파일의 경로로 매핑하는 뷰 리졸버를 사용하며, 이를 통해 실제 JSP 파일을 찾는다.코드에서는 dispatcher-servlet.xml로 오면 index.jsp내용을 찾을 수가 없다.
- 해결책
- <url-pattern>/</url-pattern>로 *을 뺀 것으로 수정하여 DispatcherServlet이 리소스를 직접 처리하지 않고 컨트롤러에게 전달하도록 설정한다.
- 이를 통해 DispatcherServlet은 컨트롤러를 통해 요청을 처리하고, 컨트롤러는 적절한 처리 후 뷰 이름을 반환하여 DispatcherServlet이 실제 JSP 파일의 경로를 찾아 응답으로 전달할 수 있게 된다.

View 페이지를 위한 위치
현재 코드에서 index.jsp를 실행할 경우

이렇게 브라우저에 출력이 되는 것을 보게된다. 이는 올바르지 못하며 사용자가 컨트롤러를 건너뛰고 뷰 요청을 가능하게 해서는 안된다. 컨트롤러에서 정의된 비즈니스 로직이 실행되지 않고 데이터 없이 뷰가 표시될 수 있으며 이는 웹 애플리케이션의 보안 및 구조적인 일관성을 위해 바람직하지 않다. 따라서 사용자가 직접 뷰를 요청할 수 없도록 설정해야 한다.
해결책

WEB-INF는 비공개 폴더여서 접근하지 못하므로 하위에 view폴더를 만들어 jsp를 위치시킨다.
컨트롤러에서는 WEB-INF 폴더로의 접근이 가능한데 이유는 사용자 즉 클라이언트에서 WEB-INF의 요청이 불가능한것이지 서버에서 WEB-INF의 요청은 가능하기 때문이다.
IndexController에서 코드를 아래와 같이 수정하였다.
mv.setViewName("/WEB-INF/view/index.jsp");


mv.setViewName("WEB-INF/view/index.jsp"); 로 /가 빠진다면 상대경로로 찾게되므로 위처럼 절대경로로 써주는 것이 바람직하다.
뷰 리졸버란?
뷰 리졸버(View Resolver)는 Spring MVC에서 사용되는 뷰(View)를 찾아주는 기능을 담당하는 클래스이다. 클라이언트의 요청에 대한 응답으로 보여줄 뷰를 결정하고 실제 뷰의 경로를 해석하여 제공한다. 가장 일반적으로 사용되는 뷰 리졸버는 InternalResourceViewResolver이며 뷰 이름을 기반으로 JSP 파일을 찾아내어 제공한다. 뷰 리졸버는 Spring의 빈(Bean)으로 등록되어야 하며 설정은 Spring의 설정 파일(XML 또는 Java Config)에서 수행된다.
dispatcher-servlet.xml에 추가
<!-- 뷰 리졸버 설정을 통해 뷰 이름을 JSP 파일에 매핑 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 뷰 파일 위치의 접두사 설정 -->
<property name="prefix" value="/WEB-INF/view/"></property>
<!-- 뷰 파일 확장자 설정 -->
<property name="suffix" value=".jsp"></property>
</bean>
IndexController.java
public class IndexController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 콘솔에 "index controller" 출력
System.out.println("index controller");
// Model과 View를 결정하는 ModelAndView 객체 생성(생성자 오버로드)
ModelAndView mv = new ModelAndView("index");
// "data"라는 키와 "Hello Spring MVC"라는 값을 모델에 추가
mv.addObject("data", "Hello Spring MVC");
// ModelAndView 객체 반환
return mv;
}
}
정적파일 실행

test.jsp 실행 시

answeris.png 이미지(정적파일) 실행 시

둘의 차이가 있는 이유는?
web.xml에서
<url-pattern>/</url-pattern>
/ * 는 jsp까지 막겠다는 의미이고
/ 는 기본적인 정적인 파일은 막으나 jsp는 허용한다.
해결책

dispatcher-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 디스패처 서블릿 설정 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- /index URL 요청에 대한 매핑 -->
<!-- "/index" 이름을 가진 빈을 정의하고 IndexController 클래스와 연결 -->
<!-- id 속성은 주요 빈 식별자이며 name 속성은 선택적인 별칭을 제공 -->
<!-- 빈 정의는 컨테이너 내에 포함됩니다. -->
<bean name="/index" class="com.newlecture.web.controller.IndexController">
<!-- 이 빈과 관련된 협업 및 구성 설정 -->
</bean>
<!-- 이름 또는 id를 지정하지 않고 IndexController의 또 다른 인스턴스를 등록 -->
<bean class="com.newlecture.web.controller.IndexController" />
<!-- 뷰 리졸버 설정을 통해 뷰 이름을 JSP 파일에 매핑 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 뷰 파일 위치의 접두사 설정 -->
<property name="prefix" value="/WEB-INF/view/"></property>
<!-- 뷰 파일 확장자 설정 -->
<property name="suffix" value=".jsp"></property>
</bean>
<!-- mapping="/resource/**" : /**안에 들어오는 url은 location="/resource/"에 두겠다. -->
<mvc:resources location="/resource/" mapping="/resource/**"></mvc:resources>
</beans>
태그로 이루어져있는 명령어 집합들(ex) bean, mvc)이다. 그리고 이 태그들을 정의한 파일이 스키마 파일이다.
이 스키마 파일은
이 스키마 파일에 대한 식별자는
이 식별자는 너무 길기 때문에 이를 대신하기 위한 namespace는
xmlns:mvc="http://www.springframework.org/schema/mvc"
로 지정한다. 따라서 mvc태그를 사용 가능하게 된다.
<mvc:resources> 요소를 사용하여 정적 자원(이미지, CSS, JavaScript 등)을 처리하는 방법을 설정한다.
<mvc:resources location="/static/" mapping="/**"></mvc:resources>
location 속성은 실제 파일 시스템 경로를 나타내고
mapping 속성은 웹 애플리케이션에서 해당 리소스에 접근하는 URL 패턴을 지정한다.
여기서는 "/static/**" URL 패턴으로 시작하는 모든 요청을 "/WEB-INF/static/" 디렉토리에서 처리하도록 지정하는 것을 말한다. "/static/" 디렉토리의 모든 파일 및 하위 디렉토리를 대상으로 하는 경우이므로 "mvc:resources" 요소의 "mapping" 속성에 "/**"를 사용하는 것이 "/*"하는 것보다 더 적절하다.

http://localhost:8080/static/images/banner2.svg가 아니라 http://localhost:8080/images/banner2.svg인 이유는?
/static/ 경로 아래의 모든 파일을 /으로 매핑한다고 설정했기 때문이다. 전자처럼 나오기 위해서는
<mvc:resources location="/static/" mapping="/static/**">로 설정해야한다.
참고 자료 출처 :
(27~ 36)
https://www.youtube.com/watch?v=-YHVKPbkSoM&list=PLq8wAnVUcTFUHYMzoV2RoFoY2HDTKru3T&index=27
'Spring' 카테고리의 다른 글
서비스 객체 (0) | 2024.02.29 |
---|---|
컨트롤러 추가 및 View페이지 공통분모 집중화와 필요성 (1) | 2024.02.29 |
AOP (0) | 2024.02.28 |
어노테이션 종류와 적용 및 Java Configuration으로 변경 (0) | 2024.02.28 |
스프링을 이용한 DI (0) | 2024.02.27 |