MVC (1)
MVC 구조? 참고
Model, View, Controller 로 이루어진 디자인 패턴이다.
Model : 데이터와 비즈니스 로직을 관리
(=앱이 포함해야할 데이터가 무엇인지를 정의)
View : 레이아웃과 화면을 처리
(=앱의 데이터를 보여주는 방식을 정의)
Controller : 명령을 모델과 뷰 부분으로 전송
(=앱의 사용자로부터의 입력에 대한 응답으로 모델 및/또는 뷰를 업데이트하는 로직을 포함)
왜 사용하는가?
java와 HTML을 분리
→ 확장성과 재사용성을 확보해서 유지보수를 용이하게 하기 위해 사용한다.
JSP는 절차적언어 → 재사용이 안된다. = model1
M(Java)V(JSP)C(Servlet)→ 객체 → 재사용성이 높음 = model2
간단하게 말해서,
유저의 요청을 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 |