MVC (1)

부트캠프(END)/Web|2022. 7. 27. 12:50

MVC 구조? 참고

Model, View, Controller 로 이루어진 디자인 패턴이다.

Model : 데이터와 비즈니스 로직을 관리

(=앱이 포함해야할 데이터가 무엇인지를 정의)

View : 레이아웃과 화면을 처리

(=앱의 데이터를 보여주는 방식을 정의)

Controller : 명령을 모델과 뷰 부분으로 전송

(=앱의 사용자로부터의 입력에 대한 응답으로 모델 및/또는 뷰를 업데이트하는 로직을 포함)

 

왜 사용하는가?

java와 HTML을 분리

→ 확장성과 재사용성을 확보해서 유지보수를 용이하게 하기 위해 사용한다.
JSP는 절차적언어 → 재사용이 안된다. = model1
M(Java)V(JSP)C(Servlet)→ 객체 → 재사용성이 높음 = model2

 

http://www.setgetweb.com/p/rational/com.ibm.etools.struts.doc_6.0.0/topics/cstrdoc001.html
http://www.setgetweb.com/p/rational/com.ibm.etools.struts.doc_6.0.0/topics/cstrdoc001.html

 

 

간단하게 말해서,

유저의 요청을 Controller가 받고,

Controller가 Model에 넘겨주어 걔가 처리하고, View를 통해 결과를 보여준다.

(DAO도 있음! Model과 DAO가 통신함)

 

 

Controller(Servlet)

: 요청을 받고 결과값을 받아와서 JSP에 보내주는 역할

화면을 출력하는 역할을 하지 않음. 요청을 받고 보내주는 역할만 함.

→ 연결할 Model과 JSP를 어떻게 찾을지가 관건~

컨트롤러는 고정시켜놓고 시작해야 함! 

JSP마다 모델을 일일이 불러오고 import를 하고..

 ↓ 이걸 생략하기 위해서 사용하는게 컨트롤러

<% page import="package.*"> <% package... %>

 

Model / DAO(Java)

: Controller로부터 request를 받아 요청 처리(대부분 데이터 처리==DAO연결)

요청값을 request.getParemeter()로 받는다

결과값을 request.setAttribute()로 담아 보낸다

 

View(JSP)

: request로 결과값을 받아서 화면 출력 / 동적 Front 처리

 

 

 

 

한 번 해봅시다.

우선 request에 담기는 msg만을 표시해 주는 간단한 JSP 페이지들을 생성한다.

list.jsp는 "게시판 목록", insert.jsp는 "게시글 등록" 등의 간단한 메시지를 보여줄 것이다.

 

 

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="model.*"%>
<%
	ListModel model = new ListModel();
	model.execute(request);
%>
<!DOCTYPE html>
<%--컨트롤러가 찾으면 메시지를 보여주도록 할 것입니다 --%>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<center>
		<h1>${msg }</h1>
	</center>
</body>
</html>

위와 같은 형태로 insert.jsp, delete.jsp, update.jsp를 만들어 준다.

 

그리고 사용자의 요청(주소창에 어쩌구.jsp를 치는 것)에 따라,

request의 msg에 각 텍스트를 담아 주는 처리를 하는 Model들을 생성한다.

 

ListModel.java (Class)

package model;

import javax.servlet.http.HttpServletRequest;
//Model의 역할 : 요청 처리 -> 그 요청이 request에 들어 있다~
public class ListModel {
  public void execute(HttpServletRequest request) {
    request.setAttribute("msg", "게시판 목록");
  }
}

위와 같은 형태로 UpdateModel, InsertModel, DeleteModel을 만들어 준다.

 

그리고!

컨트롤러(서블릿)로 모델과 jsp를 연결해 준다.

컨트롤러는 사용자의 요청값을 받아 Model에 보내 주고, Model이 처리한 결과를 받는다.

그리고 그 결과를 RequestDispatcher를 통해 다시 jsp로 넘겨준다.

 

Controller.java (Servlet)

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
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 model.*;

@WebServlet("/Controller")
public class Controller extends HttpServlet {
  private static final long serialVersionUID = 1 L;

  protected void service(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
    //사용자의 요청값을 받아 온다. --> /Controller?cmd=사용자요청값 이렇게 받아올것이다.
    String cmd = request.getParameter("cmd");
    //요청 처리를 위해 Model을 찾아 온다.(Model에 request를 보낸다)
    String jsp = "";
    if (cmd.equals("list")) {
      ListModel model = new ListModel();
      model.execute(request); //이러면 request의 msg에 값을 채워줄 것임.
      jsp = "list.jsp";
    } else if (cmd.equals("update")) {
      UpdateModel model = new UpdateModel();
      model.execute(request);
      jsp = "update.jsp";
    } else if (cmd.equals("delete")) {
      DeleteModel model = new DeleteModel();
      model.execute(request);
      jsp = "delete.jsp";
    } else if (cmd.equals("insert")) {
      InsertModel model = new InsertModel();
      model.execute(request);
      jsp = "insert.jsp";
    }
    //요청 처리가 되면 결과값을 받아 온다.(request.setAttribute)
    //request를 jsp 페이지에 전송한다.
    RequestDispatcher rd = request.getRequestDispatcher("view/" + jsp); //전송 담당!
    rd.forward(request, response);
  }

}

 

 

좀 더 개선해 보자.

위 코드에서 Model은 각기 다른 내용이지만,

공통적으로 msg 내용을 request에 담아 준다는 기능을 가지고 있다.

 

 

Interface로 Class 묶어 주기

jsp는 그대로 두고, Model.interface를 생성하고 execute() 메서드를 선언한다.

 

Model.java (Interface)

package model;

import javax.servlet.http.HttpServletRequest;

//Interface : 기능이 유사한 클래스를 모아서 한 번에 관리
public interface Model {
  public String execute(HttpServletRequest request);
}

 

그리고 Model을 implement하여 각 모델들을 생성한다.

 

 

ListModel.java (Class)

package model;

import javax.servlet.http.HttpServletRequest;

public class ListModel implements Model {
  @Override
  public String execute(HttpServletRequest request) {
    request.setAttribute("msg", "게시물 목록");
    return "view/list.jsp"; //request가 view/list.jsp로 넘어갈 거다.
  }
}

 

마지막으로, HashMap을 생성하여 각 jsp에 맞는 키와 값으로 Map에 넣어 준다.

이로써 컨트롤러에서는 key만으로 그에 맞는 모델을 찾아 메서드를 실행할 수 있다.

 

Controller.java (Servlet)

package controller;

import java.io.IOException;
import java.util.*;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
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 model.*;

@WebServlet("/Controller")
public class Controller extends HttpServlet {
  private static final long serialVersionUID = 1 L;
  private Map clsMap = new HashMap(); //ClassMap

  public void init(ServletConfig config) throws ServletException {
    //if문을 없앤 것이다!
    clsMap.put("list", new ListModel());
    clsMap.put("insert", new InsertModel());
    clsMap.put("update", new UpdateModel());
    clsMap.put("delete", new DeleteModel());
    //나중엔 이걸 xml로 등록해서 읽어올 것임(=Spring)
  }

  protected void service(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
    String cmd = request.getParameter("cmd");
    //Model을 찾자
    Model model = (Model) clsMap.get(cmd); //if문을 와바박 써 줄 필요가 없어졌다~
    //이제 JSP를 찾자
    String jsp = model.execute(request);
    //request를 전송하자
    RequestDispatcher rd = request.getRequestDispatcher(jsp);
    rd.forward(request, response);
  }

}

 

 

하지만 이 방법도 매번 Model을 만들 때마다 Controller를 수정해 주어야 한다는 단점이 있다.

매번 clsMap.put("추가된페이지", new 추가Model()); 을 해 주어야 하니까...

이럴 때는 xml을 활용하면 좀 더 간편해진다.

 

 

추가 : xml 활용

WEB-INF 에는 모든 설정 파일(xml등)을 저장한다.

xml에 Model을 등록해 주고 읽어 와 사용해 보자.

 

Interface와 Model, jsp는 위와 동일하게 작성한다.

app.xml이라는 파일에 id와 class 속성으로 각 Model파일을 등록하자.

 

app.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- 
	XML : 문서형 데이터베이스
	왜 쓰는가? -> 모든 언어와 호환된다.
	어디에 주로 쓰이는가? -> 설정 파일(클래스 등록, 서버 설정 등)
 -->
<beans> <!-- 최상위 태그 ~= 데이터베이스에서 테이블-->
	<bean id="list" class="model.ListModel"/> <!-- COLUMN(id, class) 2개, ROW 하나-->
	<bean id="insert" class="model.InsertModel"/>
	<bean id="update" class="model.UpdateModel"/>
	<bean id="delete" class="model.DeleteModel"/>
	<bean id="find" class="model.FindModel"/>
</beans>

 

그리고 서블릿에서 자동으로 app.xml을 읽어 와 바로 HashMap에 저장한다.

 

Controller.java (Servlet)

package controller;

import java.io.File;
import java.io.IOException;
import java.util.*;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
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 model.*;
import javax.xml.parsers.*; //XML 파싱(데이터 읽어오기)을 위한 패키지
import org.w3c.dom.*; //태그 제어 프로그램

@WebServlet("/Controller")
public class Controller extends HttpServlet {
  private static final long serialVersionUID = 1 L;
  private Map clsMap = new HashMap(); //key와 클래스를 저장할 것

  public void init(ServletConfig config) throws ServletException {
    //xml파일의 properties에서 Path를 가져 온다.
    String path = "C:\\Users\\user\\git\\doodoo\\0727MVCProject3\\src\\main\\webapp\\WEB-INF\\app.xml";
    try {
      //Parser 생성 : XML, WML, HTML 종류별로 Parser 생성가능
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      //Parser
      DocumentBuilder db = dbf.newDocumentBuilder();
      //Parsing된 데이터 저장
      Document doc = db.parse(new File(path));
      //root 태그 읽기(<beans>를 읽어 그 아래 요소를 가져오겠다) - 항상 최상위 태그 이름을 가져와야 함
      Element root = doc.getDocumentElement();
      System.out.println(root.getTagName()); //beans

      //for문을 돌리기 위해 같은 태그를 묶는다
      NodeList list = root.getElementsByTagName("bean"); //bean태그를 묶어라~

      for (int i = 0; i < list.getLength(); i++) {
        Element beanTag = (Element) list.item(i);
        String id = beanTag.getAttribute("id");
        String cls = beanTag.getAttribute("class");
        System.out.println(id + "/" + cls); //list/ListModel 이렇게 출력
        //Map에 저장하자
        Class clsName = Class.forName(cls);
        Object obj = clsName.getDeclaredConstructor().newInstance(); //클래스 이름으로 메모리 할당
        clsMap.put(id, obj);
        System.out.println(id + ":" + obj);
      }

    } catch (Exception ex) {}
  }

  protected void service(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
    String cmd = request.getParameter("cmd");
    //Model을 찾자
    Model model = (Model) clsMap.get(cmd); //if문을 와바박 써 줄 필요가 없어졌다~
    //이제 JSP를 찾자
    String jsp = model.execute(request);
    //request를 전송하자
    RequestDispatcher rd = request.getRequestDispatcher(jsp);
    rd.forward(request, response);
  }

}

 

이렇게 하면 새로운 Model과 페이지를 작성한 후에 xml에만 등록해 주면

서블릿이 자동으로 xml에 등록된 모든 모델을 읽어와 등록하고 사용할 수 있게 해 준다!

→ 즉, 수정해야 할 파일 수가 적어진다!

 

 

그냥 model 패키지 안에 있는 모든 것을 읽어와 등록할 수는 없을까?

물론 가능하다!

단, 패키지 단위로 읽어 올 경우 대상이 아닌 파일(인터페이스 등)까지 읽어와,

필요 없는 곳에까지 메모리를 할당할 위험성이 있다.

이를 위해 어노테이션 기능을 활용해 보자.

 

 

Annotation 추가

Control이라는 어노테이션을 추가한다.

Retention은 Runtime(JVM 실행시에 이 어노테이션을 감지할 수 있도록 해 준다.),

Target은 Model 클래스를 인식하도록 할 것이므로 Type이다.

 

Control.java (Annotation)

package controller;

//메모리 할당 하는 클래스를 구분하는 index 역할
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(TYPE)
public @interface Control {

}

 

메모리를 할당할 모든 클래스에 @Control을 붙여 준다!

 

ListModel.java (Class)

package model;

import javax.servlet.http.HttpServletRequest;

import controller.Control;
@Control
public class ListModel implements Model {
  @Override
  public String execute(HttpServletRequest request) {
    request.setAttribute("msg", "게시물 목록");
    return "view/list.jsp"; //request가 view/list.jsp로 넘어갈 거다.
  }
}

 

그리고 컨트롤러로 패키지의 경로 내에 있는 모든 Model을 읽어와 List에 등록한다.

inAnnotationPresent() 를 통해 어노테이션이 붙은 모델만 인식하도록 할 수 있다.

여기서는 수동으로 패키지의 경로를 입력했지만,

자동으로 현재 위치로부터 경로를 읽어올 수도 있다. → 그건 다음 포스팅에서!

 

Controller.java (Servlet)

package controller;

import java.io.File;
import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
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.util.*;
import model.*;

@WebServlet("/controller")
public class Controller extends HttpServlet {
  private static final long serialVersionUID = 1 L;
  private List < String > clsList = new ArrayList < String > ();
  public void init(ServletConfig config) throws ServletException {
    try {
      String pkg = "model";
      String path = "C:\\Users\\user\\git\\doodoo\\0727MVCProject4\\src\\main\\java\\";
      String temp = path + "\\" + pkg.replace(".", "\\"); 
      //doodoo.prjct.model 이런걸 doodoo\\prjct\\model로 바꿔줌
      File dir = new File(temp);
      File[] files = dir.listFiles(); //temp에 있는 모든 파일들 가져옴
      for (File f: files) {
        //				System.out.println(f.getName());
        String s = f.getName();
        String ext = s.substring(s.lastIndexOf(".") + 1); //확장자명 가져옴
        if (ext.equals("java")) {
          //System.out.println(s);
          String fp = pkg + "." + s.substring(0, s.lastIndexOf(".")); 
          //패키지 이름과 함께 클래스이름을 가져옴
          System.out.println(fp);
          clsList.add(fp);
          //읽어 오는 순서가 일정하지 않아 데이터를 맞추기가 힘들다. -> Map같은거 쓰셈
        }
      }
    } catch (Exception ex) {}
  }

  protected void service(HttpServletRequest request, HttpServletResponse response) 
   throws ServletException, IOException {
    try {
      String cmd = request.getParameter("cmd");
      for (String ss: clsList) {
        //ss = model.ListModel
        //cmd = list
        //MODEL.LISTMODEL이 LIST를 포함하는가 확인했음ㅋㅋ이렇게하면안됨
        if (ss.toUpperCase().contains(cmd.toUpperCase())) {
          Class clsName = Class.forName(ss);
          if (clsName.isAnnotationPresent(Control.class) == false)
            continue;
          //Annotation이 존재하는지 확인하고, 존재하지 않으면 메모리 할당을 하지 않겠다.
          //Model.java같은 인터페이스를 제외하겠다는 것임
          Object obj = clsName.getDeclaredConstructor().newInstance();
          Model m = (Model) obj;
          String jsp = m.execute(request);
          RequestDispatcher rd = request.getRequestDispatcher(jsp);
          rd.forward(request, response);
          return;
        }
      }
    } catch (Exception ex) {}
  }

}

 

MVC(2)에서 계속...

'부트캠프(END) > Web' 카테고리의 다른 글

MVC(2)  (0) 2022.07.28
EL/JSTL  (0) 2022.07.25
DBCP(DataBase Connection Pool)  (0) 2022.07.22