[정리] 클라이언트에서 서버로 원하는 정보를 보낼 때 사용하는 방법 딱 세가지1.get2.post3. http message body
1,2, 번은 서버에서 읽을 떄 request.getparam으로 동일하게 읽을 수 있어
http요청 메세지의 스펙을 편리하게 조회할 수 있도록 하는게 httpServletRequest
<도메인>
@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 {
//static->memeberRepository가 new로 많이 생성해줘도 하나만 사용
private static Map<Long,Member> store=new HashMap<>();
private static long sequence=0L;
private static final MemberRepository instance =new MemberRepository(); //싱글톤으로 사용, new사용x
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();
}
}
<MemberRepositoryTest>
class MemberRepositoryTest {
MemberRepository memberRepository=MemberRepository.getInstance(); //싱글톤으로 사용, new는 사용x
@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 fingAll(){
//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);
}
}
[서블릿으로 회원관리 만들기]
<MemberFormServlet> - 회원 등록 폼을 서블릿으로 만들기
http응답으로html이 나가야됨->content-body를 설정(=hello.html)
@WebServlet(name="memberFormServlet",urlPatterns = "/servlet/members/new-form")
public class MemberFormServlet extends HttpServlet {
private MemberRepository memberRepository=MemberRepository.getInstance(); //싱글톤으로,private로 막아둠
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter w=resp.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");
}
}
<MemberSaveServlet> - 회원 저장 서블릿
(위에 폼에서 온거를 getParameter로 꺼내고 member만들어서 save하고 응답)
@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");
//폼에서 온거를 getparameter로 꺼내오고
String username=req.getParameter("username");
int age=Integer.parseInt(req.getParameter("age"));
//저장하고
Member member=new Member(username,age);
memberRepository.save(member);
//결과를 html로 할거야
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>");
}
}
<MemberListServlet> - 저장된 회원 목록 조회
@WebServlet(name = "memberListServlet", urlPatterns = "/servlet/members")
public class MemberListServlet extends HttpServlet {
private MemberRepository memberRepository=MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest requset, 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(" <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(" <tbody>");
}
}
서블릿에서 html만드는게 귀찮아->html에서 자바코드를 작성하는 템플릿엔진:jsp사용
JSP를 사용하려면 먼저 다음 라이브러리를 추가
//JSP 추가 시작 implementation 'org.apache.tomcat.embed:tomcat-embed-jasper' implementation 'javax.servlet:jstl' //JSP 추가 끝
[JSP뢰 회원 관리 웹 애플리케이션 만들기]
<nw-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>
<save.jsp> - 회원 저장
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// request, response 사용 가능
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>
<members.jsp> - 리스트들
<%@ page import="java.util.List" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
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>
<% %>는 자바코드 입력
<%= %>는 자바코트 출력
서블릿(=비지니스 로직) : 뷰html이 자바코드 안에. 자바 코드에 최적화
jsp(= 뷰) : html은 깔끔하나 코드의 절반은 비지니스 로직이고 절반은 뷰 영역. 화면 렌더링에 최적화
너무 많은 역할=> mvc로
[mvc] - 서블릿이나 jsp를 뷰, 컨트롤러로 나눔. 모델을 통해서 데이터를 담아서 전달
컨트롤러 : http요청을 받아서 파라미터를 검증하고 비지니스 로직 실행(member.save호출)하고 뷰에 전달할 결과 데이터를 조회해서 모델에 담아, 중간에서 조정하는 것(파라미터 검증하고 필요하면 비지니스로직도 호출하고 뒤에 뷰에 넘겨주고~)
모델 : 뷰에 전달할 데이터를 담아둬. 화면 렌더링에 집중.
뷰 : 화면을 그리는 일. 주로 html 생성
서블릿을 컨트롤러로, jsp를 뷰로 사용!
[mvc패턴 적용]
request는 내부에 저장소를 가짐. 그래서 request.setAttribute()로 저장하고 request.getAttribute()로 가져와.
httpServletRequest를 모델처럼 사용
<mvcMemberFormServlet>컨트롤러 - 컨트롤러를 통해야만 뷰에 들어갈 수 있어. 컨트롤러 요청이 들어와서 jsp로 이동
@WebServlet(name = "mvcMemberFormServlet", urlPatterns = "/servelt-mvc/members/new-form")
public class MvcMemberFormServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String viewPath="/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher=req.getRequestDispatcher(viewPath); //controller에서 view로 이동
dispatcher.forward(req,resp); //=>서버끼리 내부에서 호출,클라이언트를 왔다갔다 하는게 아냐(redirect가 아님)
}
//WEB-INF:컨트롤러를 거쳐서 불러지길 바람. 외부에서 불러지는게 아닐 때 이 경로에 넣어줘
//redirect:다시 서버에 요청
}
jsp를 찾아서 넘어가게됨.
dispatcher.forward() : 다른 서블릿이나 JSP로 이동할 수 있는 기능. 서블릿이나 jsp로 이동
서버 내부에서 다시 호출. 클라이언트에 왔다갔다 하는게 아니라 (리다이렉트가 아님) 서버 내부에서 호출
/WEB-INF > 이 경로안에 JSP가 있으면 외부에서 직접 JSP를 호출할 수 없다. 우리가 기대하는 것은 항상 컨트롤러를 통해서 JSP를 호출
-><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] new-form이 지워지고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>
<mvcMemberSaveServlet>
@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet/members/save")
public class MvcMemberSaveServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.파람 받고
String username = req.getParameter("username");
int age = Integer.parseInt(req.getParameter("age"));
//2. 비지니스 로직 호출하고
Member member = new Member(username, age);
System.out.println("member = " + member);
memberRepository.save(member);
//3. Model에 데이터를 보관한다.
req.setAttribute("member", member);
//4. 뷰로 던지고
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
dispatcher.forward(req, resp);
}
req.setAttribute("member", member); request의 내부 저장소에 member를 저장.
<save.jsp>
<%@ 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>
//<li>id=<%=member.getId()%></li>
//<li>username=<%=member.getUsername()%></li>
//<li>age=<%=member.getAge()%></li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
<%= request.getAttribute("member").getAge()%> 로 모델에 저장한 member 객체를 꺼내는데 ${} 이거랑 동일한 표현식임.
<mvcMemberListServlet>
@WebServlet(name = "mvcMemberListServlet" ,urlPatterns = "/servlet-mvc/members/")
public class MvcMemberListServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("MvcMemberListServlet.service");
List<Member> members = memberRepository.findAll();
req.setAttribute("members", members);
String viewPath = "/WEB-INF/views/members.jsp";
RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
dispatcher.forward(req, resp);
}
}
<members.jsp>
<%@ 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}"> //jstl
<tr>
<td>${item.id}</td>
<td>${item.username}</td>
<td>${item.age}</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
(jstl사용함)
모델 뷰 컨트롤러가 확실하게 분리됨
여기까지는 jsp와 서블릿으로 mvc를 만들었음. 컨트롤러에 중복이 많아
정리
클라에서 요청이 오면 컨트롤러를 항상 거쳐서 서비스나 리포지토리에서 회원가입 로직을 하고 결과를 모델에 담아서 (모델은 heep객체를 사용해서 setAttribute로 데이터 담고 )뷰는 request.getAttribute로 값을 꺼내
[mvc패턴의 한계]
1. 포워드 중복 : View로 이동하는 코드가 항상 중복 호출됨.
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath); dispatcher.forward(request, response); 2. ViewPath에 중복 : String viewPath = "/WEB-INF/views/new-form.jsp"; prefix: /WEB-INF/views/ suffix: .jsp 그리고 만약 jsp가 아닌 thymeleaf 같은 다른 뷰로 변경한다면 전체 코드를 다 변경.
3. 사용하지 않는 코드 : 다음 코드를 사용할 때도 있고, 사용하지 않을 때도 있다. 특히 response는 현재 코드에서 사용되지 않는다. HttpServletRequest request, HttpServletResponse response 그리고 이런 HttpServletRequest , HttpServletResponse 를 사용하는 코드는 테스트 케이스를 작성하기도 어렵다.
4. 공통 처리가 어렵.
==>>컨트롤러가 호출되기전에(서블릿이 호출되기 전에) 공통 기능 먼저 처리해야됨. 이게 프로트 컨트롤러 패턴!
(어떤 hhtp요청도 프론트컨트롤러를 통해서 뒤의 컨트롤러가 호출이 되도록)
cf.필터는 정해진 스펙대로 체인을 넘기는 것.
'Spring 강의 > springMVC' 카테고리의 다른 글
springMVC 활용 - (6) MVC프레임워크 구조 (0) | 2022.05.11 |
---|---|
springMVC 활용 - (5) mvc 프레임워크 만들기 (0) | 2022.04.13 |
springMVC 활용 - (2) 서블릿 & Request (0) | 2022.03.26 |
springMVC 기본 - (9) 빈스코프 (0) | 2022.03.13 |
springMVC 기본 - (8) 빈 생명주기 콜백 (0) | 2022.03.12 |