WEEK 5-2: MVC

ensalada.de.pollo·2025년 5월 8일

be

목록 보기
16/44

Template Engine

동적인 웹 페이지를 생성하기 위해 사용되는 도구입니다. 템플릿을 기반으로 정적인 부분과 동적인 부분을 결합하여 HTML, XML 등의 문서를 생성하는 역할을 합니다.

흔히 말하는 UI를 만들고, SSR(Server Side Rendering)에 사용이 됩니다.

왜 필요하게 됐을까?

Java 코드로 HTML을 만들어 내는 것이 아닌, HTML 문서에 동적으로 변경해야 하는 부분만 Java 코드를 삽입하는 것이 더 편하기 때문입니다.

대표적인 템플릿 엔진

  • Thymeleaf
  • JSP(Java Server Pages)
  • FreeMarker
  • Velocity
  • Mustache

MVC 패턴

하나의 Servlet 또는 JSP로 처리하던 것들을 Model, View, Controller 영역으로 나눈 것입니다. 보통의 Web Application이 사용하는 패턴입니다.

Servlet의 동작 순서

  1. 사용자가 브라우저를 통해 서버에 HTTP Request, 즉, API 요청을 보냅니다.
  2. 요청을 받은 Servlet Container는 HttpServletRequest, HttpServletResponse 객체를 생성합니다.
  3. 설정된 정보(URL, HTTP Method)를 통해 어떤 Servlet에 대한 요청인지 찾습니다.
  4. 알맞은 Servlet에서 service 메서드를 호출한 뒤 브라우저의 요청 Method(doGet(), doPost() 등)에 따라 메서드를 호출합니다.
  5. 서버에서 응답을 생성한 뒤 HttpServletResponse 객체에 응답을 담아 브라우저에 반환합니다.
  6. 응답이 완료되면 생성되었던 HttpServletRequest, HttpServletResponse 객체는 소멸됩니다.

JSP의 구조

HTML 코드 안에 Java Code가 들어가 있는 형태입니다. 이에 반해 Servlet은 Java Code로 이루어져 있습니다.

Servlet, JSP 방식의 문제점

Servlet이나 JSP만 가지고 비즈니스 로직과 View 모두 처리를 하게 된다면, 너무 많은 역할을 하게 되고 유지보수가 굉장히 까다로워집니다.

Servlet만을 사용한 경우는 View를 위한 코드와 비즈니스 로직이 모두 Servlet 안에 존재하기 때문에 유지보수가 어렵고,

JSP를 사용한 경우는 View를 분리하였어도, 비즈니스 로직의 일부가 JSP 파일 안에 남아있기 때문에 여전히 유지보수가 어렵습니다.

MVC의 핵심

View가 분리된 이유의 핵심은 변경입니다.
기획이 변하지 않는다면 비즈니스 로직과 View의 수정 원인은 별개입니다. 이전에는 비즈니스 로직과 View가 한데 모여있었기 때문에 코드 자체를 수정해야 했지만, 로직과 View가 분리되어 있다면 수정이 필요한 부분만 수정을 하면 됩니다.

쉽게 말해서 서로 연관이 없는 코드끼리 붙어있던 것을 떼놓은 것입니다.

MVC의 구조

Controller

  1. HTTP Request를 전달받아 파라미터를 검증합니다.
  2. 비즈니스 로직을 실행합니다.
    • 비즈니스 로직을 Controller에 포함하게 되면, Controller가 너무 많은 역할을 담당하게 됩니다. 그래서 일반적으로 Service Layer를 별도로 만들어서 처리합니다.
    • Database와 상호작용 하는 부분은 Repository Layer로 구분하여 둡니다.
    • Controller도 비즈니스 로직을 포함할 수는 있지만, 일반적으로는 Service Layer를 호출하는 역할을 담당합니다.
  3. View에 전달할 결과를 조회하여 Model 객체에 임시로 저장합니다.

Model

View에 출력할 Data를 저장하는 객체입니다.
책임이 분리되어 있기 때문에 View는 비즈니스 로직이나 Data 접근을 몰라도 되고 View Rendering에 집중하면 됩니다.

View

Model 객체에 담겨져 있는 Data를 사용하여 화면을 Rendering 합니다.

MVC 패턴의 문제점

MVC 패턴을 적용했을 때 일어나는 일을 살펴보겠습니다.

  • View로 이동하는 forward가 항상 중복 호출됩니다.
  • View의 path를 입력하는데, jsp 파일의 경로 혹은 이름이 바뀌면 path를 입력했던 부분을 수정해야 하고, jsp 이외의 확장자를 사용하려면 전체가 변경되어야 합니다.
  • HttpServletResponse 객체를 사용하는 경우가 적습니다.
  • 공통 기능(log, 인증, 인가 등)이 추가될 수록 Controller에서 처리해야 하는 부분들이 많아집니다.

프론트 컨트롤러 패턴

Servlet(Controller)이 호출되기 전 공통 기능을 하나의 Servlet에서 처리해주는 패턴입니다. 프론트 컨트롤러(Servlet) 하나에 모든 요청이 들어오게 됩니다.

프론트 컨트롤러의 역할

  • 모든 요청을 하나의 프론트 컨트롤러가 받습니다.
  • 공통 기능을 처리합니다.
  • 요청을 처리할 수 있는 Controller를 찾아서 호출합니다.
  • 프론트 컨트롤러를 제외한 Controller는 Servlet을 사용하지 않아도 됩니다.

의문점

공통 처리 로직에 모든 Controller가 연결되려면, return의 형태가 동일해야 합니다.
하지만, Controller마다 return 형태는 다를 것이고, 이 형태를 동일하게 맞추려고 한다면 확장성과 유지보수성을 잃습니다.
응답별로 하나하나 처리할 수 있으나, 이렇게 된다면 공통 부분의 책임이 너무 커지게 됩니다. 그리고, Controller에서 반환 결과가 달라지게 되면, 공통 처리 부분의 변경 또한 불가피합니다.

어댑터 패턴

다양한 Controller를 유연하게 만들기 위하여 도입된 패턴입니다. Controller들이 동일한 인터페이스를 구현하도록 하고, 해당 인터페이스와 공통 로직 사이에 어댑터를 둡니다.

  • Controller는 비즈니스 로직을 처리하고 알맞은 결과를 반환합니다.
  • 어댑터는 공통 로직과 Controller(Handler)가 자연스럽게 연결되도록 합니다.
  • 프론트 컨트롤러는 공통으로 처리되는 로직을 수행합니다.

Spring MVC

Spring은 MVC 패턴에 프론트 컨트롤러 패턴과 어댑터 패턴이 적용된 구조를 가지고 있습니다.

Spring MVC의 구조

  • DispatcherServlet: Spring의 프론트 컨트롤러
  • View: 인터페이스로 구성되어 있으며, 확장성을 지닙니다.

실행 순서

  1. Client로부터 HTTP Request를 받습니다.
  2. Handler List에서 Handler Mapping을 통해 요청 URL에 Mapping된 Handler를 조회합니다.
  3. 적절한 Handler를 반환합니다.
  4. Handler를 처리할 수 있는 Handler Adapter를 조회합니다.
  5. 적절한 Adapater를 반환합니다.
  6. 알맞은 Adapter가 존재하면, Handler Adapter를 실행하여 요청을 위임합니다.
  7. 결과를 반환합니다.
  8. Handler Adapater는 Handler가 반환하는 정보를 Model And View 객체로 변환하여 반환합니다.
  9. View Resolver를 호출해 알맞은 View를 요청합니다.
  10. View Resolver는 View의 논리 이름을 물리 이름으로 전환하고, Rendering 역할을 담당하는 View 객체를 반환합니다.
  11. View를 통하여 Rendering을 진행합니다.
  12. 응답을 반환합니다.

Dispatcher Servlet

Spring MVC의 프론트 컨트롤러는 Dispatcher Servlet입니다.
HttpServlet을 상속받아 사용하며 Servlet의 한 종류입니다.
Spring Boot는 Dispatcher Servlet을 서블릿으로 자동 등록하고 모든 URL 경로에 대하여 Mapping합니다. 여기서, 더 자세한 URL 경로가 더 높은 우선순위를 가져가게 됩니다. 그렇기 때문에, 개발자가 만드는 Servlet이 항상 우선순위가 더 높아 실행됩니다.

Controller Interface

Spring MVC에서는 org.springframeworkd.web.servlet.mvc.Controller 인터페이스를 구현하여 컨트롤러를 만들 수 있습니다.
현재는 주로 @Controller@RequestMapping 어노테이션을 기반한 방식을 사용합니다.

@Component("/example-controller")
public class ExampleController implements Controller {
	@Override
    public ModelAndView handlerRequest(HttpServletRequest request, HttpServletResponse resonse) throws Exception {
		System.out.println("example-controller");
        return null;
    }
}
  • @Component("/example-controller")로 Spring Bean 이름을 URL로 등록하면, 해당 URL로 요청할 때, 이 컨트롤러가 호출됩니다.
  • handleRequest() 메서드를 오버라이드하여 요청 처리 로직을 작성합니다.

동작 원리

  1. Handler Mapping: DispatcherServlet이 들어온 요청 URL과 일치하는 Spring Bean 이름을 가진 컨트롤러를 찾습니다.
  2. Handler Adapter: 찾은 핸들러를 실행시킬 수 있는 HandlerAdapter를 찾습니다.
  3. HandlerAdapter가 handleRequest()를 호출하여 컨트롤러 로직을 실행합니다.

HandlerMapping의 우선 순위

  1. RequestMappingHandlerMapping
    Annotation 기반 Controller의 @RequestMapping에 사용됩니다.
  2. BeanNameUrlHandlerMapping
    Spring Bean Name으로 HandlerMapping을 합니다.

HandlerAdapter의 우선 순위

  1. RequestMappingHandlerAdapter
    Annotation 기반 Controller의 @RequestMapping에 사용됩니다.
  2. HttpRequestHandlerAdapter
    HttpRequestHandler를 처리합니다.
  3. SimpleControllerHandlerAdapter
    Controller Interface를 처리합니다.

HttpRequestHandler

org.springframework.web.HttpRequestHandler 인터페이스를 구현한 컨트롤러도 만들 수 있습니다. 해당 방식은 Servlet과 비슷하게 작동하고, handleRequst() 메서드를 오버라이드합니다.
HandlerMapping에서 URL로 Bean을 찾고, HttpRequestHandlerAdapter가 이를 실행합니다.

View Resolver

컨트롤러에서 반환한 ModelAndView의 ViewName을 실제 뷰(Thymeleaf 등)로 변환하는 역할을 수행합니다.
DispatcherServlet이 ViewResolver를 호출하여 논리적인 ViewName을 물리적인 View 경로로 변환합니다.
Spring Boot에서는 필요한 ViewResolver가 자동으로 등록됩니다.

동작 예시

  1. 컨트롤러에서 return new ModelAndBiew("test")로 논리적 뷰 이름을 반환합니다.
  2. ViewResolver가 prefix, suffix에 따라 test를 변환합니다.
  3. 해당되는 jsp를 렌더링하여 HTML로 응답합니다.

우선 순위

  1. BeanNameViewResolver
    Bean 이름으로 뷰를 찾습니다.
  2. InternalResourceViewResolver
    JSP 등 서블릿 기반 뷰를 처리하며, prefix/suffix 설정을 사용합니다.

자료 및 코드 출처: 스파르타 코딩클럽

0개의 댓글