모델1, 모델2 또는 MVC1, MVC2라고 표현하기도 한다.
최근 스트럿츠, 스프링 등 프레임워크가 주류를 이루고 있으며 빠르게 스트럿츠를 학습하기 위한
접근법으로 급히 스트럿츠를 익히다 보니 스트럿츠를 쓰면 모델1, 2에 비해서 어떤 이점들이 있는지
모델2의 재조명으로 바라보고자 한다
모델1,2 이런것은 자바에만 국한된 것이 아니다.
자바로 인해 유명해진 모델링 기법이긴 하지만 어떤 언어를 막론하고 모델2의 구현을 위한 불가능한 언어는 없다
다만 자바 & JSP로 구현하기가 좀 쉽다
WAS서버(여기선 톰캣)가 이를 위한 준비를 이미 마친 상태고 개발자는 web.xml 정보만 컨트롤 함으로서
쉽게 구현할 수 있게 되어 있는 것이마
NT계열의 웹 서버인 IIS에선 ISAPI 필터의 구현으로 이 기능을 구현할 수 있다,
ASP, PHP WITH WINDOW 경우 어떤 언어로 구현하더라도 우선 요청을 받아 해당하는 파일을 호출하도록
되어진 부분이 IIS이기 때문이다.
LAMP를 사용하는 경우 아파치만으로 구현을 하는 것은 조금 암울하게 보일지 모르나
아파치의 설정등 어느정도의 전체 요청의 컨트롤이 가능한 요소를 구현할 수가 있다.
하지만 위 두가지 경우 전부 "구현을 해야 한다는 것이다".
그리고 "검증되지 않았다는 것이다".
가장 부담스러운 것은 모든 요청에 대한 필터를 가해야 한다는 것이다.
해서 JSP, JAVA 만큼 활성화 되고 있진 않은 듯 하다.
물론 닷넷 진행은 2.0 이후 점차, 모델 개념을 도입하고 있는 모습이 보이기도 한다.
실제 2003년까지만 해도 모델2등의 굉장히 생소하게만 느껴졌다
이미 자기가 경험해 본 패턴임에도 용어부터가 낮설게 느껴졌었다 .
그때까지 개발의 대부분은 당시 유행하던 3-TIRE, 코바, 프록시 객체 기법등 다양한 방법으로
1.디자인-2.비지니스로직-3.데이터로직에 이르는 3-tire를 구현하기 위해 노력은 했지만
결국 1.디자인이라는 이 부분에 대한 극복이 불가능했었다
이때 이 1.디자인 이라고 하는 부분은 asp, php, jsp를 뜻한다. 요청을 받고 해당하는 비지니스 객체를 통해
데이터를 처리한 후 최요 요청 받았던 해당 파일로 output이 일어난다는 얘기다
요청과 출력 그리고 이동에 대한 모든 부분을 해당 jsp파일(나머지 언어 생략)이 담당했다는 얘기다.
뭐가 불편한지 우리는 알지 못한다.
익숙하고 간편하다고 생각했기 때문이다. 그리고 무엇보다 배우기 쉽다.
하지만 그 가장 프론트에 위치한 그 파일을 디자인 개편, 로직 변경, 유지보수를 위해 HTML코딩에 CSS까지
덕지덕지 붙은 페이지를 열어 바꾸어 주어야 하는 부분을 수정하고 재배로를 하곤 했다.
자 어떤 것들을 들어낼 수 있는지 보자
우선 프론트쪽에 위치한 jsp파일의 역활중 2가지를 들어내어 디자인 소스과 구분 할 수 있다.
1. 디자인(프론트) 역활 (<- 기존의 모델1)
1) 요청 처리
2) 프로세스 처리(페이지 이동)
3) 디스플레이 처리(DB에서 가져온 내용을 출력한 다는 등의..)
2. 모델 2 적용
1) 요청 처리를 위한 컨트롤러 (서블릿 형태의..)
2) 프로세스 처리를 위한 디스페쳐 컨트롤러 (비지니스 로직에 포함 가능)
3) 디스플레이 (디자인 페이지)
자 위를 보면 3가지 역활은 그대로 있고 하나의 통합적인 페이지에서 더이상 이루어 지지 않는다
전체 요청을 처리하는 컨트롤러를 하나 생성하고 web.xml의 설정과 연동 되도록 하기 위해
init 또는 요청 처리 메소드에서 적절한 구현을 한 컨트롤러가 이제 부터 모든 요청을 관장한다.
이전 모델) 클라이언트 -> JSP <-> 비지니스로직 <-> 데이터로직
적용 모델) 클라이언트 -> 컨트롤러(서블릿) <->비지니스로직(프로세스처리) <-> 데이터로직
디스플레이
자 컨트롤러가 요청을 처리 한 후 비지니스로직 결과와 함께 디스플레이로 포워드 시켜 버린다.
그럼 데이터로직을 처리하고 받아온 데이터 값에 대한 공유는 하고 궁금해 하실텐데
이는 Request객체에 저장을 하여 공유해야할 범위내에서 공유를 하게 된다.
이 범위라는 것이 page, request, session, application 범위가 있는데 아주 간단히만 설명하면
request에 저장을 하면 페이지가 이어지는 요청 정보를 가져갈 경우 그 Request는 계속 공유된다.
예를 들어 로그인 화면에서 아이디/패스를 입력 후 로그인 과정을 거지는 전 페이지 구성에서 그 데이터를
공유하게 된다는 것이다. page객체에 저장하면 그 페이지를 벗어남과 동시에 값은 사라지고 만다.
이해가 되었을 것이다.
그럼 디스플레이는 결국 jsp코딩이 남게 되나요?
결과 부터 말하자면 그렇다.
하지만 <%...%>로 시작되는 부분이 아닌 EL, JSTL등의 기법으로 디자이너가 코드자체에 대한 컨트롤은 불가능 하도록
다양한 기법들이 제공 되고 있다.
즉 게시판 리스트 화면을 출력하기 위해 for 또는 while문 등이 HTML소스 위를 기어다는 일이 없도록
커스텀 태그도 제공하고 있어 실제 디자인과 개발소스간의 경함(?)이 많이 줄어들었다 할수 있겠다.
그럼 구체적인 부분에 대해서 언급 하겠다. (프로젝트명 mvc2 빌드패스 WEB-INF/classes)
1. web.xml 컨트롤러 및 요청에 대한 필터링
WEB-INF 바로 아래 위치하며 아래 부분이 추가 되어 있어야 함
------------------------------------------------------------------------------------------------------
생략...
<servlet>
<servlet-name>Controller</servlet-name>
<servlet-class>mvc2.controller.Controller</servlet-class>
<init-param>
<param-name>propertyConfig</param-name>
<param-value>C:/websource/jakarta-tomcat-5.5.7/webapps/mvc2/WEB-INF/Command.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Controller</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
생략...
------------------------------------------------------------------------------------------------------
해석하면 서블릿 이름은 Controller이머 mvc2.controller라는 패키지에 속한다.
컨트롤러에서 사용할 명령파라미터의 세팅 정보는 WEB-INF바로 아래 Command.properties 파일에 저정 (메모장으로 열고 저장하면 됨)
위와 같이 서블릿 정보에 대한 매칭을 시키지 위해서 페어로 존재하는
servlet-mapping의 설정은 모든 .do형식의 요청은 서블릿 Controller를 호출하도록 매칭 시킴.
(참고 여기서 do는 하다라는 의미의 실행하라는 명령으로 생각하면 됨 관용적임, 네이버 경우는 NHN을 사용하는 것 같음)
2. Command.properties (어떤 이름이라도 관계 없음, web.xml의 해당 부분과 일치만 한다면)
여기엔 단순하게 하나의 명령만 실행되도록 해 보겠다.
물론 게시판 간은 경우 list.do, write.do, modify.do 등등 처리할 파일들이 많겠지만.
여기선 아주 단순하게 파라미터로 전달 되어온 메세지하나 처리하도록 구현할 예정이므로 아래 한줄만 넣어주기 바란다.
------------------------------------------------------------------------------------------------------
/message.do=mvc2.controller.MessageProcess
------------------------------------------------------------------------------------------------------
프로젝트 메인 루트에서 message.do형식의 URL패턴이면 mvc2.controller.MessageProcess 서블릿을 호출하라는 뜻이다.
예를 들면 http://localhost/mvc2/message.do .....
3. Contoller 서블릿 구현 (이하 소스, 이놈에 네이버 에디터가 깨먹지 않아야 하는데 ㅋㅋ)
------------------------------------------------------------------------------------------------------
package mvc2.controller;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Properties;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Controller extends HttpServlet
{
private java.util.Map commandMap = new java.util.HashMap();
public void init(ServletConfig config) throws ServletException {
String propertyConfig = config.getInitParameter("propertyConfig");
Properties prop = new Properties();
FileInputStream fs = null;
try
{
fs = new FileInputStream(propertyConfig);
prop.load(fs);
}
catch (IOException e)
{
throw new ServletException(e);
} finally {
if(fs!=null) try{fs.close();} catch (IOException ex){}
}
Iterator keyIter = prop.keySet().iterator();
while(keyIter.hasNext()) {
String command = (String)keyIter.next();
String className = prop.getProperty(command);
try
{
Class commandClass = Class.forName(className);
Object commandInstance = commandClass.newInstance();
commandMap.put(command, commandInstance);
}
catch (ClassNotFoundException e)
{
throw new ServletException(e);
}
catch (InstantiationException e)
{
throw new ServletException(e);
}
catch (IllegalAccessException e)
{
throw new ServletException(e);
}
}
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
requestPro(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
requestPro(request, response);
}
private void requestPro(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
CommandProcess comp = null;
String view = null;
try
{
String command = request.getRequestURI();
if(command.indexOf(request.getContextPath())==0) {
command = command.substring(request.getContextPath().length());
}
comp = (CommandProcess)commandMap.get(command);
view = comp.requestPro(request, response);
}
catch (Throwable e)
{
throw new ServletException(e);
}
RequestDispatcher disp = request.getRequestDispatcher(view);
disp.forward(request,response);
}
}
------------------------------------------------------------------------------------------------------
초기화 메소드 init()에서 web.xml에서 필요한 정보를 읽어와 해쉬맵(HashMap)객체에 저장하는 것을 볼수 있다.
doGet, doPost는 requestPro(request, response)형식의 메소드를 내부적으로 호출하게 되어 있다.
doGet, doPost에 아무것도 없는 것은 간략하게 하기 위합니다.
필요한 보안적 처리 및 필터는 보완을 해가면 사용하길 바란다.
requestPro 메소드를 보면 첫줄에 CommandProcess comp = null; 형식을 선언하고
그 보다 몇줄 더 아해 부분에 comp = (CommandProcess)commandMap.get(command); 이 부분에서 암시적 형변환을 한다.
이 부분은 web.xml 파일에서 propertyConfig라는 파라미터의 값의 Command.properties 파일 내용일 읽고
그 서블릿의 키 값과 객체의 인스턴스 형태를 해쉬맵에 저장한다.
그럼 키에 /message.do 가 남고 값에는 mvc2.controller.MessageProcess의 인스턴스가 지정된다.
이때 주목할 것은 난데 없는 CommandProcess 와 MessageProcess 두 오브젝트이다.
어찌됐든 디스페쳐가 포워드 해야 할 경로 정보를 받아 request값과 함께 디스플레이 화면으로 보낸다 (자세한 설명은 뒤..)
4. CommandProcess.java 구현 (인터페이스)
------------------------------------------------------------------------------------------------------
package mvc2.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//요청 파라미터로 명령어를 전달하는 방식의 슈퍼 인터페이스
public interface CommandProcess {
public String requestPro(HttpServletRequest request, HttpServletResponse response)
throws Throwable;
}
------------------------------------------------------------------------------------------------------
인터페이스를 사용해 타입을 통일한다. (OOP상속의 단점을 피하고 횡적설계 인터페이스의 확장성을 고려한...)
사실 이 부분은 형변화 하지 않고 쓰면 없어도 그만이다.
뭐 전체적으로 컨트롤러의 해당 부분을 없에 버리면 그만이지만. 이 예제에서는 CommandProcess 서블릿(인터페이스)을
단 하나의 서블릿이 구현하고 있어 의미를 모를수 있으나 같은 형식의 구현체들이 여럿 있을 경우
if else / switch를 해 가며 해당 형마다 호출해 줄수는 없는 일이다. 지금 이해가 가지 않더라도 우선 따라가자 .
5. MessageProcess.java (구현체)
------------------------------------------------------------------------------------------------------
package mvc2.controller;
import javax.servlet.http.*;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class MessageProcess implements CommandProcess {
public String requestPro(HttpServletRequest request,HttpServletResponse response) throws Throwable {
request.setAttribute("message", "지금은 고정값으로 정합니다.");
return "/view/process.jsp";
}
}
------------------------------------------------------------------------------------------------------
request객체에 값을 세팅하고 난 후 3번 Contoller서블릿에서 전달해야 할 디스플레이 소스에 값을 전달하도록 리턴
그럼 컨트롤러의 requestPro메소드의 마지막 2줄이었던 RequestDispatcher가 값view를 받아 request, resposne함께
프로세스를 전힝시킨다.
RequestDispatcher disp = request.getRequestDispatcher(view);
disp.forward(request,response);
6. 디스플레이
------------------------------------------------------------------------------------------------------
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>요청 파라미터를 명령어로 전달하는 예제</title>
</head>
<body>
처리결과 : <c:set var="message" value="${requestScope.message}"/>
<c:out value="${message}"/>
</body>
</html>
------------------------------------------------------------------------------------------------------
<c:out value="${message}"/> 여기서 ${message}은 5번 MessageProcess서블릿에서
세팅했던 값이다.
자 간단하게 MVC2에 대한 개념 및 구성에 대해 알아보았다.
자바를 잘 모르고 스트럿츠로 가자라고 하면 곧 포기하게 될 것이다.
자바를 안다고 하더라도 모델을 개념 없이 스트럿츠로 가더라도 힘든 길을 가게 될 것이다.
모델2까지 이해하고 가면 돌아가는 것 같지만 결국 스트럿츠에 모델2가 어느정도 개념이 녹아 있기 때문에
결국 돌아가는 길이 빨리 학습할 수 있는 길이라는 것을 명심했으면 한다.
"계단은 한번에 한계단씩 오르듯이...." 쩝
이건 거짓말이다.
"계단은 한번에 여러 계단을 뛰어 오를수 있지만 나이는 한번에 여러살 못 먹듯이 ..."
단계를 거치는 것이 곧 지름길이다.
[확장 예제를 보고 싶은 경우]
http://mcpicdtl.blogspot.com/2009/02/2-by-vins-build-in-concept-mvc2model2_03.html