회원 정보
기능 요구사항
회원 도메인 모델
: servlet > domain 패키지를 생성하고, 내부에 memebr 패키지를 생성
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;
}
}
회원 저장소 MemberRepository 클래스 생성
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();
}
}
Repository 테스트 클래스 생성
public class MemberRepositoryTest {
MemberRepository memberRepository = MemberRepository.getInstance();
@AfterEach
void afterEach(){
memberRepository.clearStore();
}
@Test
void save(){
//given
Member member = new Member("hello", 20);
//when
Member saveMember = memberRepository.save(member);
//then
Member findMember = memberRepository.findById(saveMember.getId());
assertThat(findMember).isEqualTo(saveMember);
}
@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();
//then
assertThat(result.size()).isEqualTo(2);
assertThat(result).contains(member1,member2);
}
}
순수하게 서블릿으로만 HTML Form 까지 작성할 수 있다.
MemberFormServlet 클래스
회원 등록용 HTML 서블릿
@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");
}
}

전송을 클릭하여 DB에 저장 후 결과를 보여줄 서블릿을 만들어보자
MemberSaveServlet 클래스
@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);
memberRepository.save(member);
//save로직 수행 후 결과 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>");
}
}


마지막으로 목록을 보여줄 서블릿을 만들어보자
MemberListServlet 클래스
@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");
//멤버 리스트 HTML 출력
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>");
}
}
저장한 후, http://localhost:8080/servlet/members 에 접속하면,

을 출력할 수 있다.
MemberListServlet 은 다음 순서로 동작한다.
1) memberRepository.findAll() 을 통해 모든 회원을 조회한다.
2) 회원 목록 HTML을 for 루프를 통해서 회원 수 만큼 동적으로 생성하고 응답한다.
템플릿 엔진으로
지금까지 서블릿과 자바코드로만 HTML을 만들어 보았다. 서블릿 덕분에 동적으로 원하는 HTML을 만들 수 있었다.
하지만, 코드를 보게 되면 매우 복잡하고 비효율적임을 알 수 있다. 자바 코드로 HTML을 만들어내는 것보다, HTML문서에 동적으로 변경해야 하는 부분을 자바 코드를 넣을 수 있다면 더 편리하다는 것을 알 수 있다.
--> 템플릿 엔진을 사용하게 되면, HTML 문서에 필요한 곳만 동적으로 변경할 수 있다.
JSP를 사용하기 전에 build.gradle에 라이브러리를 추가해보자.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
//JSP 추가 시작
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'javax.servlet:jstl'
//JSP 추가 끝
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
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>
위의 코드를 살펴보면, 첫 줄을 제외하고는 HTML과 동일한 모습을 하고 있으며, 서버 내부에서 서블릿으로 변환한다.
회원 저장 후, 결과를 보여줄 저장 jsp를 만들어보자
save.jsp(회원 저장)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
MemberRepository memberRepository = MemberRepository.getInstance();
System.out.println("MemberSaveServlet.service");
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
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>
JSP 에서는 동적으로 표현하기 위해 (JAVA코드를 사용하기 위해) 스크립트를 사용하는데,
members.jsp (회원 목록)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
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의 한계
MVC 패턴의 등장
너무 많은 역할
변경의 라이프 사이클
기능 특화
: MVC 패턴은 지금까지 학습한 것처럼 하나의 서블릿이나, JSP로 처리하던 것을 컨트롤러(Controller)와 뷰(View)라는 영역으로 서로 역할을 나눈 것을 말한다.
웹 애플리케이션은 보통 이 MVC 패턴을 사용한다.
참고
MVC 패턴

서블릿을 컨트롤러로 사용하고, JSP를 뷰로 사용해서 MVC 패턴을 적용해보자
Model은 HttpServletRequest 객체를 사용한다. request는 내부에 데이터 저장소를 가지고 있는데 request.setAttribute()로 데이터를 저장하고, request.getAttribute()로 데이터를 꺼낼 수 있다.

MvcMemberFormServlet(회원 등록)
@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);
dispatcher.forward(request,response);
}
}
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>
현재 계층 경로: /servlet-mvc/members/
결과: /servlet-mvc/members/save
ex) /servlet-mvc/members/new-form -> /servlet-mvc/members/save
MvcMemberSaveServlet 회원 저장
@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet 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);
//view로 전달
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request,response);
}
}
save-result.jsp 회원 저장 뷰
<%@ 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>
MvcMemebrListServlet 회원 목록 조회
@WebServlet(name = "mvcMemberListServlet", urlPatterns = "/servlet-mve/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);
}
}
members.jsp 회원 목록 조회 뷰
<%@ 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>
MVC 컨트롤러의 단점

정리하면 공통 처리가 어렵다는 문제가 있다