공부함

서블릿과 MVC 패턴과 프론트 컨트롤러 패턴 본문

스프링

서블릿과 MVC 패턴과 프론트 컨트롤러 패턴

찌땀 2024. 8. 27. 14:39

서블릿이란?

서블릿(Servlet)이란 동적 웹 페이지를 만들 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술이다. 서블릿은 웹 요청과 응답의 흐름을 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 해준다.

 

서블릿은 클라이언트의 요청에 따라 동적으로 작동하는 웹 애플리케이션 컴포넌트이다.

 

서블릿이 필요한 이유

서블릿을 사용하면 개발자는 의미있는 비즈니스 로직 개발에만 집중하면 되므로 효율적인 개발이 가능하다.

 

@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 비즈니스 로직
    }
}
  • urlPatterns에 해당하는 url이 호출되면 해당 서블릿 코드가 실행된다. (service 메서드)
  • service 메서드의 파라미터인 HttpServletRequest와 HttpServletResponse를 통해 Http 요청, 응답에 편리하게 접근할 수 있다

  • 스프링이 내장 tomcat 서버를 생성하고, 내장 톰캣 서버가 서블릿을 생성해 놓습니다. 
  • Http 요청이 들어오면 요청을 토대로 request 객체를 생성하고, 서블릿에 의해 요청 처리 후 response 객체를 토대로 Http 응답을 생성해 반환합니다.

서블릿 컨테이너 

  • 서블릿을 지원하는 WAS를 서블릿 컨테이너라 한다. (ex : tomcat)
  • 서블릿 생성-초기화-호출-종료의 생명주기를 관리한다.
  • 서블릿 객체는 최초에 하나의 인스턴스를 생성하고 재활용하는 싱글턴 방식이다. 즉 모든 클라이언트 요청은 같은 서블릿 인스턴스에 의해 처리된다. 

쓰레드 풀

  • WAS는 쓰레드 풀 방식으로 쓰레드를 관리한다. 요청마다 쓰레드를 할당받고 사용 후 반납하는 방식이다. 
  • 쓰레드 풀에 생성 가능한 최대 쓰레드 수를 튜닝하는 것이 중요하다. (톰캣은 최대 200개)
  • WAS는 멀티 쓰레드에 관한 부분을 알아서 처리하기 때문에 개발자는 싱글 쓰레드 상황인 것 처럼 편하게 개발할 수 있다. 

템플릿 엔진

@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 비즈니스 로직

        PrintWriter w = response.getWriter();
        w.write("<html>");
        w.write("<head>");
        w.write(" <meta charset=\"UTF-8\">");
        w.write(" <title>Title</title>");
        w.write("</head>");
        w.write("<body>");
        w.write("<a href=\"/index.html\">메인</a>");
        // .. 등등 복잡한 view를 위한 html 코드
    }
}

서블릿과 java 코드만 활용해 화면을 동적으로 표시하려면 위와 같이 복잡한 html 코드를 java로 작성해야 하는 불편함이 있다.

템플릿 엔진이란

  • 이러한 복잡성을 해소하기 위해 템플릿 엔진을 활용할 수 있다. 템플릿 엔진은 html 파일상에 필요한 부분에 java 코드를 넣어 동적으로 구성할 수 있다. 
  • 대표적인 템플릿 엔진으로는 JSP, Thymeleaf 등이 있다. 
<table>
 <thead>
 <th>id</th>
 <th>username</th>
 <th>age</th>
 </thead>
 <tbody>
<%
 for (Member member : members) {
 out.write(" <tr>");
 out.write(" <td>" + member.getId() + "</td>");
 out.write(" <td>" + member.getUsername() + "</td>");
 out.write(" <td>" + member.getAge() + "</td>");
 out.write(" </tr>");
 }
%>
 </tbody>
</table>

예를 들자면 JSP를 활용해 이런 식으로 html 파일에 java 코드를 같이 사용해 동적으로 구성할 수 있다. 

 

MVC 패턴

서블릿+JSP 엔진의 조합으로 개발하면 서블릿만 사용할 때 보다는 편리하지만 여전히 문제점이 있다.

  • JSP에 비즈니스 로직이 노출된다 => JSP가 너무 많은 역할을 한다.
  • 복잡한 프로젝트라면 JSP 코드가 굉장히 복잡해질 것이고 유지보수성이 떨어진다. 
    • 비즈니스 로직이나 UI 하나만 변경하려고 해도 둘이 같이 있는 코드를 손대야 한다. 
    • UI와 비즈니스 로직을 수정하는 라이프 사이클이 다를 가능성이 높다. 라이프 사이클이 다른 부분이 같이 존재하면 유지보수하기 좋지 않다. 

이러한 문제를 해결하기 위해 MVC 패턴을 사용할 수 있다. 

  • Controller : Http 요청을 받아 파라미터 검증 후 비즈니스 로직을 실행한다. 뷰에 전달할 데이터는 모델에 담는다.
  • Model : 뷰에 출력할 데이터를 담아둔다. 뷰는 모델 덕분에 비즈니스 로직, 데이터에 관해 모르고 렌더링에만 집중할 수 있다. 
  • View : 모델에 담겨 있는 데이터로 화면을 렌더링하는 일에 집중한다. 
  • Service : 비즈니스 로직을 컨트롤러에 두면 컨트롤러의 일이 너무 많아져 일반적으로 서비스 계층에 비즈니스 로직을 두고 컨트롤러는 필요한 비즈니스 로직을 호출한다. 
@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String username = request.getParameter("username");
        Member member = memberRepository.findByMemberName(username);
        reponse.setAttribute("member", member);

        String viewPath = "/WEB-INF/views/showMember.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}
  • 서블릿(컨트롤러)에서 비즈니스 로직을 처리하고, HttpServletResponse 객체(모델)에 필요한 데이터를 저장해 JSP(뷰)로 넘기는 식으로 MVC 패턴을 적용했다.

프론트 컨트롤러 패턴 

MVC 패턴은 유용하지만 컨트롤러에서 viewPath, forward 등의 중복이 발생한다. 또 request, response 객체를 항상 사용하는 것이 아닌 사용할 때도 있고 사용하지 않을 때도 있다. 

 

이러한 문제를 해결하기 위해 컨트롤러 호출 전 수문장 역할을 하는 프론트 컨트롤러를 호출하는 프론트 컨트롤러 패턴을 도입할 수 있다. 

 

  • 프론트 컨트롤러가 공통 요청을 처리하고 알맞은 컨트롤러를 찾아서 호출한다. 
  • 나머지 컨트롤러들은 서블릿을 사용하지 않아도 된다
  • Spring Web MVC의 DispatcherServlet이 프론트 컨트롤러 패턴으로 구현된 것이다. 

프론트 컨트롤러 패턴 이해하기 

김영한님 강의에서는 프론트 컨트롤러를 도입하며 V1~V5까지 발전시켜 왜 지금의 Spring MVC 형태가 되었는지 설명해주신다. 이 과정이 매우 도움되기 때문에 간단하게 정리해보겠다. 

V1

  • 프론트컨트롤러는 Map으로 컨트롤러를 urlpattern에 대해 매핑한다. 요청 url에 따라 컨트롤러를 찾고 해당 컨트롤러의 process 메서드를 호출한다 
  • 컨트롤러는 process 메서드를 오버라이딩해야한다. process에서는 비즈니스 로직을 처리하고 forward를 호출해 뷰로 넘어간다. 
void process(HttpServletRequest request, HttpServletResponse response);

 

V2

  • V1에서는 컨트롤러의 process에서 forward를 호출하는 부분이 컨트롤러마다 중복된다. 이것을 해결하기 위해 뷰를 처리하는 객체 MyView를 만든다. 
  • MyView는 뷰 경로 viewPath를 필드로 갖고, forward를 호출하는 render 메서드를 갖는다. 
  • 컨트롤러는 process 호출 결과 MyView를 반환한다. 프론트컨트롤러에서 반환된 MyView를 통해 render를 호출한다. 
 MyView process(HttpServletRequest request, HttpServletResponse response);

 

V3

  • 컨트롤러에서 서블릿 종속성을 제거한다. 
    • HttpServletRequest대신 Java의 Map을 사용해 필요 데이터를 넘긴다.
    • HttpServletResponse를 model로 사용하는 대신 Model 객체를 만들어 반환한다. 
  • 뷰 이름에서 중복을 제거한다. 
    • 컨트롤러는 뷰의 논리 이름을 반환하고 물리 이름은 프론트 컨트롤러에서 만들어 처리한다. 
  • 따라서 ModelView를 도입한다. 
public class ModelView {
 private String viewName;
 private Map<String, Object> model = new HashMap<>();
 ...
 }
  • ModelView는 viewName과 model을 갖는다. 
  • HttpServletResponse 대신 ModelView의 model을 사용해 데이터를 view로 넘긴다.
ModelView process(Map<String, String> paramMap);
  • 컨트롤러는 서블릿 종속성이 제거되었다. 
  • process는 ModelView에 viewName과 model 데이터를 담아 반환한다. 
	// service 메서드
    	// 생략 
	Map<String, String> paramMap = createParamMap(request);
	ModelView mv = controller.process(paramMap);

 	String viewName = mv.getViewName();
 	MyView view = viewResolver(viewName);
 	view.render(mv.getModel(), request, response);
}

private MyView viewResolver(String viewName) {
 return new MyView("/WEB-INF/views/" + viewName + ".jsp");
 }
  • 프론트 컨트롤러에서는 Java의 Map을 사용해 (paramMap) 컨트롤러로 데이터를 넘긴다. 
  • 컨트롤러는 process 호출 결과 viewName과 model을 담은 ModelView를 반환한다. 
  • viewResolver로 view의 물리이름을 얻는다. 
  • MyView를 통해 render를 호출해 forward를 호출한다. 

V4

  • 컨트롤러에서 ModelView를 생성, 반환하는 과정이 번거로워 String으로 viewName만 반환하도록 변경한다. 
  • 모델을 컨트롤러에서 생성하지 않고 프론트컨트롤러에서 생성해 파라미터로 넘겨준다. 
     Map<String, String> paramMap = createParamMap(request);
     Map<String, Object> model = new HashMap<>(); //추가
     String viewName = controller.process(paramMap, model);
     MyView view = viewResolver(viewName);
     view.render(model, request, response);
 }
  • 프론트컨트롤러에서 model을 생성해 넘겨준다. 
@Override
 public String process(Map<String, String> paramMap, Map<String, Object> model) {
 	List<Member> members = memberRepository.findAll();
 	model.put("members", members);
 	return "members";
 }
  • 컨트롤러에서는 비즈니스 로직을 처리하고 model에 필요 데이터를 넣어주고 view의 논리 이름을 반환하기만 하면 된다. 

V5

  • 어댑터를 사용해 다양한 종류의 핸들러(컨트롤러)를 사용할 수 있게 한다. 
  • 프론트 컨트롤러가 핸들러를 호출하는 것이 아닌 어댑터가 핸들러를 호출한다.
public interface MyHandlerAdapter {
     boolean supports(Object handler);
     ModelView handle(HttpServletRequest request, HttpServletResponse response,
    Object handler) throws ServletException, IOException;
}
  • 어댑터는 handle에서 컨트롤러를 호출하고 결과로 ModelView를 반환해야 한다. 컨트롤러가 ModelView를 반환하지 못한다면 (다양한 핸들러를 호출할 수 있으므로) 어댑터가 만들어서 반환해야 한다. 
@Override
 protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     Object handler = getHandler(request);
     
     if (handler == null) {
         response.setStatus(HttpServletResponse.SC_NOT_FOUND);
         return;
     }
     
     MyHandlerAdapter adapter = getHandlerAdapter(handler);
     ModelView mv = adapter.handle(request, response, handler);
     MyView view = viewResolver(mv.getViewName());
     view.render(mv.getModel(), request, response);
 }
  • 프론트컨트롤러는 먼저 핸들러를 찾고, 핸들러를 처리할 수 있는 핸들러어댑터를 찾고, 핸들러어댑터의 handle을 호출한다. 
  • 핸들러어댑터는 handle에서 핸들러를 호출해 비즈니스 로직을 처리하고 결과적으로 ModelView를 반환한다.  

V5까지 따라왔으면 용어만 살짝 다르다 뿐이지 Spring MVC 구조랑 똑같다. 우왕

 

결론

서블릿과 Spring MVC에 대해 복습할 겸 정리했는데, 웬일로 완벽히 이해한 것 같다. 

그런데 퇴근하고 피자먹고 운동까지하고 쓴거라 넘모 힘들어서 뒤로 갈수록 살짝 글이 성의가 없다. ㅎ.ㅎ

 

출처

 

https://velog.io/@falling_star3/Tomcat-%EC%84%9C%EB%B8%94%EB%A6%BFServlet%EC%9D%B4%EB%9E%80#%EF%B8%8F-1-%EC%84%9C%EB%B8%94%EB%A6%BFservlet%EC%9D%B4%EB%9E%80

서블릿 관련해서 참고한 개발블로그

 

[Servlet] 서블릿(Servlet)이란?

서블릿의 개념과 동작 과정, 생명주기(메서드), 인터페이스, 서블릿 컨테이너에 대해 공부하고 정리한 내용입니다.

velog.io

https://www.inflearn.com/course/lecture?courseSlug=%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1&unitId=71225

김영한님 spring mvc1 인프런 강의 

 

학습 페이지

 

www.inflearn.com

'스프링' 카테고리의 다른 글

dto를 사용해야 하는 이유  (0) 2024.09.01
ApplicationContext  (0) 2024.08.28
카카오 로그인  (0) 2024.08.02
@RestControllerAdvice를 활용한 스프링 예외 처리  (0) 2023.12.19
SpringDataJpa 사용자 정의 Repository 적용  (1) 2023.12.09