Spring

[인프런/스프링 MVC 1편] 3. 서블릿, JSP, MVC 패턴

주니어주니 2023. 4. 28. 19:00

 

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

 

회원 정보

이름: username

나이: age 

 

기능 요구사항

- 회원 저장 

- 회원 목록 조회

 

 

회원 도메인 모델 (회원 객체)

- id는 Member를 회원 저장소에 저장하면 회원 저장소가 save 메소드를 통해 할당한다.

 

package hello.servlet.domain.member;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class Member {

    private Long id;
    private String username;
    private int age;

    public Member() {
    }

    public Member(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

 

 

 

회원 저장소 

- 싱글톤 패턴 적용 (스프링을 사용하면 스프링 빈으로 등록하면 되지만, 지금은 스프링 없이 순수 서블릿으로 구현해보는 것)

- 싱글톤 패턴은 객체를 하나만 생성하므로 생성자를 private 접근자로 막음 (memberRepository.getInstance()로 객체 조회) 

 

package hello.servlet.domain.member;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 동시성 문제가 고려되어 있지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용 고려
 */
public class MemberRepository {

    private static Map<Long, Member> store = new HashMap<>();   // static 사용 (MemberRepository 객체가 많이 생성되어도 얘는 한번만 생성
    private static long sequence = 0L;                          // static 사용

    // 싱글턴으로 생성 (스프링 없이 순수 서블릿만으로 구현하기 위해)
    private static final MemberRepository instance = new MemberRepository();

    // 무조건 얘로 조회
    public static MemberRepository getInstance() {
        return instance;
    }
    // 생성자로 생성못하게 private으로 생성자를 막음 (싱글톤은 객체를 단 하나만 생성해서 공유해야 하므로)
    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()); // store에 있는 값에 접근할 수 없도록 하기 위해 new ArrayList
    }

    //store 비우기
    public void clearStore() {
        store.clear();

    }
}

 

 

 

회원 저장소 테스트 코드

- test 폴더 -> java -> hello.servlet.doman.member 

 

package hello.servlet.domain.member;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.*;

public class MemberRepositoryTest {

    // new로 불러올 수 없음 (싱글톤이니까)
    MemberRepository memberRepository = MemberRepository.getInstance();

    // 테스트가 끝날 때마다 테스트 초기화
    @AfterEach
    void afterEach() {
        memberRepository.clearStore();
    }

    @Test
    void save() {   // save 메소드 테스트
        // given (이런게 주어졌을 때)
        Member member = new Member("hello", 20);

        // when (이런걸 실행했을 때)
        Member savedMember = memberRepository.save(member);       // save 메소드 실행했을 때

        // then (결과가 이렇게 되어야 함)
        Member findMember = memberRepository.findById(savedMember.getId());
        assertThat(findMember).isEqualTo(savedMember);   	// 찾은 멤버가 저장된 멤버와 같아야 한다.

    }

    @Test
    void findAll() {
        // given
        Member member1 = new Member("member1", 20);
        Member member2 = new Member("member2", 30);

        memberRepository.save(member1);
        memberRepository.save(member2);

        // when
        List<Member> result = memberRepository.findAll();       // findAll() 메소드 실행했을 때

        // then
        assertThat(result.size()).isEqualTo(2);  		// 결과값의 크기가 2가 맞는지 (alt+enter -> static import 만듦)
        assertThat(result).contains(member1, member2);   	// 결과가 member1, member2를 포함하고 있는지

    }

}

 

 


 

 

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

 

 

2-1. 서블릿으로 회원등록 HTML 폼 생성

 

* MemberFormServlet - 회원등록 폼

  • 단순하게 회원정보 (이름, 나이)를 입력할 수 있는 HTML Form 만들어서 응답 
  • 자바 코드로 HTML을 제공해야 함
  • 실행 : http://localhost:8080/servlet/members/new-form

 

package hello.servlet.web.servlet;

import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "memberFormServlet", urlPatterns = "/servlet/members/new-form")
public class MemberFormServlet extends HttpServlet {

    // 얘를 사용하려면 객체가 있어야 하는데 싱글톤 -> getInstance()로 불러와야 함
    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // html을 응답으로 받아야 하니까
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");

        // HTML을 자바코드로 다 작성해야 함 -> 서블릿의 단점
        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");
    }
}

 

 

 

 

2-2. 서블릿으로 회원 데이터 실제 저장하는 기능 

 

* MemberSaveServlet - 회원 저장

  • HTML Form에서 데이터 입력하고 전송 누르면 실제 회원 데이터 저장
  • 작동 순서
    • HTML 폼에서 보낸 데이터 읽음 (이름, 나이)
    • 입력된 데이터로 Member 객체 생성 
    • MemberRepository의 save 메소드를 통해 Member객체를 MemberRepository에 저장
    • Member 객체를 사용해서 결과 화면용 HTML을 동적으로 만들어서 응답 
  • 실행 : http://localhost:8080/servlet/members/save

 

package hello.servlet.web.servlet;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "memberSaveServlet", urlPatterns = "/servlet/members/save")
public class MemberSaveServlet extends HttpServlet {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // html 폼에서 보낸 데이터 읽기
        System.out.println("MemberSaveServlet.service");
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        // 파라미터(폼)에 입력된 데이터로 Member객체 만들고, MemberRepository에 회원 데이터 저장
        Member member = new Member(username, age);
        memberRepository.save(member);

        // 결과화면으로 html을 동적으로 만들어 응답보내기
        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>");
    }
}

 

 

2-3. 저장된 회원 목록 조회하는 기능

 

* MemberListServlet - 회원 목록

  • 작동 순서
    • MemberRepository의 findAll() 메소드를 통해 모든 회원 조회
    • for문을 통해 회원 목록 HTML을 동적으로 생성하여 응답
  • 실행 : http://localhost:8080/servlet/members

 

package hello.servlet.web.servlet;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

@WebServlet(name = "memberListServlet", urlPatterns = "/servlet/members")
public class MemberListServlet extends HttpServlet {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

	// findAll() 메소드로 회원목록 조회
        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>");

        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을 동적으로 만들어본 것

-> 매우 비효율적

-> 템플릿 엔진 등장 (JSP, Thymleaf, Freemarker, Velocity 등)

 

 


 

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

( * 인텔리제이 무료버전은 JSP 편의 기능 X -> 일일이 작성, 색깔 표현 없음 ㅠ )

 

3-1. JSP 라이브러리 추가 -> Gradle refresh

 

build.gradle 의 dependencies (스프링부트 3.0 이상)

//JSP 추가 시작
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'jakarta.servlet:jakarta.servlet-api' //스프링부트 3.0 이상
implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api' //스프링부트 3.0 이상
implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl' //스프링부트 3.0 이상
//JSP 추가 끝

 

 

 

3-2. 회원등록 폼 JSP 

 

* new-form.jsp

  • 첫 줄에 JSP 문서라는 문장 삽입
    ( <%@ page contentType="text/html;charset=UTF-8" language="java" %> )
  • 첫 줄 제외하고는 HTML과 똑같음. 
    ( JSP는 서버 내부에서 서블릿으로 변환 )
  • 실행 : http://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>

 

 

3-3. 회원 저장 JSP

 

* save.jsp

  • JSP는 자바 코드를 그대로 다 사용할 수 있다
  • 서블릿과 동일한 로직 (단, 자바코드를 <% %> 안에 넣어주고, import 필요)
  • 로직 -> 자바코드
    뷰(응답) -> HTML 코드
  • request, response 는 바로 사용 가능 (jsp도 어차피 서블릿으로 변경되어서 실행되기 때문)
  • 자바의 import문
    <%@ page import="hello.servlet.domain.member.MemberRepository" %>
  • 자바 코드 입력
    <% ~~ %>
  • 자바 코드 출력
    <%= ~~ %>

 

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%
    // 서블릿과 동일한 로직 (단, 자바 객체 import 필요)
    // request, response 는 그냥 사용 가능 (jsp도 어차피 서블릿으로 변경되어서 실행되기 때문)

    MemberRepository memberRepository = MemberRepository.getInstance();

    // html 폼에서 보낸 데이터 읽기
    System.out.println("MemberSaveServlet.service");
    String username = request.getParameter("username");
    int age = Integer.parseInt(request.getParameter("age"));

    // 파라미터(폼)에 입력된 데이터로 Member객체 만들고, MemberRepository에 회원 데이터 저장
    Member member = new Member(username, age);
    memberRepository.save(member);
%>
<html>
<head>
    <title>Title</title>
</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>

 

 

 

3-4. 회원 목록 JSP 

 

* members.jsp

  • List도 import 필요
  • 자바 코드로 회원 저장소에 있는 회원목록 조회 -> HTML 코드로 출력

 

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.List" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%
    MemberRepository memberRepository = MemberRepository.getInstance();
    List<Member> members = memberRepository.findAll();
%>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <a href="/index.html">메인</a>
    <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>
</body>
</html>

 

 

 

** 서블릿과 JSP의 한계

 

  • 서블릿 : 뷰(응답) 화면을 위한 HTML 코드를 자바 코드로 일일이 구현해야 해서 지저분하고 복잡
  • JSP : 응답으로 보내는 HTML은 간편하게 작성할 수 있게 됨 + 동적으로 변경 필요한 부분에만 자바 코드 적용
               비즈니스 로직을 위한 자바코드 + 뷰(응답) 화면을 위한 HTML 코드가 한 페이지에 모두 적혀있어서 매우 복잡

 ---> MVC 패턴 등장

 

 

 


 

 

4. MVC 패턴 

 

4-1. 개요

 

하나의 서블릿, JSP로 처리하던 것을 컨트롤러, 뷰라는 영역으로 서로 역할을 나눈 것

 

컨트롤러 (서블릿) : HTTP 요청을 받아서 파라미터 검증, 비즈니스 로직 실행 , 뷰에 전달할 결과 데이터를 조회해서 모델에 담음

모델 : 뷰에 출력할 데이터를 담음. 

 (JSP) : 모델에 담겨있는 데이터를 사용해서 화면에 구현 - HTML 생성하는 부분

 

Model 1 : JSP에서 컨트롤러, 뷰의 역할을 모두 함 Model 2 : 컨트롤러(Servelt), 뷰(JSP) 구분 

 

 

 

 

MVC 패턴 1

 

 

MVC 패턴 2

 

 

 

💡 참고

DAO : 데이터 접근 객체 (데이터베이스에 접근하는 역할)
DTO : 데이터 전송 객체 (계층간 데이터 전송할 때)

 

 

 

4-2. MVC 패턴 적용

 

* Model은 HttpServletRequest 객체 사용. request는 내부에 저장소 가지고 있음

 

 

1) 회원 등록

 

(1) 회원 등록 폼 - 컨트롤러

 

* MvcMemberFormServlet

  • dispatcher.forward() : 다른 서블릿이나 JSP로 이동할 수 있는 기능. 서버 내부에서 다시 호출 발생
  • redirect vs forward
    • redirect : 실제 클라이언트(웹 브라우저)에 응답이 나갔다가, 클라이언트가 redirect 경로로 다시 요청
      -> 클라이언트가 인지할 수 있고, URL 경로도 변경
    • forward : 서버 내부에서 일어나는 호출. 메소드 호출하듯이 클라이언트까지 가지 않고 내부에서만 이동.
      -> 클라이언트가 인지 X, URL 경로 변경 X 
  • /WEB-INF : 외부에서 직접 경로를 입력하여 JSP를 호출할 수 X -> 항상 컨트롤러를 통해 JSP 호출

 

package hello.servlet.web.servletmvc;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@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 {

        // 비즈니스 로직은 없지만 컨트롤러를 무조건 거치기 때문에 컨트롤러 -> 뷰로 이동하는 코드 구현

        // 뷰의 위치
        String viewPath = "/WEB-INF/views/new-form.jsp";
        // 컨트롤러에서 뷰로 이동할 때 사용 (이 경로로 이동할거야)
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        // 이걸 호출하면 서블릿에서 jsp를 찾아서 호출 (메소드 호출하듯이, 클라이언트까지 가지 않고 내부 호출이기 때문에 url이 바뀌지 않음)
        dispatcher.forward(request, response);

    }
}

 

 

(2) 회원 등록 폼 -

 

* /WEB-INF/views/new-form.jsp

  • action : 절대경로(/로 시작)가 아니라 상대경로(/로 시작X) 
    -> 현재 경로 + save 로 호출
    결과 : /servlet-mvc/members/save
  • 실행 : http://localhost:8080/servlet-mvc/members/new-form

 

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<!-- 상대경로 사용, [현재 URL이 속한 계층 경로 + /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>

 

 

2) 회원 저장

 

(1) 회원 저장 - 컨트롤러 

 

* MvcMemberSaveServlet

  • HttpServletRequest를 Model로 사용
  • 컨트롤러 역할 수행
    => 요청 파라미터 받음, 비즈니스 로직 실행(호출), Model에 데이터 담음, 컨트롤러에서 뷰로 이동 

 

package hello.servlet.web.servletmvc;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemeberSaveServlet extends HttpServlet {

    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);

    }
}

 

 

(2) 회원 저장 -

 

* /WEB-INF/views/save-result.jsp

  • request의 attribute에 담긴 데이터 편리하게 조회 -> ${ } 문법 (프로퍼티 접근법)
  • 실행 : http://localhost:8080/servlet-mvc/members/save

 

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
성공
<ul>
    <li>id=${member.id}</li>
    <li>username=${member.username}</li>
    <li>age=${member.age}</li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>

 

 

 

 

3) 회원 목록 조회

 

(1) 회원 목록 - 컨트롤러 

 

* MvcMemberListServlet

 

package hello.servlet.web.servletmvc;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.List;

@WebServlet(name = "mvcMemberListServlet", urlPatterns = "/servlet-mvc/members")
public class MvcMemberListServlet extends HttpServlet {

    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);

    }
}

 

 

(2) 회원 목록 -

 

* /WEB-INF/views/members.jsp

  • model에 담아둔 members데이터를 JSP가 제공하는 taglib 기능을 사용해서 반복 출력
  • 실행 : http://localhost:8080/servlet-mvc/members

 

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <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>

 

 

 

4-3. MVC 패턴의 한계

 

1) MVC 패턴 장점

 

컨트롤러 - 컨트롤러의 역할만 수행 

뷰 - 뷰의 역할만 수행, 코드 깔끔

 

2) MVC 패턴 단점

 

(1) 포워드 중복

RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);

 

(2) ViewPath 중복

String viewPath = "/WEB-INF/views/new-form.jsp";

prefix : /WEB-INF/viewssuffix : .jsp그리고 jsp가 아닌 다른 뷰로 변경한다면 전체 코드 다 변경해야 함

 

(3) 사용하지 않는 코드

특히 response는 사용되지 않음

 

=> 공통 처리 어려움

메소드로 따로 만든다고 해도, 해당 메소드를 항상 호출해야 함

 

=> 컨트롤러 호출 전에 먼저 공통 기능 처리 => 프론트 컨트롤러(Front Controller) 패턴 도입 

(≠ 필터. 필터는 정해진 대로 전처리 기능을 수행해야 하지만, 프론트 컨트롤러는 컨트롤러처럼 다 조작할 수도 있음)