BackEnd/WEB

MVC_국비Day79

Leo.K 2022. 6. 23. 14:54

본격적으로 스프링에 대해 학습하기 전에 지금껏 해왔던 Model2 패턴을 정리하고, 스프링에서 사용할 MVC패턴과 비교분석하면서 MVC패턴의 구조를 학습해보자.

모델2 패턴은 사용자 요청사항마다 각각의 서블릿(서버에서 실행되는 서비스 객체)이 존재한다. 사용자의 요청사항에 100가지라고 한다면 100개의 서블릿이 존재해야 하는 비 효율적인 구조가 있을 수 있기 때문에 이를 보완하기 위해서 등장한 것이 MVC패턴이다.

MVC패턴은 사용자의 요청 수와는 무관하게 하나의 컨트롤러(Front Controller)만 만들고, 이 컨트롤러가 요청사항을 제어한다. 여기서 말하는 컨트롤러가 Model2패턴에서의 서블릿이라고 생각하면 비교가 수월할 것이다.

[ FrontController ]

  1. 사용자 요청 접수 
  2. 사용자 요청 분류 
    1. 과거에는 커맨드 패턴을 많이 사용했다고 하지만, 최근에는 우리가 Model2에서 연습해왔던, URL패턴으로 요청을 분류한다고 한다.
    2. filter를 만들어서 사용해보자. filter가 가장 먼저 호출된다. filter의 파라미터로 사용되는 ServletRequest가 서블릿의 파라미터로 사용되는 HTTPServletRequest보다 상위 인터페이스이기 때문에 먼저 호출된다. (frontcontroller를 호출해도 필터를 한 번 거치고 오게된다. 인터페이스의 상속 관계를 파악하자. 필터가 프론트 컨트롤러의 부모이다.)
  3. 분류된 요청에 따른 처리 객체(POJO: 순수자바 객체. Object를 제외한 누구로부터도 상속을 받지 않는 객체)를 호출
  4. 처리결과 수신 
  5. 뷰에게 출력 지시

 

[ 사용자 요청 분류 ]

  • 뒤에서부터 검색을 해서 '/'가 나오는 인덱스를 구한다. 
  • 위에서 구한 인덱스 바로 다음부터 문자열을 슬라이스 한다. 
  • ".do"의 확장자를 ""으로 바꾼다. 
  • 슬라이싱 된 결과(list, view, update, ...등 )로 요청을 분류하여 작업을 하는 객체를 호출한다.
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
package controller;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
/**
 * Servlet implementation class FrontController
 */
@WebServlet("*.do")
public class FrontController extends HttpServlet {
    private static final long serialVersionUID = 1L;
 
    /**
     * @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response)
     */
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
 
        //System.out.println("--2--");
        
        //    어노테이션에 '/'하나만 쓰면 가장 먼저, 호출된다. -> 모든 요청이 이 컨트롤러로 들어온다.
        //    하지만 이는 명확하지 않은 결과를 초래하기 때문에, "*.do"를 사용하여 do로 끝나는 요청사항은 반드시 이 컨트롤러를 지나도록 한다.
        
        //URL :  전체 주소 
        //URI :  전체 주소 - 서버 주소(ip + port) 
        String uri = request.getRequestURI();
        System.out.println(uri);
        //       01234567890123456789012345678901234567
        //uri = "/2022_0623_MVCPatternTest/book/list.do";
        //uri = "/2022_0623_MVCPatternTest/book/view.do";
        
        //뒤에서 부터 검색해서 /가 몇 번째 인덱스에 있는지 확인한다.
        int index = uri.lastIndexOf('/');
        
        System.out.println(index);
        String cmd = uri.substring(index+1).replaceAll(".do""");
        System.out.println(cmd);
    }
 
}
 
cs

 

cmd의 값에 따라서 사용자의 요청에 따른 작업을 분류해보자. 현재는 ui를 만들지 않고 실행과정만 살펴보는 중이기 때문에 frontcontroller를 실행하면 "/2022_0623_MVCPatternTest/*.do"이와 같은 uri가 나오기 때문에, 필자가 직접 /2022_0623_MVCPatternTest/list.do로 url을 수정해주어야 한다. cmd가 list인 경우 이를 처리하는 작업 객체(ListAction)를 호출해서 작업을 명령한다. 자세한 내용은 코드의 주석으로 달아두었다. 작업 객체로부터 전달받은 뷰 정보로 작업의 결과물인 리스트를 포워딩한다. ListAction에서 바인딩을 했기 때문에 여기서는 포워딩만 한다

1
2
3
4
5
6
7
8
9
10
11
12
13
if(cmd.equals("list")) {
    
    //Controller -> ListAction에게 일을 시킨다.
    ListAction action = new ListAction();
    //ListAction의 객체가 처리 결과값으로 View의 정보를 반환
    String forward_page = action.execute(request, response);
    
    RequestDispatcher disp = request.getRequestDispatcher(forward_page);
    disp.forward(request, response);
    
}else if(cmd.equals("view")) {
    
}
cs

 

[ ListAction ]

작업 객체는 리스트에 책 제목을 입력해서 전달받은 request객체에 바인딩시킨다. 아래 주석에도 정리했지만, 작업 객체를 호출한 frontcontroller와 같은 request 객체이기 때문에 별다른 리턴없이 frontcontroller에서도 사용할 수 있다. 여기서는 반환값으로 뷰에 대한 정보를 반환한다.

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
package action;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
//POJO(Plain Old Java Object)
public class ListAction {
    public String execute(HttpServletRequest request, HttpServletResponse response ) {
        
        List<String> list = new ArrayList<String>();
        list.add("Java");
        list.add("Oracle");
        list.add("HTML5");
        list.add("CSS3");
        list.add("JavaScript");
        list.add("jQuery");
        
        //이 리퀘스트가 frontcontroller가 사용하는 request와 동일한 객체이므로 여기서 바인딩을 하면 
        //굳이 리턴값을 주지 않아도 메서드가 종료하여 돌아갔을 때, frontcontroller는 바인딩된 list를 사용할 수 있다.
        request.setAttribute("list", list);
        
        return "list.jsp"//반환 정보 => view의 정보
    }
}
 
cs

 

[ list.jsp ]

ListAction의 작업 결과를 출력하는 뷰의 페이지를 보자. 앵커 태그를 함께 작성함으로써, 클릭을 하게 되면 "http://localhost:9090/2022_0623_MVCPatternTest/view.do?book=Java"를 호출하게 되는데 위에서 설명했듯이, frontcontroller의 어노테이션을 *.do로 설정해두었기 때문에, .do로 끝나는 모든 url패턴은 반드시 frontcontroller를 거치게 된다.

여기서 약간 헷갈릴 수 있는데, uri에서는 파라미터 값은 읽히지 않는다. 결론적으로 frontcontroller에 전달되는 uri는 "/2022_0623_MVCPatternTest/view.do"인 것이다.

위에서 list를 분류한 것과 마찬가지로 사용자의 요청을 분류하게 되면, 이번에는 cmd=view라는 값이 나오게 되므로 이에 대한 작업을 해줄 ViewAction객체를 만들어서 처리한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<hr>
    Book List
<hr>
<ul>
    <c:forEach var="book" items="${list }">
        <li><a href="view.do?book=${book }">${book }</a></li>
    </c:forEach>
</ul>
</body>
</html>
cs

 

[ ViewAction ]

데이터를 처리하는 과정만 다를 뿐이지, 바인딩하고 포워딩하는 내용은 ListAction과 같기 때문에 설명은 생략하겠다.

작업 객체 ViewAction이 반환한 뷰정보를 frontcontroller에서 수신하고, 이 페이지로 작업 객체에서 바인딩한 정보를 포워딩만 해준다. 즉, frontcontroller는 작업 객체에게 명령을 지시하고, 작업 객체가 반환한 처리 결과를 View에게 전달하여 출력을 지시하는 중간 관리자 한 명이다.

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
package action;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
//POJO(Plain Old Java Object)
public class ViewAction {
    public String execute(HttpServletRequest request, HttpServletResponse response ) {
        
        // /2022_0623_MVCPatternTest/view.do?book=Java
        // /2022_0623_MVCPatternTest/view.do?book=Oracle
        // /2022_0623_MVCPatternTest/view.do?book=Html
        String book    = request.getParameter("book");
        String content = "";
        
        if(book.equalsIgnoreCase("Java")) {//대소문자 가리지 않고 체크. 단어 스펠링만 맞으면 된다.
            content ="Java는 제임스 고슬링이 만든 프로그래밍 언어";
        }else if(book.equalsIgnoreCase("Oracle")) {
            content="세계 제1의 DBMS";
        }else if(book.equalsIgnoreCase("Html5")) {
            content="Hyper Text Markup Language";
        }else if(book.equalsIgnoreCase("CSS3")) {
            content="Cascade StyleSheet";
        }else if(book.equalsIgnoreCase("JavaScript")) {
            content="Browser 제어용 언어";
        }else if(book.equalsIgnoreCase("jQuery")) {
            content="Javascript을 편리하게 사용할 수 있도록 만들어 놓은 라이브러리 입니다.";
        }
        
        //request binding
        request.setAttribute("book", book);
        request.setAttribute("content", content);
        
        return "view.jsp"//반환 정보 => view의 정보
    }
}
 
cs

 

[ view.jsp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<hr>
    제목 : ${book }
<hr>
    ${content }<br>
<a href="list.do">다시하기</a>
</body>
</html>
cs

 

[ 정리 ]

  1. 메인 컨트롤러의 어노테이션은 "*.do"로 해야 모든 요청이 메인 컨트롤러를 반드시 거치게 된다.
  2. 사용자의 요청이 들어오면 메인 컨트롤러가 요청 정보를 담은 request객체에서 URI(URL아니다.)를 얻어낸다.
  3. URI에 담긴 URL패턴(~.do)을 문자열 슬라이싱으로 얻어낸다.
  4. 슬라이싱으로 얻어낸 cmd값을 처리해줄 작업 객체(~Action)를 호출한다.
  5. 작업 객체가 처리한 결과를 mainController에게 반환한다. 
  6. 메인 컨트롤러는 반환 받은 결과를 뷰단에 포워딩 해주어서 결과를 출력하도록 지시한다.

 

[ 참고 ]

위의 코드들을 보면 알겠지만, 필자는 어노테이션을 사용한 실습을 진행했다. 이를 어노테이션 대신에 web.xml로 작성하는 방법은 아래와 같다. 이후에 스프링을 공부할때는 부트가 아닌 레거시로 진행하기 때문에 직접 설정을 해주다 보면 xml파일 수정이 잦아질텐데 그 때를 대비해서 미리 익숙해져보록 하자

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name></display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  
  <!-- 
      @WebServlet("*.do")
      어노테이션 한 줄을 사용하지 않는다면, 같은 기능을 구현하기 위해서는 아래와 같은 내용을 
      web.xml을 따로 설정해주어야 한다.
   -->
  
  <!-- FrontController Mapping 
         Spring에서 이러한 역할을 하는 객체가 Dispatcher Servlet이다. 얘가 메인 컨트롤러(FrontController이다.)
  -->
  <servlet>
      <!-- 메인 컨트롤러의 이름설정 -->
      <servlet-name>FrontController</servlet-name>
      <!-- 메인 컨트롤러의 경로설정 -->
      <servlet-class>controller.FrontController</servlet-class>
  </servlet>
    
  <servlet-mapping>
      <servlet-name>FrontController</servlet-name>
      <url-pattern>*.do</url-pattern>
  </servlet-mapping>
  
  
  <!-- EncodingFilter Mapping -->
  <filter>
      <filter-name>EncodingFilter</filter-name>
      <filter-class>filter.EncodingFilter</filter-class>
      <!-- 내가 설정한 인코딩 값을 파라미터로 초기에 초기화 시킬 수 있다. -->
      <init-param>
          <param-name>encoding</param-name>
          <param-value>utf-8</param-value>
      </init-param>
  </filter>
  
  <filter-mapping>
      <filter-name>EncodingFilter</filter-name>
      <url-pattern>*.do</url-pattern>
  </filter-mapping>
  
</web-app>
cs

37~50행에 정의한 filter 태그를 보자. 이를 사용하기 위해서는 filter클래스 내부에 정의된 init메소드에서 정의해야 한다. 일단 필터또한 서블릿의 생명주기와 동일하기 때문에 프로그램이 처음 실행되면 필터가 이미 만들어져 있는지 확인하고, 없다면 새로 생성(new) -> 초기화(init)을 하게 되는데, 이때, web.xml파일에 정의된 init-param태그를 읽어서 init 메소드에서 처리한다. 

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
package filter;
 
/**
 * Servlet Filter implementation class EncodingFilter
 */
//@WebFilter("*.do")
public class EncodingFilter implements Filter {
    
    String encoding = "utf-8";
    /**
     * Default constructor. 
     */
    public EncodingFilter() {
        // TODO Auto-generated constructor stub
    }
 
    /**
     * @see Filter#destroy()
     */
    public void destroy() {
        // TODO Auto-generated method stub
    }
    /**
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // TODO Auto-generated method stub
        // place your code here
        // pass the request along the filter chain
    }
    /**
     * @see Filter#init(FilterConfig)
     */
    public void init(FilterConfig fConfig) throws ServletException {
        // TODO Auto-generated method stub
        //web.xml파일의 <init-param> 태그 내부의 내용을 읽어들인다.
        
        //<param-name>에 작성한 값을 적으면, <param-value>가 셋팅 된다.
        encoding = fConfig.getInitParameter("encoding");
        System.out.printf("encoding=[%s]\n", encoding);
    }
 
}
 
cs

서블릿의 생명주기

'BackEnd > WEB' 카테고리의 다른 글

국비_Day90  (0) 2022.07.11
DispatcherServlet  (0) 2022.06.23
MyBatis_SubQuery_국비Day75  (0) 2022.06.17
mybatis_검색기능_국비Day74  (0) 2022.06.16
mybatis_국비Day73  (0) 2022.06.15