1 . 회원 관리 웹 애플리케이션 요구사항
- 회원 정보 : 이름(username), 나이(age)
- 기능 요구사항 : 회원 저장, 회원 목록 조회
먼저 main/hello/servlet에 domain 패키지를 생성하고, 그 안에 member 패키지를 만든 후, 그 안에 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;
}
}
그 다음 main/java/servlet/domain/member에 MemberRepository 클래스를 생성한다.
이때 다음 테스트에 영향을 주지 않도록 각 테스트의 저장소를 clearStore()를 호출해서 초기화해야 한다.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 동시성 문제가 고려되어 있지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용 고려
*/
public class MemberRepository {
// id 하나씩 증가
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
//[싱글톤]
//싱글톤의 유일한 인스턴스를 저장할 정적 변수(private)
private static final MemberRepository instance = new MemberRepository();
//유일한 인스턴스를 반환하는 정적 메서드(public)
public static MemberRepository getInstance() {
return instance;
}
//private 생성자로 외부에서 직접 인스턴스를 생성하는 것을 막음
//초기화 코드
private MemberRepository() {
}
//[save]
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 날리기
public void clearStore(){
store.clear();
}
}
테스트 코드(MemberRepositoryTest)를 작성해보자.
cf) Ctrl + Shift + T : 테스트하고자 하는 클래스를 드래그하고 단축키 입력 시, 테스트 클래스가 생성된다.
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
class MemberRepositoryTest {
MemberRepository memberRepository = MemberRepository.getInstance();
//테스트 후 초기화
@AfterEach
void afterEach() {
memberRepository.clearStore();
}
// save 테스트
@Test
void save() {
//given
Member member = new Member("hello", 20);
//when
Member savedMember = memberRepository.save(member);
//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에 저장
memberRepository.save(member1);
memberRepository.save(member2);
//when
List<Member> result=memberRepository.findAll();
//then
assertThat(result.size()).isEqualTo(2);
assertThat(result).contains(member1,member2);
}
}
2 . 서블릿으로 회원 관리 웹 애플리케이션 만들기
1) 회원 등록 폼
main/hello/servlet/domain/member에 MemberRepository 클래스를 생성한다.
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 {
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");
}
}
실행되는지 확인하기 위해 http://localhost:8080/servlet/members/save에 들어가서 username, age를 입력하면 다음과 같이 오류 화면이 뜬다. 이는 /servlet/members/save를 지정하지 않았기 때문이다.
2) 회원 저장
main/hello/servlet/web/servlet에 MemberSaveServlet 클래스를 생성한다.
1. 파라미터를 조회해서 Member 객체 생성
2. Member 객체를 MemberRepository를 통해서 저장
3. Member 객체를 사용해서 결과 화면용 HTML을 동적으로 만들어서 응답
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 {
private MemberRepository memberRepository=MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("MemberSaveServlet.service");
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
System.out.println("member = " + member);
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>");
}
}
3) 회원 목록 조회
main/hello/servlet/web/servlet에 memberListServlet 클래스를 생성한다.
1. memberRepository.findAll() 을 통해 모든 회원 조회
2. 회원 목록 HTML을 for 루프를 통해서 회원 수 만큼 동적으로 생성하고 응답
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 {
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>");
/*
w.write(" <tr>");
w.write(" <td>1</td>");
w.write(" <td>userA</td>");
w.write(" <td>10</td>");
w.write(" </tr>");
*/
//동적으로 만들기 위해
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>");
}
}
추가적으로 welcome 페이지를 바꾸기 위해, src/main/webapp/basic/index.html을 다음 코드로 바꾸자.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li><a href="basic.html">서블릿 basic</a></li>
<li>서블릿
<ul>
<li><a href="/servlet/members/new-form">회원가입</a></li>
<li><a href="/servlet/members">회원목록</a></li>
</ul>
</li>
<li>JSP
<ul>
<li><a href="/jsp/members/new-form.jsp">회원가입</a></li>
<li><a href="/jsp/members.jsp">회원목록</a></li>
</ul>
</li>
<li>서블릿 MVC
<ul>
<li><a href="/servlet-mvc/members/new-form">회원가입</a></li>
<li><a href="/servlet-mvc/members">회원목록</a></li>
</ul>
</li>
<li>FrontController - v1
<ul>
<li><a href="/front-controller/v1/members/new-form">회원가입</a></li>
<li><a href="/front-controller/v1/members">회원목록</a></li>
</ul>
</li>
<li>FrontController - v2
<ul>
<li><a href="/front-controller/v2/members/new-form">회원가입</a></li>
<li><a href="/front-controller/v2/members">회원목록</a></li>
</ul>
</li>
<li>FrontController - v3
<ul>
<li><a href="/front-controller/v3/members/new-form">회원가입</a></li>
<li><a href="/front-controller/v3/members">회원목록</a></li>
</ul>
</li>
<li>FrontController - v4
<ul>
<li><a href="/front-controller/v4/members/new-form">회원가입</a></li>
<li><a href="/front-controller/v4/members">회원목록</a></li>
</ul>
</li>
<li>FrontController - v5 - v3
<ul>
<li><a href="/front-controller/v5/v3/members/new-form">회원가입</a></
li>
<li><a href="/front-controller/v5/v3/members">회원목록</a></li>
</ul>
</li>
<li>FrontController - v5 - v4
<ul>
<li><a href="/front-controller/v5/v4/members/new-form">회원가입</a></
li>
<li><a href="/front-controller/v5/v4/members">회원목록</a></li>
</ul>
</li>
<li>SpringMVC - v1
<ul>
<li><a href="/springmvc/v1/members/new-form">회원가입</a></li>
<li><a href="/springmvc/v1/members">회원목록</a></li>
</ul>
</li>
<li>SpringMVC - v2
<ul>
<li><a href="/springmvc/v2/members/new-form">회원가입</a></li>
<li><a href="/springmvc/v2/members">회원목록</a></li>
</ul>
</li>
<li>SpringMVC - v3
<ul>
<li><a href="/springmvc/v3/members/new-form">회원가입</a></li>
<li><a href="/springmvc/v3/members">회원목록</a></li>
</ul>
</li>
</ul>
</body>
</html>
localhost:8080/index.html을 실행하면 다음과 같은 화면이 나오게 된다.
3 . JSP로 회원 관리 웹 애플리케이션 만들기
최근에는 JSP를 잘 사용하지 않는 추세이다. 그래도 JSP에도 장점이 있으므로 간단히 알아보도록 하자.
먼저 JSP를 사용하기 위해 build.gradle에 다음 라이브러리를 추가해야 한다.
//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 추가 끝
1) 회원 등록 폼 JSP
src/main/webapp에 jsp패키지를 생성하고, 그 안에 members 패키지를 생성한 다음, 그 안에 new-form.jsp이름의 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>
2) 회원 저장 JSP
src/main/webapp/jsp/members에 save.jsp 이름의 jsp 파일을 생성한다. 이때 메인 로직의 코드는 MemberListServlet 로직의 코드와 동일하기 때문에 그대로 가져와서 붙인다.
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="java.lang.reflect.Member" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
//request, response 사용 가능
//MemberListServlet 클래스의 로직 그대로 사용 가능
MemberRepository memberRepository=MemberRepository.getInstance();
System.out.println("MemberSaveServlet.service");
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
hello.servlet.domain.member.Member member = new hello.servlet.domain.member.Member(username, age);
System.out.println("member = " + member);
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>
- import 문 : <%@ page import="hello.servlet.domain.member.MemberRepository" %>
- 자바 코드 입력 : <% ~~ %>
- 자바 코드 출력 : <%= ~~ %>
cf) 파일 목록 보기 : Ctrl + E
3) 회원 목록 보기
src/main/webapp/jsp에 members.jsp란 이름의 jsp 파일을 생성한다.
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
//MemberListServlet 클래스에서 필요한 로직 가져오기
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>
<%
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>
localhost:8080/index.html의 jsp 회원 목록에 들어가면 다음과 같은 화면이 출력된다.
- 서블릿 & JSP 한계
서블릿으로 개발할 때는 뷰(View)화면을 위한 HTML을 만드는 작업이 자바 코드에 섞여서 지저분하고 복잡하다. JSP를 사용한 덕분에 뷰를 생성하는 HTML 작업을 깔끔하고, 중간중간 동적으로 변경이 필요한 부분에만 자바 코드를 적용한다.
반면 회원 저장 JSP를 고려해보자. 코드의 상위 절반은 회원을 저장하기 위한 비즈니스 로직이고, 나머지 하위 절반만 결과를 HTML로 보여주기 위한 뷰 영역이다. JAVA 코드, 데이터를 조회하는 리포지토리 등등 다양한 코드가 모두 JSP에 노출되어 있기 때문에 JSP가 너무 많은 역할을 한다.
- MVC 패턴의 등장
위와 같은 서블릿과 JSP의 한계를 보완하고자, 비즈니스 로직은 서블릿처럼 다른곳에서 처리하고, JSP는 목적에 맞게 HTML로 화면을 그리는 일에 집중하도록 하는 MVC가 등장했다.
4 . MVC 패턴 - 개요
- MVC 패턴 이전
MVC를 사용하기 전에는 서블릿과 JSP를 이용하여 비즈니스 로직과 뷰 로직을 하나의 코드로 관리하였다. 그러나 이 경우 둘 사이의 변경 라이프 사이클이 다르기 때문에, UI를 일부 수정하는 일과 비즈니스 로직을 수정하는 일은 각각 다르게 발생할 가능성이 있기 때문에 유지보수가 어렵다는 단점이 있다.
- MVC 패턴 1
JSP와 같은 뷰 템플릿은 화면을 렌더링 하는데 최적화되어 있기 때문에 이 부분의 업무만 담당하는 것이 효과적이다. 이때 사용 가능한 것이 MVC(Model View Controller)이다. MVC 패턴은 하나의 서블릿이나, JSP로 처리하던 것을 컨트롤러와 뷰라는 영역으로 서로 역할을 나눈 것이다.
클라이언트가 요청을 하면 컨트롤러에서 비즈니스 로직을 수행하고, 데이터를 모델에 담는다. 그리고 컨트롤러가 뷰 로직을 실행하는데, 뷰 로직이 모델에 있는 데이터를 참고해서 뷰를 실행하고 응답한다.
- 컨트롤러 : HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다. 그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담는다.
- 모델 : 뷰에 출력할 데이터를 담아둔다. 뷰가 필요한 데이터를 모두 모델에 담아서 전달해주는 덕분에 뷰는 비즈니스 로직이나 데이터 접근을 몰라도 되고, 화면을 렌더링 하는 일에 집중할 수 있다
- 뷰 : 모델에 담겨있는 데이터를 사용해서 화면을 그리는 일에 집중한다. 여기서는 HTML을 생성하는 부분을 말한다.
- MVC 패턴 2
컨트롤러에 비즈니스 로직을 둘 수도 있지만, 이렇게 되면 컨트롤러가 너무 많은 역할을 담당한다. 그래서 일반적으로 비즈니스 로직은 서비스(Service)라는 계층을 별도로 만들어서 처리한다. 그리고 컨트롤러는 비즈니스 로직이 있는 서비스를 호출하는 역할을 담당한다.
5 . MVC 패턴 - 적용
서블릿을 컨트롤러로 사용하고, JSP를 뷰로 사용해서 MVC 패턴을 적용해보자. Model은 HttpServletRequest 객체를 사용한다. 이때 request는 내부에 데이터 저장소를 가지고 있는데, request.setAttribute() , request.getAttribute() 를 사용하면 데이터를 보관하고, 조회할 수 있다.
1) 회원 등록 폼 - 컨트롤러
main/hello/servlet/web에 servletmvc 패키지를 생성하고, 그 안에 MvcMemberFormServlet 클래스를 생성한다.
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 {
// 경로를 따라 서블릿에서 jsp 호출
String viewPath="/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher=request.getRequestDispatcher(viewPath);
dispatcher.forward(request,response);
}
}
- dispatcher.forward() : 다른 서블릿이나 JSP로 이동할 수 있는 기능. 서버 내부에서 다시 호출이 발생
- /WEB-INF : 이 경로안에 JSP가 있으면 외부에서 직접 JSP를 호출 불가. 항상 컨트롤러를 통해서 JSP를 호출하도록 한다
- redirect : 실제 클라이언트에 응답이 나갔다가, 클라이언트가 redirect 경로로 다시 요청한다. 따라서 클라이언트가 인지할 수 있고, URL 경로도 실제로 변경된다.
- forward : 서버 내부에서 일어나는 호출이기 때문에 클라이언트가 전혀 인지하지 못한다.
2) 회원 등록 폼 - 뷰
main/webapp에 WEB-INF 패지키를 생성하고, 그 안에 new-form.jsp라는 이름의 파일을 생성한다.
<%@ 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
form의 action을 보면 절대 경로( / 로 시작)가 아니라 상대경로( / 로 시작X)인 것을 확인할 수 있다. 이렇게 상대경로를 사용하면 폼 전송시 현재 URL이 속한 계층 경로 + save가 호출된다.
-> 현재 계층 경로: /servlet-mvc/members/ , 결과: /servlet-mvc/members/save
3) 회원 저장 - 컨트롤러
main/java/hello/servlet/web/servletmvc에 MvcMemberSaveServlet 클래스를 생성한다.
HttpServletRequest를 Model로 사용한다. 이때 request가 제공하는 setAttribute() 를 사용하면 request 객체에 데이터를 보관해서 뷰에 전달할 수 있다.
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 MvcMemberSaveServlet extends HttpServlet {
private MemberRepository memberRepository=MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//MemberSaveServlet 클래스에서 로직 코드 가져오기
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
System.out.println("member = " + member);
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);
}
}
4) 회원 저장 - 뷰
main/webapp/WEB-INF에 save-result.jsp란 이름의 jsp 파일을 생성한다.
뷰는 request.getAttribute() 를 사용해서 데이터를 꺼낸다. 또한 ${} 문법을 사용해서 request의 attribute에 담긴 데이터를 편리하게 조회하는 코드를 작성한다.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
</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>
5) 회원 목록 조회 - 컨트롤러
main/java/hello/servlet/web/servletmvc에 MvcMemberListServlet 클래스를 생성한다.
request 객체를 사용해서 List members 를 모델에 보관하도록 아래와 같이 코드를 작성한다.
import hello.servlet.domain.member.MemberRepository;
import hello.servlet.web.servlet.MemberListServlet;
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="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);
}
}
6) 회원 목록 조회 - 뷰
다음으로 /WEB-INF/views/members.jsp의 members.jsp를 만들어야 한다. main/webapp/WEB-INF에 members.jsp란 이름의 jsp 파일을 생성한다.
members 리스트에서 member 를 순서대로 꺼내서 item 변수에 담고, 출력하는 과정을 반복한다. 이때 <c:forEach> 기능을 사용하기 위해 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>를 선언한다.
<%@ 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>
6 . MVC 패턴 - 한계
MVC 패턴을 적용한 덕분에 컨트롤러의 역할과 뷰를 렌더링하는 역할을 명확하게 구분할 수 있다. 특히 뷰는 단순하게 모델에서 필요한 데이터를 꺼내고 화면을 만들면 되기 때문에, 코드가 깔끔하고 직관적이다. 그러나 컨트롤러는 중복이 많고, 필요하지 않는 코드들도 많이 나온다. MVC 컨트롤러의 단점에 대해 자세히 알아보도록 하자.
- 포워드 중복
View로 이동하는 다음 코드가 항상 중복 호출되어야 한다.
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
- ViewPath 중복
String viewPath = "/WEB-INF/views/new-form.jsp";
prefix(앞) : /WEB-INF/views/ , suffix(뒤) : .jsp
그러나 만약 jsp가 아닌 thymeleaf 같은 다른 뷰로 변경한다면 전체 코드를 다 변경해야 하는 문제가 발생한다.
- 사용하지 않는 코드
HttpServletRequest request, HttpServletResponse response를 사용할 때도 있고, 사용하지 않을 때도 있다. 그러나 테스트 코드를 작성할 때, HttpServletRequest, HttpServletResponse를 사용해야 하기 때문에 컨트롤러에서 테스트 케이스를 작성하기 어렵다.
- 공통 처리가 어렵다
기능이 복잡해질 수록 컨트롤러에서 공통으로 처리해야 하는 부분이 점점 더 많이 증가할 것이다. 결과적으로 해당 메서드를 항상 호출해야 하고, 실수로 호출하지 않으면 문제가 된다. 무엇보다 호출하는 것 자체도 중복이다.
-> 위 문제를 해결하려면 컨트롤러 호출 전에 먼저 공통 기능을 처리해야 한다. 프론트 컨트롤러(Front Controller) 패턴을 도입하면 이런 문제를 해결할 수 있다.
'Spring > MVC 1편' 카테고리의 다른 글
인프런 MVC 1 강의 정리 #6 (0) | 2024.01.09 |
---|---|
인프런 MVC 1 강의 정리 #5 (1) | 2024.01.08 |
인프런 MVC 1 강의 정리 #4 (0) | 2024.01.01 |
인프런 MVC 1 강의 정리 #2 (0) | 2023.12.27 |
인프런 MVC 1 강의 정리 #1 (1) | 2023.12.27 |