김영한님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(유료강의)을 보면서 공부한 내용입니다.
- 회원 정보
username, age- 기능
회원 저장, 회원 목록 조회
package hello.servlet.domain.member
회원 도메인에 필요한 id(식별자), username, age를 만들고
기본 생성자와 username과 age 정보가 들어간 회원 정보 생성자를 만든다.
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;
}
}
hello.servlet.domain.member
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 {
// 데이터 저장하는 키 : id, 값 : member
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
// 싱글톤으로 생성
// 객체의 인스턴스가 오직 1개만 생성 => 외부에서 호출 못하게 private으로 지정
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;
}
// id로 회원 조회
public Member findById(Long id){
return store.get(id);
}
// 전체 회원 조회
// 저장소에 있는 값들을 찾아서 새로운 ArrayList에 넣어줌
// 왜? new ArrayList에 값을 넣거나 조작해도 store에 있는 값들을 건드리고 싶지 않아서
public List<Member> findAll(){
return new ArrayList<>(store.values());
}
// 저장소 데이터 삭제
public void clearStore(){
store.clear();
}
}
회원 저장소 기능이 제대로 작동하는지 테스트 코드(Ctrl+Shift+T)를 만들어서 확인한다.
package hello.servlet.domain.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 {
MemberRepository memberRepository = MemberRepository.getInstance();
// 테스트 끝난 후 초기화
@AfterEach
void afterEach(){
memberRepository.clearStore();
}
@Test // 저장 테스트
void save(){
// given : 이런게 주어졌을 때
...
// when : 이런걸 실행했을 때
...
// then : 결과가 이거여야돼
...
}
@Test // 조회 테스트
void findAll(){
// given
...
// when
...
// then
...
}
}
package hello.servlet.web.servlet;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.validation.annotation.Validated;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.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(...);
}
}
http://localhost:9090/servlet/members/new-form
에 들어가 보면 HTML 폼의 화면을 확인할 수 있다.
package hello.servlet.web.servlet;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name="memberSaveServlce", 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"));
// request.getParameter의 응답타입은 항상 문자이므로 숫자타임으로 변환시켜야된다.
Member member = new Member(username,age);
memberRepository.save(member);
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter w = response.getWriter();
w.write(...);
}
}
package hello.servlet.web.servlet;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.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(...);
}
}
http://localhost:9090/servlet/members/new-form
에 들어가 보면 저장된 회원 목록을 조회할 수 있다.
JSP를 하기 전에 build.gradle
에 있는 dependencies
안에 추가적으로 코드를 생성해야된다.
//JSP 추가 시작
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'javax.servlet:jstl'
//JSP 추가 끝
src/main/webapp/jsp/members/new-form.jsp
JSP폼 안에는 반드시
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
문구가 있어야 한다.
<%@ 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>
src/main/webapp/jsp/members/save.jsp
<%@ page import="~~~" %> // 자바의 import 문
<% ~~ %> // 자바 코드 입력, request, response 사용 가능
<%= ~~ %> // 자바 코드 출력
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ 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" %>
<%
// MemberSaveServlet와 동일
%>
<html>
<head>
<title>Title</title>
</head>
<body>
성공
<ul>
<li>id=<%= ... %></li>
<li>username=<%= ... %></li>
<li>age=<%= ... %></li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
src/main/webapp/jsp/members.jsp
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="java.util.List" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// MemberListServlet 와 동일
%>
<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(" </tr>");
}
%>
</tbody>
</table>
</body>
</html>
Model View Controller(MVC)
서블릿이나, JSP로 처리하던 것을 컨트롤러(Controller)와 뷰(View) 라는 영역으로 역할을 나눈 것
컨트롤러 (Controller)
HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다. 그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담는다.
모델 (Model)
뷰에 출력할 데이터를 담아 전달한다.
뷰 (View)
모델에 담겨있는 데이터를 사용해서 화면을 생성한다. ex) HTML 생성
서블릿은 컨트롤러
로 사용하고, JSP는 뷰
로, Model은 HttpServletRequest 객체
를 사용한다. request는 내부에 데이터 저장소를 가지고 있고, request.setAttribute() , request.getAttribute()
를 사용하면 데이터를 보관하고, 조회할 수 있다.
hello.servlet.web.servletmvc.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); // 서블릿(컨트롤러)에서 jsp를 호출
}
}
WEB-INF : 이 경로안에 JSP가 있으면 외부에서 직접 JSP를 호출할 수 없다. 항상 컨트롤러를 통해서 JSP를 호출해야 한다.
main/webapp/WEB-INF/views/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">
...
</form>
</body>
</html>
여기서 form의 action을 보면 절대 경로( / 로 시작)가 아니라
상대경로( / 로 시작X)인 것을 확인
할 수 있다. 상대경로를 사용하면 폼 전송시현재 URL이 속한 계층 경로 + save
가 호출된다.
- 현재 계층 경로:
/servlet-mvc/members/
- 결과:
/servlet-mvc/members/save
src/main/java/hello/servlet/web/servletmcv/MvcMemberSaveServlet
@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); // 내부에 member 저장
String viewPath = "/WEB-INF/views/save-result.jsp";
...
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
성공
<ul>
<%--
<li>id=<%=((Member)request.getAttribute("member")).getId()%></li>
...
--%>
<li>id=${member.id}</li>
<li>...</li>
<li>...</li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
src/main/java/hello/servlet/web/servletmcv/MvcMemberListServlet
@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";
...
}
}
src/main/webapp/WEB-INF/views/members.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!-- jsp에서 반복문을 사용할 수 있는 구문 -->
<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>
...
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>