회원관리 웹 애플리케이션을 만든다. 서블릿, JSP 순서대로 만들어가면서 어떤 점이 개선되어야 하는지, 그리고 개선점이 어떻게 반영되어 현재의 Spring MVC에 도달하였는지 이해해보자. 앞서 HttpServletRequest, HttpServletResponse 객체에 대해 이해했다면, 코드는 길지만 쉬운, 실습 목적의 내용이다.
1. 회원관리 객체 생성
Member 클래스를 만든다. id, username, age를 필드로 갖는다.
Member 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
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;
}
public void setId(Long id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setAge(Integer age) {
this.age = age;
}
}
|
cs |
그리고 Member 클래스를 저장할 메모리 저장소인 MemberRepository를 만든다. 스프링 MVC를 사용하는 것이 아니라 순수 자바를 통해서 코딩하므로, MemberRepository를 instance 형태로 만들고, private으로 생성자를 만들어서 외부에서의 접근을 막는다. 외부 접근은 getInstance() 메서드로만 가능하도록 한다. 그리고 save, findById, findAll, clearStore 등 기본적인 메모리 접근 메서드를 만들어준다.
MemberRepository 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
package hello.servlet.domain.member;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 실무에서는 동시성 문제를 고려하여 ConccurentHashMap, AtomicLong 사용을 고려함
*/
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();
}
}
|
cs |
다음과 같이 테스트를 해본다. 여기서 @AfterEach 어노테이션을 사용하면, 단위 테스트가 끝날 때마다 @AfterEach 내부의 코드를 실행한다. 테스트가 끝날 때마다 clearStore() 메서드를 실행하여 메모리인 MemberRepository가 비어있도록해야 다른 테스트에 지장이 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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.assertThat;
import static org.junit.jupiter.api.Assertions.*;
class MemberRepositoryTest {
MemberRepository memberRepository = MemberRepository.getInstance();
@AfterEach
void afterEach() {
memberRepository.clearStore();
}
@Test
void save() {
//given
Member member = new Member("hello", 100);
//when
Member savedMember = memberRepository.save(member);
//then
Member foundMember = memberRepository.findById(savedMember.getId());
assertThat(foundMember.getUsername()).isEqualTo("hello");
}
@Test
void findAll() {
//given
Member member1 = new Member("member1", 10);
Member member2 = new Member("member2", 20);
memberRepository.save(member1);
memberRepository.save(member2);
//when
List<Member> members = memberRepository.findAll();
//then
Assertions.assertThat(members.size()).isEqualTo(2);
Assertions.assertThat(members).contains(member1, member2);
}
}
|
cs |
2. 서블릿으로 회원관리 웹 애플리케이션 만들기
순수 자바와 서블릿만을 이용해서 웹 애플리케이션을 만들어본다. 회원 정보를 저장하고, 리스트를 조회할 수 있게 된다. 총 3개의 클래스를 만들어서 데이터가 연동되게 할 것이다.
각 클래스에서 response 형태로 HTML 데이터를 보내서 브라우저 상에서 페이지가 표시되도록 한다.
그런데, 서블릿을 이용하는 경우에는 response.getWriter()를 이용해서 writer 안에 html 코드를 일일이 넣어주어야하는 불편함이 있는 것을 아래 코드들에서 확인할 수 있다. 그래서 나온 것이 JSP, Thymeleaf, Freemarker, Velocity와 같은 템플릿 엔진 이다. 템플릿 엔진을 이용하면 HTML 코드 사이에 동적으로 변하는 자바 데이터들을 넣어주는 형태로 편하게 코딩할 수 있게 된다.
우선은 회원관리 웹 애플리케이션을 서블릿으로 만들어보자.
1. MemberFormServlet(URL - /servlet/members/new-form)
회원가입을 할 수 있는 form 데이터 입력창이 있는 url 이다. <form> 태그의 action 부분에서 /servlet/members/save URL이 지정되어, 전송버튼을 누르면 해당 url로 form 데이터를 쿼리파라미터 형태로 보낸다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
package hello.servlet.web.servlet;
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 = "MemberFormServlet", urlPatterns = "/servlet/members/new-form")
public class MemberFormServlet extends HttpServlet {
private MemberRepository instance = 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");
}
}
|
cs |
2. MemberSaveServlet(URL - servlet/members/save)
MemberFormServlet에서 넘어온 데이터들을 HttpServletRequest 객체에서 꺼낸다. 쿼리파라미터로 꺼내는 정보들은 모두 String으로 꺼내지기 때문에, age 정보의 경우 Integer.parseInt()를 사용하여 int 형태로 바꿔주었다. 그리고 이 정보들을 바탕으로 내가 생성해뒀던 메모리 저장소인 memberRepository에 save를 해서 정보를 저장한다.
여기서도 HTML 방식으로 response를 보낸다. 아래 코드의 37~39번째 줄에서 member의 정보를 동적으로 표현하는 부분을 확인할 수 있다. 이렇게 해서 MemberFormServlet에서 온 정보를 저장하고, 저장된 결과를 HTML 형태로 바로 페이지에서 보여줄 수 있는 것이다.
(member.getId()를 바로 해도 되는 이유는, MemberRepository 코드에서 save 메서드 실행 시 id값이 1 올라가도록 작성해놨기 때문)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
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 = "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>");
}
}
|
cs |
3. MemberListServlet(URL - /servlet/members)
이제 memberRepository에 저장된 회원 목록을 보여주는 MemberListServlet을 만든다. 이 페이지는 단순 조회 기능만 있으므로 response만 설정하여 HTML로 회원 정보들을 보여주면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
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("<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>");
}
}
|
cs |
3. JSP로 회원관리 웹 애플리케이션 만들기
이제 템플릿 엔진으로 html을 편하게 작성해보자. JSP로 만들어본다. JSP로 작성하면 .jsp 파일 안에 html 코드를 자바 형식으로 써줄 필요가 없고, 원래 html을 작성하던 방식대로 작성하면 된다. 그리고 <% ~ %> 문법을 이용해서 자바 코드도 한 번에 써줄 수 있다.
하지만, 비즈니스 로직인 자바 코드와 화면 표시를 하는 html 부분이 섞여있어서 유지보수가 필요할 때 코드를 분리해서 수정하기도 어렵고, 비즈니스 로직 부분에서 에러가 나더라도 html 표시 부분에서 에러가 나진 않았는지 다 점검해야하는 등, 문제가 발생할 수 있다. 그래서 나중에는 Thymeleaf 라는 템플릿 엔진을 사용할 것이다.
일단 jsp로 코드를 작성해보자. 자바가 아니기 때문에 java 디렉토리가 아니라 webapp 디렉토리 아래에 작성해야 한다.
1. new-form.jsp
회원가입을 할 수 있는 페이지이다. 기본적으로 java 코드를 이용하기 위해서 첫 번째 줄의 <%@ ~ %> 구문을 삽입해야한다. 그리고 form 제출 시 action으로 이동하는 경로는 .jsp 까지 모두 작성해주어야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<%@ 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>
|
cs |
2. save.jsp
<% ~ %> 구문 사이에 자바 코드를 삽입한다. 주석문에 있듯이, JSP도 결국에는 servlet으로 바꿔서 처리해주기 때문에, request, response는 그냥 사용하면 된다.
20번째 줄부터 데이터를 표기하는 부분에서는 <%= ~ %> 문법을 사용했다. 이 구문을 사용하면 자바 객체의 값을 그대로 표현할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
//JSP도 servlet으로 바꿔서 처리해주기 때문에, request, response는 그냥 써도 됨
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>
<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>
|
cs |
3. members.jsp
for문을 이용하여 리스트로 표현한다. out 문법을 통해서 html 상에서 표현하고자하는 내용을 출력할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
<%@ 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" %>
<%
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>
|
cs |
참조
1. 인프런_스프링 MVC 1편 - 백엔드 웹개발 핵심 기술_김영한 님 강의
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1
'Programming-[Backend] > Spring' 카테고리의 다른 글
[스프링 웹MVC] 7. 프론트 컨트롤러(Front Controller) - 1. 컨트롤러 수정 (0) | 2021.07.31 |
---|---|
[스프링 웹MVC] 6. MVC 패턴 적용 (0) | 2021.07.25 |
[스프링 웹MVC] 4. HTTP 응답 정보 : 서블릿 - HttpServletResponse (0) | 2021.07.19 |
[스프링 웹MVC] 3. HTTP 요청 정보 : 서블릿 - HttpServletRequest (0) | 2021.07.18 |
[스프링 웹MVC] 2. 프로젝트 생성과 웹서블릿 기본 개념 (0) | 2021.07.18 |