Dispatcher Servlet과 MVC 패턴에 대해서 알아보자
들어가며
스프링 프레임워크를 이용하여 웹 개발을 시작하려면 Dispatcher Servlet과 MVC 패턴에 대한 기본적인 이해가 필요합니다. 먼저, Servlet이란 무엇인지, 어떻게 동작하는지에 대해 알아보겠습니다. 그리고 MVC 패턴은 어떻게 구성되어 있으며 왜 사용되며, 실제로 어떻게 동작하는지를 자세히 살펴보겠습니다. 이러한 기본적인 이해는 원활한 스프링 기반 웹 개발을 위한 필수적인 요소이기 때문에 이번 시간에 알아보도록 하겠습니다.
Servlet이란?
서블릿은 자바의 웹 프로그래밍 기술 중 하나로, 동적인 웹 페이지를 생성하고 처리하는 역할을 합니다.
웹 페이지에서는 클라이언트와 서버 간의 통신은 웹 어플리케이션을 통해 이루어집니다. 클라이언트가 요청을 하면 WAS(Web Application Server)가 해당 요청을 받아들이고, 웹 애플리케이션 내에서 필요한 처리를 수행한 후에 응답을 생성하여 클라이언트에게 전송합니다. WAS는 이러한 요청과 응답을 처리하는 데 사용되며, 이 과정에서 필요한 데이터를 서버와 주고받게 됩니다. WAS에는 Tomcat,Jetty등과 같이 여러 개가 있습니다.
WAS안에는 서블릿 컨테이너가 포함되어있어 원하는 데이터를 연결하고 데이터를 전달하는 역할을 합니다. 서블릿은 클라이언트의 요청 시에 서블릿 컨테이너를 생성합니다. 생성된 서블릿 컨테이너에서 요청한 값을 처리한 후에 클라이언트에게 값을 전달해 주는 역할을 합니다.
위 그림처럼 서블릿이 중간에 위치해 요청이 들어오면 동적으로 어떤 컨트롤러를 사용하지 동적으로 선택해 주는 역할을 한다.
Servlet을 사용한 여러 요청
서블릿을 통해 여러 요청이 들어왔을 때, 각 요청에 대해 서블릿이 생성되는 상황을 생각해 봅시다.
3명의 클라이언트가 각각 다른 요청을 보냅니다. 이 경우, 각 요청마다 서블릿이 생성됩니다. 이로 인해 같은 코드가 중복으로 생성되고, 유지보수나 성능 측면에서 문제가 발생할 수 있습니다.
Servlet 사용 예시
@WebServlet(name = "blogServlet", urlPatterns = "/servlet/blog/servlet")
public class CustomServlet extends HttpServlet {
private BlogRepository blogRepository = BlogRepository.getInstance();
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// 사용할 Servlet 로직
}
}
직접 서블릿을 통해 사용한다면 HttpServlet를 확장하여 service() 메서드를 오바라이딩 해서 사용하면 된다.
@WebServlet를 통해 서블릿 명을 정하여 컨테이너에 빈으로 등록하며, urlPattenrs을 통해서 url이 패턴으로 등록되게 한다. 이런 식으로 일반 서블릿컨테니어를 등록해 사용할 수 있습니다. 하지만 위에 말했듯이
Dispatcher Servlet
일반 서블릿을 사용했을 때 여러 요청이 들어온다면 위와 같은 문제가 발생합니다. 중복된 문제를 해결하기 위해 Front Controller 패턴을 도입할 수 있습니다. Front Controller는 모든 요청을 하나의 진입점으로 받아들이고, 요청에 따라 적절한 처리를 수행하는 역할을 합니다. 이를 통해 공통된 HTTP 메서드를 한 곳에서 처리할 수 있으며, 서블릿의 중복 생성과 유지보수 문제를 해결할 수 있습니다.
Spring에서는 Front Controller 역할을 하는 것이 Dispatcher Servlet라고 할 수 있습니다. Dispatcher Servlet는 모든 클라이언트에 요청을 받아 컨트롤러로 동적으로 수행할 수 있도록 합니다.
Spring MVC 패턴
스프링 MVC 패턴을 도입하기 전에는 주로 JSP를 이용하여 웹 개발을 진행했습니다.
public class SimpleController extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int num1 = 1;
int num2 = 2;
int sum = num1 + num2;
request.setAttribute("sum", sum);
request.getRequestDispatcher("result.jsp").forward(request, response);
}
}
<body>
<h1>덧셈 결과</h1>
<p>10 + 20 = ${sum}</p>
</body>
위의 코드는 JSP를 통해 웹개발을 한 예시입니다.
이 코드를 보면 SimpleController가 HttpServlet을 상속하여 모든 비즈니스 로직과 뷰 생성을 담당하고 있는 것을 알 수 있습니다. 이러한 형태는 유지보수와 성능 측면에서 좋지 않을 뿐만 아니라, 한 클래스에 너무 많은 역할이 담겨 있어 코드를 이해하고 수정하기가 어렵습니다.
그러나 스프링 프레임워크는 이러한 문제를 해결하기 위해 디자인 패턴을 중추로 한 MVC 패턴을 도입했습니다. MVC 패턴을 통해 코드의 비즈니스 로직과 컨트롤러를 분리하여 유지보수성과 성능 향상을 이끌어냈습니다.
MVC패턴이란?
모델(Model)-뷰(View)-컨트롤러(Controller)를 명칭 하는 패턴이다 MVC패턴을 사용하면 JSP처럼 한 곳 클래스가 여러 기능을 담당했던 것을 기능을 분리하여 유연한 설계를 통해 불필요한 결합을 막을 수 있다.
모델은 뷰에 의해 표현되는 데이터를 캡슐화하고, 컨트롤러는 사용자의 요청을 받아서 비즈니스 로직을 호출에 값을 받아온다. 컨트롤러는 서비스로직에서 받은 데이터를 모델에 담고 뷰를 통해서 데이터가 사용자가 보여주게 된다.
MVC 패턴이 도입될 수 있게 된 큰 요인 중 하나는 IoC(Inversion of Control) 컨테이너를 사용했기 때문입니다. IoC 컨테이너는 스프링 프레임워크의 핵심 기능 중 하나로, 객체의 생명 주기와 의존 관계를 관리하며, 객체를 생성하고 조립하는 역할을 합니다. IoC 컨테이너를 사용하면 개발자는 객체의 생성과 관리에 대한 부담을 덜 수 있으며, 코드의 유연성과 확장성을 높일 수 있습니다.
간단하게 서명하자면 데이터를 직접 Service에서 전달받는 것이 아닌 중간에 IoC컨테이너에 동적으로 값을 컨트롤러에게 주는 것을 말합니다.
(IoC 컨테이너에 대한 자세한 내용은 여기서 설명하기에는 너무 많이 때문에 스프링 공식 문서나 관련 서적을 통해 살펴보시는 것이 좋습니다.)
동작 방식
- 사용자의 요청이 들어오면 Dispatcher Servlet가 모든 요청을 담아둔다.
- 핸들러 매핑에게 요청을 위임하고 요청을 처리한 스프링 컨테이너에서 값을 찾는다.
- 해당 매핑정보가 있다면 어댑터에게 요청 처리를 위임해 준다.
- 컨트롤러는 비즈니스 로직에서 값을 요청을 보내 처리한 값을 받아준다.
- 찾아온 값을 모델에 담고 논리적 뷰 이림을 디스패처 서블릿에게 다시 전달한다.
- 뷰 리졸버는 논리적인 뷰이름을 가지고 해당 엔진의 컨버터가 실행되어 물리적인 위치로 반환한다.
- 반환된 뷰는 렌덩이에 데이터를 사용한다.
- 클라이언트에게 해당 정보를 보여준다
핸들러 매핑(Handler Mapping)은 요청 URL과 핸들러 객체를 매핑하는 인터페이스로, 스프링 MVC에서 요청을 처리하는 컨트롤러를 찾아주는 역할을 합니다. 이를 통해 어떤 요청이 어떤 컨트롤러에 의해 처리되어야 하는지를 결정합니다. 대표적으로 두 가지의 핸들러 매핑이 있습니다.
- BeanNameUrlHandlerMapping: 빈의 이름을 URL 패턴에 매핑하여 요청을 처리하는 핸들러 매핑입니다.
- RequestMappingHandlerMapping: 애노테이션을 사용하여 요청과 컨트롤러 메서드를 매핑하는 핸들러 매핑입니다.
이러한 핸들러 매핑을 통해 요청이 들어오면 적절한 컨트롤러로 라우팅 됩니다. 또한, 스프링 MVC에서는 인터셉터(Interceptor)를 통해 요청 전후에 작업을 수행할 수 있습니다. 인터셉터는 핸들러 매핑과 핸들러 어댑터 사이에 위치하며, 요청 처리 전(pre)과 후(post)에 실행될 수 있습니다. 이를 통해 요청의 전처리나 후처리 작업을 효율적으로 처리할 수 있습니다.
핸들러 어댑터(HandlerAdapter)는 디스패처 서블릿이 요청에 매핑된 핸들러를 호출하는 작업을 돕는 인터페이스입니다. 스프링 MVC에서는 다양한 종류의 핸들러를 처리해야 하기 때문에 이러한 다양성을 처리하기 위해 핸들러 어댑터를 사용합니다. 각 핸들러에 적합한 메서드를 호출하여 해당 핸들러가 요청을 처리할 수 있도록 돕습니다.
뷰리졸버 (ViewResolver)은 문자열로 넘오 온 논리적인 주소를 해당 엔진의 컨버터를 사용해 물리적인 경로의 값을 만들어준다.
@RequestMapping
@RequestMapping은 핸들러 매핑과 핸들러 어댑터를 통해 요청을 처리하는 데 사용되는 스프링 MVC의 핵심 애노테이션입니다. 요청 URL을 지정하여 어떤 메서드가 실행될지를 결정하는 역할을 합니다.
제일 우선순위가 높은 어뎁터와 핸들러이다
@RequestMapping이름을 사용해 만든 핸들러 어댑터이다.
RequestMappingHandlerMapping
RequestMappingHandlerAdapte
RequestMappingHandlerAdapte동작방 식
- 컨트롤로에서 지정한 정보기반으로 데이터 생성 (HttpServletRequest,@RequestParam,@RequestBody 등)
- 데이터 기반을 통해 컨트롤러 호출
- 컨트롤러의 값을 반환 (ModelAndView, @ResponseBody, HttpEntity)
ArgumentResolver
스프링 MVC에서 컨트롤러 메서드의 파라미터를 연결해주는 인터페이스이다. 이를통해서 여러가지의 컨트롤러의 사용되는 메소드의 정보를 사용해 사용가능 메서드를 적절한 타입으로 변환해 매핑하여 메서드에 전달한다.
HandlerMethodArgumentResolver 인터페이스에서 supportsParameter()을 호출해 작성된 정보 중에 넘아온 파미터를 사용가능한지 확인 후에 사용이 가능하다면 객체를 생성해 컨트롤러에 전달해 준다.
다음으로
이렇게, 서블릿을 통한 웹 개발의 기본적인 이해와 스프링 MVC의 구성 요소에 대해 살펴보았습니다. 이를 통해 프론트 컨트롤러 패턴과 MVC 패턴이 어떻게 동작하는지에 대한 개념을 이해할 수 있습니다.