Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 자바의 정석 기초편 ch6
- 자바의 정석 기초편 ch3
- 스프링 입문(무료)
- 자바의 정석 기초편 ch13
- 스프링 mvc1 - 스프링 mvc
- 스프링 mvc2 - 타임리프
- 스프링 mvc1 - 서블릿
- 자바의 정석 기초편 ch1
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch2
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch12
- @Aspect
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch5
- 스프링 db1 - 스프링과 문제 해결
- 스프링 고급 - 스프링 aop
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch14
- 게시글 목록 api
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch8
- 스프링 db2 - 데이터 접근 기술
- jpa - 객체지향 쿼리 언어
- 타임리프 - 기본기능
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch7
- jpa 활용2 - api 개발 고급
- 코드로 시작하는 자바 첫걸음
Archives
- Today
- Total
나구리의 개발공부기록
서블릿,JSP,MVC패턴, 회원관리 웹 애플리케이션 요구사항, 서블릿으로 회원관리 웹 애플리케이션 만들기, JSP로 회원관리 웹 애플리케이션 만들기 본문
인프런 - 스프링 완전정복 코스 로드맵/스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
서블릿,JSP,MVC패턴, 회원관리 웹 애플리케이션 요구사항, 서블릿으로 회원관리 웹 애플리케이션 만들기, JSP로 회원관리 웹 애플리케이션 만들기
소소한나구리 2024. 2. 23. 18:41 출처 : 인프런 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 회원 관리 웹 애플리케이션 요구사항
1) 애플리케이션 요구사항
(1) 회원 정보
- 이름 : username
- 나이 : age
(2) 기능 요구사항
- 회원 저장
- 회원 목록 조회
2) 비즈니스 로직 구현
(1) Member
- domain.member 패키지 생성후 작성
- 회원 도메인 모델
- id는 Member를 회원 저장소에 저장하면 회원 저장소가 자동으로 할당
package hello.servlet.domain.member;
@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;
}
}
(2) MemberRepository
- 회원 저장소
- 스프링을 사용하면 스프링 빈으로 등록하면 되지만 지금은 최대한 스프링 없이 순수 서블릿만으로 구현하는 것이 목적이므로 싱글톤 패턴을 구현
- 싱글톤패턴은 객체를 단 하나만 생성해서 공유해야 하므로 생성자를 private접근제어자로 막아야함
package hello.servlet.domain.member;
/**
* 동시성 문제가 고려되어 있지 않으므로, 실무에서는 ConcurrentHashMap 이나 AtomicLong 사용을 고려
*/
public class MemberRepository {
// static으로 생성된 변수는 별도 공간에서 관리하기 때문에 new MemberRepository가 많이 생성 되어도 하나로 관리 됨
// 싱글톤으로 설계했기 때문에 static을 제외해도 됨(별도 공간에서 관리)
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
// 싱글톤 만들기 - 생성자를 private로 막고 getInstance 메서드를 만들어서 호출해서 사용하도록 설계
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()); // store를 보호하기 위함
}
// 테스트에서 사용하기 위한 store 리셋
public void clearStore() {
store.clear();
}
}
(3) MemberRepositoryTest
- test 하위에 domain.member 패키지 생성후 작성
- IntelliJ의 Test 생성 기능을 활용하면 동일 패키지 구조로 테스트 클래스가 생성됨
- 회원을 저장하고 목록을 조회하는 테스트를 작성
- 각 테스트가 끝날 때 다음 테스트에 영향을 주지 않도록 각 테스트의 저장서를 clearStore()를 호출하여 초기화
package hello.servlet.domain.member;
// junit5 부터 test 클래스에 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);
}
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); // result안에 member1,member2가 들어있는지 확인
}
}
2. 서블릿으로 회원 관리 웹 애플리케이션 만들기
1) 서블릿으로 웹 구현
(1) MemberFormServlet
- web.servlet 패키지를 생성 후 작성
- 회원 정보를 입력할 수 있는 HTML Form 작성
- 회원 정보를 form에 입력하면 /servlet/members/save의 위치로 post방식으로 응답
- 자바코드로 HTML을 제공해야 하므로 작업이 쉽지 않음
package hello.servlet.domain.web.servlet;
@WebServlet(name = "memberFormServlet", urlPatterns = "/servlet/members/new-form")
public class MemberFormServlet extends HttpServlet {
// 싱글톤이므로 new가 아닌 메서드 호출로 객체를 이용
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
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) MemberSaveServlet
- 회원 저장
- 파라미터를 조회하여 Member객체를 만들고 Member객체를 MemberRepository를 통해서 저장한 뒤 Member객체를 사용하여 결과 화면용 HTML을 동적으로 만들어서 응답
package hello.servlet.domain.web.servlet;
@WebServlet(name = "memberSaveServlet", urlPatterns = "/servlet/members/save")
public class MemberSaveServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("MemberSaveServlet.service");
String username = req.getParameter("username");
int age = Integer.parseInt(req.getParameter("age"));
Member member = new Member(username, age);
System.out.println("member = " + member);
memberRepository.save(member);
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
PrintWriter w = resp.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) 회원 목록
- memberRepository.findAll()을 통해 모든 회원을 조회 후
- 회원 목록 HTML을 for문을 이용하여 회원 수 만큼 동적으로 생성하고 응답
package hello.servlet.domain.web.servlet;
@WebServlet(name = "memberListServlet", urlPatterns = "/servlet/members")
public class MemberListServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<Member> members = memberRepository.findAll();
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
PrintWriter w = resp.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>");
}
}
(4) 문제점과 해결
- 서블릿덕분에 동적으로 원하는 HTML을 자유롭게 만들 수 있음
- 그러나.. 자바코드로 HTML을 만들어 내는 것이 매우 비효율 적임(HTML양이 천줄 이천줄이라면..?)
- HTML 문서에 동적으로 변경해야 하는 부분만 자바 코드를 넣을 수 있다면 더 편리할 것 -> 템플릿 엔진이 나온 이유
- JSP, Thymeleaf, Freemarker, Velocity 등의 템플릿 엔진을 사용하면 HTML문서에 필요한 곳만 코드를 적용해서 동적으로 HTML을 변경할 수 있음
** 참고
- JSP는 성능과 기능면에서 다른 템플릿 엔진과의 경쟁에서 밀리면서 점점 사장되어가는 추세임(스프링부트에서는 권장하지 않음)
3. JSP으로 회원 관리 웹 애플리케이션 만들기
1) 설정
(1) build.gradle 수정
- JSP를 사용하기 위한 라이브러리 추가(스프링부트 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 이상
2) JSP 사용
(1) new-form.jsp - 회원 등록 폼
- main/webapp/jsp/members/경로에 작성
- 첫 줄에 <%@ page contentType=".... language="java" %> 코드는 JSP 문서라는 뜻이며 JSP 문서는 이렇게 시작해야 함
- servlet을 이용하여 java코드로 html을 작성했을 때의 코드와 똑같음
- localhost:8080/jsp/members/new-form.jsp로 접속하면 작성된 jsp파일이 동작하는 모습을 확인할 수 있는데, 실제 jsp파일이 위치한경로가 그대로 url로 매핑되어서 사용됨(.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) save.jsp - 회원 저장
- 자바 코드 사용을 위해 import문을 작성해야 함
- JSP는 자바 코드를 그대로 사용할 수 있음
- <% ~~ %> 자바 코드 입력
- <%= ~~ %> 자바 코드를 출력
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%
MemberRepository memberRepository = MemberRepository.getInstance();
System.out.println("save.jsp");
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);
%>
<html>
<head>
<meta charset="UTF-8">
</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) members.jsp - 회원 목록
- main/webapp/jsp 경로에 작성, url경로가 localhost:8080/jsp/members로 매핑되게 하기 위함
- 회원 리포지토리를 먼저 조회하고 결과 List를 사용하여 중간에 <tr><td>HTML 태그를 반복해서 출력
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.List" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%
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>
<tr>
<th>id</th>
<th>username</th>
<th>age</th>
</tr>
</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>
(4) 서블릿과 JSP의 한계
- 서블릿만으로 개발할 때는 뷰화면을 위한 HTML을 만드는 작업이 자바 코드에 섞여서 매우 지저분하고 복잡하고 번거로웠음
- JSP를 사용하여 기존에 자바코드로 html을 작성하는 것보다 훨씬 수월하게 코드를 작성할 수 있었음
- 그러나, JSP코드들을 보면 절반은 비즈니스 로직이고 나머지는 HTML로 보여주기 위한 뷰의 영역으로 구성 됨 즉, 역할이 섞여있음
- JAVA코드, 데이터 조회하는 리포지토리 등등 다양한 코드가 JSP에 그대로 노출되어있음
- JSP가 너무 많은 역할을 함
- 지금처럼 작은 프로젝트도 유지보수하기가 쉬워보이는 구조가 아닌데 수천, 수백줄이 넘어가는 실무에 적용된 JSP파일은 유지보수가 매우 어려워짐
- 간단한 뷰에 관련된 작업을 수정할 때 비즈니스 로직까지 잘못 건드릴 수 있고, 파일이 점점 커짐
(5) MVC 패턴의 등장
- 비즈니스 로직은 서블릿 처럼 다른곳에서 처리하고 JSP는 목적에 맞게 HTML로 화면(view)를 그리는 일에 집중하도록 설계하기 위해 MVC 패턴을 적용