3. 서블릿, JSP, MVC 패턴

2025. 4. 22. 12:36Spring Framework/스프링 MVC

[목차] 

1. 회원 관리 웹 애플리케이션 요구사항

2. 서블릿으로 웹 애플리케이션 만들기

3. JSP로 웹 애플리케이션 만들기

4. 서블릿과 JSP를 사용한 MVC 패턴으로 웹 애플리케이션 만들기 


1. 회원 관리 웹 애플리케이션 요구사항

  • 기능 요구사항: 회원 저장, 회원 전체 목록 조회
  • 회원 정보: 이름, 나이 

@Getter @Setter
public class Member {
    private Long id;
    private String username;
    private int age;

    public Member(String username, int age) {
        this.username = username;
        this.age = age;
    }
    
    public Member(){}
}
public class MemberRepository {
    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    //싱글톤 패턴 구현
    private static final MemberRepository instance = new MemberRepository();
    public static MemberRepository getInstance(){
        return instance;
    }
    private MemberRepository(){}

    public Member save(Member member){
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }
    public Member findById(Long id){
        return store.get(id);
    }
    public List<Member> findAll(){
        return new ArrayList<>(store.values());
    }
    public void clearStore(){
        store.clear();
    }
}
class MemberRepositoryTest {
    MemberRepository memberRepository = MemberRepository.getInstance();

    @AfterEach
    void afterEach(){
        // 테스트 메소드의 실행 순서가 보장되지 않기 때문에
        // 각각의 메서드를 독립적인 조건에서 실행하기 위해서는 clear 해줘야함
        memberRepository.clearStore();
    }

    @Test
    void save(){
        //given
        Member member = new Member("Lee", 20);
        //when
        Member savedMember = memberRepository.save(member);
        //then
        Member foundMember = memberRepository.findById(savedMember.getId());
        assertThat(foundMember).isEqualTo(savedMember);
    }
    @Test
    void findAll(){
        //given
        Member member1 = new Member("Kim", 20);
        Member member2 = new Member("Park", 30);
        memberRepository.save(member1);
        memberRepository.save(member2);
        //when
        List<Member> result = memberRepository.findAll();
        //then
        assertThat(result.size()).isEqualTo(2);
        assertThat(result).contains(member1, member2);
    }
}

 

2. 서블릿으로 웹 애플리케이션 만들기

//회원 등록 폼 화면
@WebServlet(name = "memberFormServlet", urlPatterns = "/servlet/members/new-form")
public class MemberFormServlet extends HttpServlet {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");

        PrintWriter w = response.getWriter();
        w.write("<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>\n" +
                " <meta charset=\"UTF-8\">\n" +
                " <title>Title</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "<form action=\"/servlet/members/save\" method=\"post\">\n" +
                " username: <input type=\"text\" name=\"username\" />\n" +
                " age: <input type=\"text\" name=\"age\" />\n" +
                " <button type=\"submit\">전송</button>\n" +
                "</form>\n" +
                "</body>\n" +
                "</html>\n");
    }
}
//회원 등록
@WebServlet(name = "memberSaveServlet", urlPatterns = "/servlet/members/save")
public class MemberSaveServlet extends HttpServlet {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
        PrintWriter w = response.getWriter();
        w.write("<html>\n" +
                "<head>\n" +
                " <meta charset=\"UTF-8\">\n" +
                "</head>\n" +
                "<body>\n" +
                "성공\n" +
                "<ul>\n" +
                " <li>id="+member.getId()+"</li>\n" +
                " <li>username="+member.getUsername()+"</li>\n" +
                " <li>age="+member.getAge()+"</li>\n" +
                "</ul>\n" +
                "<a href=\"/index.html\">메인</a>\n" +
                "</body>\n" +
                "</html>");
    }
}
//등록된 회원 리스트 조회
@WebServlet(name = "memberListServlet", urlPatterns = "/servlet/members")
public class MemberListServlet extends HttpServlet {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Member> members = memberRepository.findAll();
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
        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>");
        w.write("<table>");
        w.write(" <thead>");
        w.write(" <th>id</th>");
        w.write(" <th>username</th>");
        w.write(" <th>age</th>");
        w.write(" </thead>");
        w.write(" <tbody>");
        //자바 코드를 통해 html을 동적으로 생성
        for (Member member : members) {
            w.write(" <tr>");
            w.write(" <td>" + member.getId() + "</td>");
            w.write(" <td>" + member.getUsername() + "</td>");
            w.write(" <td>" + member.getAge() + "</td>");
            w.write(" </tr>");
        }
        w.write(" </tbody>");
        w.write("</table>");
        w.write("</body>");
        w.write("</html>");
    }
}
  • 서블릿의 한계
    • 코드에서 보다시피 서블릿 코드만으로 애플리케이션을 만드는 것은 매우 복잡하고 비효율적이다.
    • 따라서 자바 코드로 HTML을 만들어 내는 것 보다
      차라리 
      HTML 문서에 동적으로 변경해야 하는 부분만 자바 코드를 넣을 수 있다면 더 편리할 것이다.
      이것이 바로 "템플릿 엔진"이 나온 이유이다. 
  • 서블릿: 자바 코드로 → 동적인 HTML을 만들어서 HTTP 응답 메시지에 전달하는 것
  • 템플릿 엔진:  HTML 문서에서 → 필요한 곳만 코드를 적용해서 동적으로 변경하는 것
    • 내부적으로는 서블릿 코드로 변환되어 응답 메시지를 생성한다.
    • 템플릿 엔진에는 JSP, Thymeleaf, Freemarker, Velocity등이 있다.
 

3. JSP로 웹 애플리케이션 만들기

<!-- localhost:8080/jsp/members/new-form.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form action="/jsp/members/save.jsp" method="post">
         username: <input type="text" name="username" />
         age:      <input type="text" name="age" />
         <button type="submit">전송</button>
    </form>
</body>
</html>
<!-- localhost:8080/jsp/members/save.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%
    MemberRepository memberRepository = MemberRepository.getInstance();
    String username = request.getParameter("username");
    int age = Integer.parseInt(request.getParameter("age"));

    Member member = new Member(username, age);
    memberRepository.save(member);
%>

<html>
<head>
     <meta charset="UTF-8">
</head>
<body>
    성공
    <ul>
         <li>id=<%=member.getId()%></li>
         <li>username=<%=member.getUsername()%></li>
         <li>age=<%=member.getAge()%></li>
    </ul>
    <a href="/index.html">메인</a>
</body>
</html>
<!-- localhost:8080/jsp/members.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="java.util.List" %>

<%
    MemberRepository memberRepository = MemberRepository.getInstance();
    List<Member> members = memberRepository.findAll();
%>

<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="/index.html">메인</a>
<table>
    <thead>
        <th>id</th>
        <th>username</th>
        <th>age</th>
    </thead>
    <tbody>
        <%
            //out도 바로 사용할 수 있도록 제공되는 약어
            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>
</body>
</html>
    • JSP는 서버 내부에서 위의 코드와 같은 자바 서블릿 코드로 변환된다. 
    • 따라서 JSP는 자바 코드를 그대로 다 사용할 수 있다.
      • <% %>: 서블릿의 메서드 안으로 들어가는 자바 코드, 지역변수만 선언할 수 있다. 
      • <%! %>: 서블릿 클래스 레벨로 들어가는 자바 코드
        멤버변수나 메서드 선언이 가능하고 private/public 같은 접근 제한자도 사용할 수 있다. 
      • <%= %>: 내부에 작성된 변수의 값을 자동으로 문자열로 변환하여 HTML로 출력
    • JSP 파일 맨 위에 명시할 것들 <%@ ~ %>
      • 요청 메세지 헤더에 들어갈 content-type과 charset 등
        <%@ page contentType="text/html;charset=UTF-8" language="java" %>
      • 자바 코드에서 사용하는 라이브러리 import문
        <%@ page import="java.util.List" %>
  • JSP의 한계
    • 자바코드에 HTML을 넣는 서블릿 코드 보다 JSP 코드가 깔끔하지만 여전히 해결되지 않은 문제가 있다.
    • JAVA 코드, 데이터를 조회하는 리포지토리 등의 다양한 코드가 모두 JSP에 노출되어 있다.
      즉, JSP
      가 너무 많은 역할을 한다. 이렇게 한 페이지에 모든 코드가 모여있는 경우 유지 보수가 어려워진다.
    • 따라서 비즈니스 로직과 화면 코드를 분리한 MVC 패턴이 등장했다. 

 

4. 서블릿과 JSP를 활용한 MVC 패턴으로 웹 애플리케이션 만들기 

1) MVC 패턴

 

MVC 패턴 적용 이전과 이후

  • MVC 패턴의 등장 배경: 서블릿과 JSP 단독 사용의 한계
    • 너무 많은 역할: 하나의 서블릿이나 JSP만으로 비즈니스 로직과 뷰 렌더링까지 모두 처리하게 되면너무 많은 역할을 하게되고결과적으로 유지보수가 어려워진다.
    • 변경의 라이프 사이클의 분리가 필요: 비즈니스 로직과 화면은 대부분 서로에게 영향을 주지 않고 변경의 리사이클이 다르다. 이렇게 변경의 라이프 사이클이 다른 부분을 하나의 코드로 관리하는 것은 유지보수하기 좋지 않다.
    • 기능 특화: 특히 JSP 같은 뷰 템플릿은 화면을 렌더링 하는데 최적화 되어 있기 때문에 이 부분의 업무만 담당하는 것이 가장 효과 적이다.
  • MVC 패턴 (Model View Controller) 
    • MVC 패턴은 하나의 서블릿이나, JSP로 처리하던 것을 컨트롤러(Controller)와 뷰(View)라는 영역으로 서로 역할을 나눈 것을 말한다. 웹 애플리케이션은 보통 이 MVC 패턴을 사용한다.
    • Controller: HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다. 그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담는다.
    • Model: 뷰에 출력할 데이터를 담아둔다. 뷰가 필요한 데이터를 모두 모델에 담아서 전달해주는 덕분에 뷰는 비즈니스 로직이나 데이터 접근을 몰라도 되고, 화면을 렌더링 하는 일에 집중할 수 있다.
    • View: 모델에 담겨있는 데이터를 사용해서 화면을 그리는 일에 집중한다. 여기서는 HTML을 생성하는 부분을 말한다.
  • 참고: 컨트롤러와 서비스 계층의 분리 컨트롤러에 비즈니스 로직을 둘 수도 있지만, 이렇게 되면 컨트롤러가 너무 많은 역할을 담당한다. 그래서 일반적으로 비즈니스 로직은 서비스(Service)라는 계층을 별도로 만들어서 처리한다. 그리고 컨트롤러는 HTTP 요청을 받아서 비즈니스 로직이 있는 서비스를 호출하는 역할을 담당한다. 
컨트롤러와 서비스 계층의 분리

2) 서블릿과 JSP를 활용한 MVC 패턴 적용 

  • 서블릿을 Controller로, JSP를 View로 사용하여 MVC 패턴을 적용
  • Model은 HttpServletRequest 객체를 사용한다.
    • request는 내부에 데이터 저장소를 가지고 있음
    • request.setAttribute(), request.getAttribute()를 사용하면 데이터를 보관하고, 조회할 수 있다.
@WebServlet(name="mvcMemberFormServlet", urlPatterns = "/servlet-mvc/members/new-form")
public class MvcMemberFormServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //form을 가지고 있는 jsp 화면으로 가주면 됨
        String viewPath = "/WEB-INF/views/new-form.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
     <meta charset="UTF-8">
     <title>Title</title>
</head>
<body>
    <!-- 상대경로 사용: [현재 URL이 속한 계층 경로(/servlet-mvc/members) + /save] -->
    <form action="save" method="post">
         username: <input type="text" name="username" />
         age: <input type="text" name="age" />
         <button type="submit">전송</button>
    </form>
</body>
</html>
@WebServlet(name="mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet extends HttpServlet {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        //Model에 데이터를 보관한다.
        request.setAttribute("member", member);

        String viewPath = "/WEB-INF/views/save-result.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request,response);
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<!-- <%@ page import="hello.servlet.domain.member.Member" %> -->
<html>
<head>
     <meta charset="UTF-8">
</head>
<body>
    성공
    <ul>
    <!--
         <li>id=<%=((Member)request.getAttribute("member")).getId()%></li>
         <li>username=<%=((Member)request.getAttribute("member")).getUsername()%></li>
         <li>age=<%=((Member)request.getAttribute("member")).getAge()%></li>
    -->
        <li>id=${member.id}</li>
        <li>username=${member.username}</li>
        <li>age=${member.age}</li>
    </ul>
    <a href="/index.html">메인</a>
</body>
</html>
@WebServlet(name="mvcMemberListServlet", urlPatterns = "/servlet-mvc/members")
public class MvcMemberListServlet extends HttpServlet {
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Member> members = memberRepository.findAll();

        request.setAttribute("members", members);

        String viewPath = "/WEB-INF/views/members.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request,response);
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="/index.html">메인</a>
<table>
    <thead>
        <th>id</th>
        <th>username</th>
        <th>age</th>
    </thead>
    <tbody>
    <c:forEach var="item" items="${members}">
        <tr>
            <td>${item.id}</td>
            <td>${item.username}</td>
            <td>${item.age}</td>
        </tr>
    </c:forEach>
    </tbody>
</table>
</body>
</html>
  • /WEB-INF 경로
    • 이 경로안에 JSP가 있으면 외부에서 직접 JSP를 호출할 수 없다.
      예)localhost:8080/jsp/member.jsp → 에러
    • 항상 컨트롤러를 통해서 JSP를 호출하도록 하고 싶은 경우 WEB-INF 경로 안에 뷰 파일을 두면 된다. 
  • 절대경로 vs 상대경로
  절대경로 상대경로 
기준 항상 루트 (/) 현재 경로
시작 /로 시작함 그냥 글자(폴더, 파일명)로 시작
사용시기 언제나 명확한 경로로 이동해야 할 때.
서버가 redirect 보낼 때 Location에 넣는 경로는 절대경로를 쓰는게 안전하다. 
현재 위치 기준으로 이동할 곳이 달라질 때

 

3) Redirect vs Forward

  • 요청과 응답의 방식 
개념 누가 수행? 의미 
POST, GET 등  클라이언트(브라우저) 서버로 어떻게 요청할까, 요청의 종류 
redirect, forward 서버(서블릿, 컨트롤러) 클라이언트한테 어떻게 응답할까, 응답 후 화면 이동 방식
  • 리다이렉트와 포워드 비교 
[Redirect] 요청이 두 번 일어남 
(1) 클라이언트 -> 서버 (요청: /login)
(2) 서버 -> 클라이언트 (응답: 상태코드 "302 redirect" + Location "/home")
(3) 클라이언트 -> 서버 (새로운 요청: /home)
(4) 서버 -> 클라이언트 (응답: /home 화면 보여줌)

[Forward] 요청은 한 번, 서버 내부 이동만 있음 
(1) 클라이언트 -> 서버 (요청: /login)
(2) 서버 내부에서 /home으로 포워드
(3) 서버 -> 클라이언트 (응답: /home 화면 내용)

 

  리다이렉트 포워드
요청 흐름 클라이언트한테 새 요청 시킴 서버 안에서 내부 이동
데이터 유지 안됨 (새 요청이니까) 됨 (request 데이터 유지)
URL 변화 바뀜 안 바뀜
속도 상대적으로 느림 빠름
주로 쓰는 상황 POST 처리 후 새로고침 문제 방지 데이터 넘기면서 화면 전환
  • 리다이렉트를 써야하는 상황: Form 제출(POST) 후 이동 
    • 만약 login 후에 forward를 사용하면 서버가 클라이언트한테 바로 home.jsp 화면을 넘겨준다. 근데 클라이언트 입장에서는 아직 "요청 URL이 /login" 으로 되어 있기 때문에 사용자가 새로고침(F5) 누르면, 브라우저는 마지막 요청(/login)  그대로 다시 보내버리고 /login POST 요청이 또 날아간다. 그러면 로그인 처리가 또 다시 일어나서 중복 처리가 문제 발생한다. 
    • 반대로 login 후에 redirect를 사용하면 서버가 클라이언트한테 /home으로 다시 요청하고 브라우저가 /home 으로 새롭게 GET 요청을 보내게 된다. 그러면 브라우저의 주소창도 /home으로 바뀌어 있으니 새로고침(F5) 해도 /home을 새로고침하는 거고 /login을 재요청하지 않는다. 따라서 중복 로그인 처리 문제가 해결된다. 
    • 이렇게 중복 form 제출 문제는 막는 방식을 PRG 패턴이라고 한다. (Post → Redirect → Get)
      사용자가 form 제출 POST 요청
      → 서버가 처리하고 Redirect로 응답 
      브라우저는 해당 화면으로 GET 요청
    • 거의 대부분의 경우, 리다이렉트 후 재요청은 GET 방식이다.
      일반적인 302, 303 redirect (sendRedirect, redirect:) -> GET 요청으로 바뀐다
      307, 308 redirect (명시적 사용 시)-> 원래 요청 방식 그대로 유지 
  • 포워드를 쓰면 좋은 상황
    • 데이터를 그대로 유지하고 싶을 때 
      포워드는 기존 요청과 응답 객체를 그대로 넘기기 때문에 request에 담긴 데이터를 그대로 다음 페이지에서도 꺼내 쓸 수 있다. 
      dispatcher.forward(request, response);
    • 단순한 화면 이동인 경우
      redirect는 요청이 2번 발생해서 네트워크를 한번 더 타야 해서 조금 느리지만, 
      forward는 서버 안에서만 이동하니까 훨씬 빠르고 부담이 적다. 

 

4) JSP에서 제공하는 편리한 문법들

<%= %>, <% %> 난무하는 JSP 코드를 깔끔하게 만들기 위해 제공되는 ${}, <c:forEach> 등의 문법 

 

4-1) EL(Expression Language)

  • JSP 내장객체(request, session 등)에 저장된 값을 쉽게 꺼내쓰기 위한 문법
  • ${} 안에 간단하게 값 넣어 출력한다.
  • JSP에서 제공하는 내장 객체
    JSP는 자바 코드 내에서 별도로 선언하지 않고 자동으로 사용할 수 있게 만들어진 내장 객체를 지원한다. 
이름 타입 설명
request HttpServletRequest 요청 정보를 담고 있는 객체
response HttpServletResponse 응답 정보를 담고 있는 객체
session HttpSession 세션 관리용 객체
application ServletContext 웹 어플리케이션 범위 객체
out JspWriter 출력 스트림 (HTML 출력할 때 사용)
config ServletConfig JSP 페이지 설정 정보 객체
pageContext PageContext JSP 전체를 관리하는 객체
page this 현재 JSP 페이지 객체 자체
exception Throwable 예외처리용 (error page에서만 사용)
  • EL 사용 예시 
EL 사용 전
<%@ page import="com.example.Member" %>
<%=((Member)request.getAttribute("member")).getId()%>

EL 사용 후
${member.id}

EL이 자동으로 내장객체(request, session, application 등) 뒤져서 꺼내야 할 객체 찾고 타입 캐스팅도 자동으로 해주고, 필드값 꺼낼 때도 점(dot)으로 쉽게 꺼낼 수 있게 해준다. 

 

4-2) JSTL (JSP Standard Tag Library)

  • JSP에서 자주 쓰는 패턴을 HTML 태그처럼 쉽게 쓰기 위해 만든 라이브러리
  • 반복문, 조건문, 포맷팅, URL 처리 등을 스크립트릿 없이 태그 형태로 깔끔하게 쓸 수 있도록 해준다.
    • 조건문: <c:if test="${조건}"> 
    • 반복문: <c:forEach var="item" items="${items}">
    • URL 만들기: <c:url value="/some/path" />
  • <c:forEach> 사용 예시 
jstl 사용 전 
<%
    //out도 바로 사용할 수 있도록 제공되는 약어
    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>");
    }
%>
 
jstl 사용 후  
<c:forEach var="item" items="${members}">
    <tr>
        <td>${item.id}</td>
        <td>${item.username}</td>
        <td>${item.age}</td>
    </tr>
</c:forEach>

 

5) MVC 패턴의 한계

  • MVC 패턴을 적용한 덕분에 컨트롤러의 역할과 뷰를 렌더링 하는 역할을 명확하게 구분할 수 있다. 특히 뷰는 화면을 그리는 역할에 충실한 덕분에, 코드가 깔끔하고 직관적이다. 단순하게 모델에서 필요한 데이터를 꺼내고, 화면을 만들면 된다.
  • 그러나 컨트롤러는 딱 봐도 중복되는 코드가 많고, 필요하지 않는 코드들도 많이 보인다.
    • 포워드 중복
    • ViewPath 중복
    • Response 객체 사용 안 하는 경우가 많음
  • 또한 공통 처리가 어렵다.
    기능이 복잡해질 수록 컨트롤러에서 공통으로 처리해야 하는 부분이 점점 더 많이 증가할 것이다. 단순히 공통 기능을 메서드로 뽑으면 될 것 같지만, 결과적으로 해당 메서드를 항상 호출해야 하고, 실수로 호출하지 않으면 문제가 될 것이 다. 그리고 호출하는 것 자체도 중복이다.
  • 이 문제를 해결하려면 컨트롤러 호출 전에 먼저 공통 기능을 처리하는 수문장 역할이 필요하다.
    프론트 컨트롤러(Front Controller) 패턴을 도입하면 이런 문제를 깔끔하게 해결할 수 있다.
    스프링 MVC의 핵심도 바로 이 프론트 컨트롤러에 있다.