[JSP] MVC model2
* MVC 모델
MVC란 Model, View, Controller를 뜻하는 용어로 개발 형태의 일종 입니다.
- Model : 데이터베이스와의 관계를 담당합니다. 클라이언트의 요청에서 필요한 자료를 데이터베이스로부터 추출하거나, 수정하여 Controller로 전달 합니다.
- View : 사용자한테 보여지는 UI 화면입니다. 주로 .jsp파일로 작성 하며, Controller에서 어떤 View 컴포넌트를 보여줄지 결정 합니다.
- Controller : 클라이언트의 요청을 받고, 적절한 Model에 지시를 내리며, Model에서 전달된 데이터를 적절한 View에 전달 합니다.
MVC로 분할하면, 추후 유지보수에 유리
MVC Model1 은 View(출력)와 Controller(로직)가 같이 있는 형태 - JSP가 '출력'과 '로직'을 둘 다 담당
* MVC - Model1 장단점
장점 : 구조가 단순하여 익히기가 쉽다.
위와 같은 이유로 숙련된 개발자가 아니더라도 구현이 용이하다.
유지보수보다는 빨리 구축해야하는 경우 사용가능.
단점 : 출력을 위한 뷰 코드와 로직 처리를 위한 자바 코드가 함께 섞이기 때문에 JSP 코드 자체가 복잡해진다
JSP 코드에서 백엔드와 프론트엔드가 혼재되기 때문에 분업이 용이하지 않다.
코드가 복잡해져 유지보수가 어렵다.
* MVC - Model2
MVC Model2 는 Model, View 그리고 Controller가 모두 ‘모듈’화 되어 있는 형태
장점 : 출력을 위한 뷰 코드와 로직 처리를 위한 자바 코드를 분리하기 때문에 JSP가 모델1에 비해
코드가 복잡하지 않다.
뷰, 로직처리에 대한 분업이 용이하다.
기능에 따라 분리되어있기 때문에 유지보수가 용이하다.
단점 : 구조가 복잡하여 습득이 어렵고 작업량이 많다.
JAVA에 대한 깊은 이해가 필요하다.
패키지
com.command.write - MVC2의 '커맨드 객체'들을 담을 패키지
com.controller - MVC2에서 '컨트롤러 객체'들을 담을 패키지
com.lec.beans - DAO, DTO, 기타 서블릿 등
* 커맨드 인터페이스 작성
Command.java
패키지 : com.command.write
인터페이스 : Command
* 컨트롤러 작성 - Controller 서블릿
서블릿 생성 : DoController
URL매핑을 *.do 로 하면 URL 에 ~~.do 로 입력되면 이 서블릿이 요청된다.
(확장자 패턴이라고도 함)
* 컨트롤러 작성 - actionDo 메소드 추가.
doGet, doPost 와 같은 형태의 actionDo 메소드 추가하고 doGet, doPost 메소드에서 actionDo 메소드 호출케 구조 변경
actionDo 동작 확인
*.do 형태이면 get 이든 post 방식이든 관계없이 FrontController 에게 넘어간다.
package com.controller;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.RequestDispatcher;
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 com.command.write.Command;
import com.command.write.DeleteCommand;
import com.command.write.ListCommand;
import com.command.write.SelectCommand;
import com.command.write.UpdateCommand;
import com.command.write.ViewCommand;
import com.command.write.WriteCommand;
// *.do <-- 확장자 패턴 . URL 에 ~~.do 로 request 들어오면
// 아래 서블릿이 동작함.
@WebServlet("*.do")
public class DoController extends HttpServlet {
private static final long serialVersionUID = 1L;
public DoController() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
actionDo(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
actionDo(request, response);
}
private void actionDo(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("actionDo() 호출");
if(request.getMethod().equalsIgnoreCase("POST")) {
request.setCharacterEncoding("UTF-8");
}
// URL로부터 URI, ContextPath, Command 분리
String uri = request.getRequestURI(); // --> /JSP18_MVC/aaa.do
String conPath = request.getContextPath(); // --> /JSP18_MVC
String com = uri.substring(conPath.length()); // --> /aaa.do
// 테스트 출력
System.out.println("uri: " + uri);
System.out.println("conPath: " + conPath);
System.out.println("com: " + com);
// 컨트롤러는 커맨드에 따라, 로직을 수행하고
// 그 결과를 내보낼 view 를 결정한다
Command command = null; // 어떠한 로직을 수행?
String viewPage = null; // 어떠한 페이지(뷰) 를 보여줄지?
switch(com) {
case "/list.do":
command = new ListCommand();
command.execute(request, response); // Command 를 수행한 결과는 request 의 attribute 에 담겨 있다!
viewPage = "list.jsp";
break;
case "/write.do":
viewPage = "write.jsp";
break;
case "/writeOk.do":
command = new WriteCommand();
command.execute(request, response);
viewPage = "writeOk.jsp";
break;
case "/view.do":
command = new ViewCommand();
command.execute(request, response);
viewPage = "view.jsp";
break;
case "/update.do":
command = new SelectCommand();
command.execute(request, response);
viewPage = "update.jsp";
break;
case "/updateOk.do":
command = new UpdateCommand();
command.execute(request, response);
viewPage = "updateOk.jsp";
break;
case "/deleteOk.do":
command = new DeleteCommand();
command.execute(request, response);
viewPage = "deleteOk.jsp";
break;
} // end switch
// response 로서, 위 viewPage 를 forward 해줌.
if(viewPage != null) {
RequestDispatcher dispatcher =
request.getRequestDispatcher("/WEB-INF/views/board/" + viewPage);
// request 에 담긴 Command 수행결과도 viewPage 로 전달
dispatcher.forward(request, response);
}
} // end doAction()
} // end Controller
- 이를 통해 MVC model2 에선 서블릿이 ‘컨트롤러’ 역할을 하고, 사용자는 컨트롤러를 통해서만 서버에 접근 가능하게 된다.
* request.setAttribute() / getAttribute()
GET 방식의 request 이든 POST 방식의 request 이든 request 시 parameter가 담겨 가는 것은 잘 알고 있다.
그동안 request 에 담겨온 parameter를 받기 위해 request.getParameter( name ) 를 사용했었다.
그러나 getParameter 는 오직 문자열(String) 값밖에 받을 수 없었다.
반면에 request.setAttribute(name, value), request.getAttribute(name)는 어떠한 Object 타입도 보내고 받을 수 있다.
getAttribute() 의 리턴타입은 Object 이니 필요한 타입으로 형변환 필요.
* List 구현 - ListCommand 작성
list.do 커맨드가 입력되면 처리할 커맨드 객체.
클래스 : ListCommand
패키지 : com.command.write
인터페이스 : com.command.write.Command
package com.command.write;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.lec.beans.WriteDAO;
import com.lec.beans.WriteDTO;
public class ListCommand implements Command {
@Override
public void execute(HttpServletRequest request, HttpServletResponse response) {
WriteDAO dao = new WriteDAO();
List<WriteDTO> list = null;
try {
// 트랜잭션 수행
list = dao.select();
// "list" 란 name 으로 request 에 list 를 담아 컨트롤러에 전달
request.setAttribute("list", list);
} catch(SQLException e) {
e.printStackTrace();
// ※ 예외 처리 필요
// ex) 예외 페이지 설정, 혹은 예외 페이지로 redirect
}
}
}
ListCommand.java
“list” 란 name 으로 request 의 attiribute 에 list 값 담김 (정체는 DTO [] 배열)
* list.jsp
이전에는 JSP 에서 직접 DAO를 생성하여 사용했지만
이제는 request.getAttribute() 를 사용하여 컨트롤러를 통해 넘어오는 데이터를 받아서 출력한다.
이 경우는 “list” 라는 name 값에 담긴 attribute 값을 받아온다.
JSP 에서 DAO가 없어진다. 이제 JSP 는 순수하게 ‘뷰’ 역할만 담당
‘뷰’ 역할 하는 JSP 는 ‘컨트롤러’ 에서 넘어오는 데이터를 받아서 화면에 그려주는 역할만 한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import = "com.lec.beans.*, java.util.*" %>
<% // Controller 로부터 결과 받음.
List<WriteDTO> list = (List<WriteDTO>)request.getAttribute("list");
%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
<title>목록</title>
</head>
<body>
<div class="container mt-3">
<h2>목록</h2>
<hr>
<table class="table table-hover">
<thead class="table-success">
<tr>
<th>#</th>
<th>제목</th>
<th>작성자</th>
<th>조회수</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
<% for(WriteDTO dto : list){ %>
<tr>
<td><%= dto.getUid() %></td>
<td><a href="view.do?uid=<%= dto.getUid() %>"><%= dto.getSubject() %></a> </td>
<td><%= dto.getName() %></td>
<td><%= dto.getViewCnt() %></td>
<td><%= dto.getRegDateTime() %></td>
</tr>
<% } %>
</tbody>
</table>
<div class="row">
<div class="col-12">
<a class="btn btn-outline-dark" href="write.do">작성</a>
</div>
</div>
</div>
</body>
</html>
MVC2 모델 하에서는 전적으로 Controller 가 Model 과 View 를 통제 하는 것
일반적으로 MVC2 모델에선 ‘뷰’ 파일을 직접적으로 request 할수 없도록 은닉 시키거나 차단 시킨다.
* 작성 구현 - WriteCommand 작성
패키지: com.command.write
클래스: WriteCommand
인터페이스 상속 : Command
package com.command.write;
import java.sql.SQLException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.lec.beans.WriteDAO;
import com.lec.beans.WriteDTO;
public class WriteCommand implements Command {
@Override
public void execute(HttpServletRequest request, HttpServletResponse response) {
int cnt = 0;
// 입력한 값 받아오기
String name = request.getParameter("name");
String subject = request.getParameter("subject");
String content = request.getParameter("content");
// ※ 파라미터 유효성 검증
WriteDTO dto = new WriteDTO();
dto.setName(name);
dto.setSubject(subject);
dto.setContent(content);
WriteDAO dao = new WriteDAO();
try {
cnt = dao.insert(dto);
} catch (SQLException e) {
e.printStackTrace();
}
request.setAttribute("result", cnt);
request.setAttribute("dto", dto); // auto-generated key (uid)
}
}
* writeOk.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="com.lec.beans.*" %>
<% // Controller 로부터 결과 데이터 받음
int cnt = (Integer)request.getAttribute("result");
WriteDTO dto = (WriteDTO)request.getAttribute("dto");
%>
<% if(cnt == 0){ %>
<script>
alert("등록 실패");
history.back();
</script>
<% } else { %>
<script>
alert("등록 성공");
location.href = "view.do?uid=<%= dto.getUid() %>";
</script>
<% } %>
MVC model 2 동작을 이해하자
- 어떻게 서블릿이 컨트롤러로서 모든 (*.do) request 를 받아들이는지?
- 어떻게 request 에서 command 를 분리해내고 분석하는지
- request 를 해당 command 에 넘기고 DB 트랜잭션 (비즈니스 로직) 실행하는지?
- 트랜잭션의 결과를 어떻게 다시 컨트롤러에게 넘겨 받는지?
- 컨트롤러는 그 결과를 어떻게 뷰 에 넘겨주는지?
동작순서를 이해해야 디버깅이 가능해진다.
*.jsp 를 숨겨라!
MVC 패턴에서는 비즈니스로직을 ‘컨트롤러’ 에서 처리하고 JSP 파일로 포워딩 한다.
뷰 (jsp 파일) 이 직접 url 을 통해 요청되는것은 막아야 한다.
어떻게?
방법1 : WEB-INF 폴더 아래 두기 : ← 이 폴더는 보안상의 문제로 URL 로 직접 접근 못함.
방법2 : web.xml 에 세팅하기
명심)
FrontController가 모든 request 를 받는 컨트롤러 ( Controller )다.
컨트롤러는 request 를 분석하여 어떠한 Command를 수행할지 결정
Command 결과에 따라 어떠한 View 로 넘길지 결정
‘설계’가 관건이다.
페이지 를 구현하기 위해 어떠한 DAO(들)이 필요하며, 각 DAO 에는 어떠한 동작을 하는 메소드들을 설계할것인가?
어떠한 Coommand 들로 DAO 객체를 사용하고, 어떠한 parameter 들을 넘기고, 어떠한 attribute 값들을 주고 받을것인가?