Spring 강의/springMVC

springMVC 활용 - (4) 회원관리 웹 애플리케이션

lxexjx 2022. 3. 26. 18:32

[정리] 클라이언트에서 서버로 원하는 정보를 보낼 때 사용하는 방법  딱 세가지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)로직에 제어권을 넘기면 모델을 참고하면서 그려
클라이언트가 컨트롤러를 호출하면 서비스나 리포지토리 호출해서 로직 실행하고 결과를 받아서 모델에 담아. 그리고 뷰 로직에 넘겨

 


 

서블릿을 컨트롤러로, 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.필터는 정해진 스펙대로 체인을 넘기는 것.