Site Search :
Standard Enterprise XML Methodology Pattern Setting Tunning Other
Article Contributors
GuestBook
Javapattern Maven
XSourceGen Dev
JetSpeed Test
JLook Image
jLook Family Site


Web Application Framework개발방법(2)-LifeCycle,Controller
 
안녕하십니까? 전편에 이어서 이번에는 WAF에 대한 2차 내용을 진행하고자 합니다. 항상 생각이지만 여러분들께 꼭 필요한 정보들을 보여주고자 하는데 얼마나 만족하실런지 잘 모르겠습니다. ( 2003/03/24 ) 510
Written by ienvyou - 최지웅
1 of 1
 

안녕하십니까? 전편에 이어서 이번에는 WAF에 대한 2차 내용을 진행하고자 합니다.
항상 생각이지만 여러분들께 꼭 필요한 정보들을 보여주고자 하는데 얼마나 만족하실런지
잘 모르겠습니다.

자, 다시 반말로 시작합니다.

▶ Framework Lifecycle설계

지난 편에서는 프레임웍이란 도대체 무엇이며, 어떠한 특징을 가졌는지를 살펴보았다.
또한 개발주변상황에 대하여서도 알아보았는데, 사실 개발이란 것이 무대뽀가 가능할런지도
모르지만 설계사상만큼 중요한 항목이 없지 않나한다. 

프로젝트란 것이 하나의 집을 짓는 것과 같아서 뛰어난 감각을 가진 건축설계사에 의하여 치밀하게 
설계되어지고 설계사상을 충분히 반영할 수 있는 현장감독을 고용하여, 아낌없는 투자가 이루어졌을때
비로소 정말 잘지어진 집을 짓는 것과 같음이다. 
당연히 설계가 잘되어져 있고, 좋은 시공업자들이 붙는다는 건 소프트웨어에서도 마찬가지로
좋은 아키텍트아래에서 역량을 충분히 낼 수 있을 만한 개발자들이 있다는 말로 표현되어질 수 있다.

콕번은 "제대로 된 사람들만 있다면 가만히 두어도 프로젝트는 성공하기 마련이다"라는 이야기를
한다.

당신은 진정으로 위의 말을 실감할 수 있는가? 프로젝트는 능동적이어야 한다.

각설하고, 설계작업을 한번 해보도록 하자.
가장 중요한 것이 라이프사이클일 수 있는데, 이미 우리가 WAF를 만들어낼때 서블릿 엔진을 통하여 
client request, response정보는 한번 걸러져 우리가 만들어내는 애플리케이션으로 전달되어질 것이다.

기본적인 시나리오를 보았었을 때 중앙집중적으로 request를 받아서 처리한 후 JSP로 Presention을 
한다고 하였다. 이것이 무엇을 의미하는가? 

개발자는 우선 들어온 reqeust를 이용하여 BL을 처리하고, JSP측으로 request에 attribute를 세팅시킨다는
의미이고, 화면의 구성은 template페이지를 이용한 조립방식이다라는 힌트에서 include속성이 
필요하다는 것을 알것이다.
그러면 여기서 어떤 결과가 도출되는가? 한가지측면이긴 하지만 Model 2방식이기 때문에 Dispatcher방식의 
도입이 하나 존재하게 된다. 이 dispatcher의 프레임웍상의 위치는 어디인가? 맨 마지막부분이다.

이러한 부분을 코더들에게 매번 작성되는 include, forward부분을 작성하게 할것인가? 그렇게 생각한다면
당신을 초보아키텍트에 지나지 않는다. 그러면 extends를 시켜서 상위클래스에서 사용하게 할것인가?
흠.. 괜찮은 방법이기도 하지만 그러한 것까지 개발자들에게 보여줄 필요는 없다. 
추상화의 정도를 아주 끌어올리자는 것이다. 개발자들 가뜩이나 머리아픈데 이러한 것까지 신경쓰게
한다는 것은 설계자가 "나쁜 놈" 소리를 들을 수 있는 이유가 될수도 있을 것이다.

자, 그럼 끝부분에 대한 것은 그렇게 처리되어지고, BL(Business Logic)을 처리함에 있어서 프로그램작성에
개발자들이 필요한 API객체들은 대체 무엇인가? 
Control부분이 framework에 의하여 전달되어지고 개발자들이 실제 필요한 객체를 적절하게 요리하여
던져줘야 할 책임이 프레임웍에 있다. 
대충 나열해 본다면 context, request, response정도면 될까? 어떻게 생각하는가? 
당연히 위의 3가지만 있어도 연결된 API상에서 모든 객체를 뽑아낼 수 있다. 

서블릿의 lifecycle이 가지는 init(), service(), destory()메소드를 적절하게 요리해보자.
초기화와 종료부분은 필요에 의하여 개발자들에게 코딩하도록 주문하자. 실제 중요한 것은 service인데
아무것도 필요없이 들어오는 request에서 원하는 객체등을 뽑아낸후 request.setAttribute()을 이용하여
jsp측으로 데이터를 실어보낸다는 사상만을 갖자

아래와 같은 코딩이 이루어지도록 하는 것이다.

    public void service(){
        //request에서 원하는 데이터를 뽑아낸다.

        // EJB Business호출 또는 Data Access Object를 이용하여 결과를 얻어낸다

        // 얻어낸 결과를 request에 세팅하여 jsp로 보낸다.
    }


위의 코드만으로 모든 처리가 끝나도록 하자는 것이다. 그렇다면 forwading될 페이지 및 기본적인
설정정보등은 대체 어디에 기술한다는 것인가? 1편에서 XML을 이용하여 기술한다고 하였다.

자, 결론을 지어보자. 최대한 심플하게 구성하기 위하여 기본적으로 프로그래머들에게 필요한
context, request를 넘겨주는 것으로 결론을 지어보자.

복잡한 라이프사이클을 정의하면 읽는 당신들만 복잡하기 때문에 3가지 메소드를 자동으로 
호출할 수 있도록 정의하자.

놀새는 아래와 같이 정의하고 그 메소드들을 Carouser Framework의 라이프사이클이라 임명하노라.
참고로 response에 대한 핸들링 기능을 넣지 않았다.

        setServletContext(context);
        perform(req);
        doEnd(req);


위의 정도로 라이프사이클을 정의하여 자동으로 framework이 실제 설정된 클래스들을 이용하여
호출을 유도하도록 하자.

▶ Framework Configuration을 이용한 Controller시나리오

자, 이제 3가지 메소드를 이용하여 라이프사이클을 정의해봤다. 이제는 그 라이프사이클의
흐름을 탈 수 있는 제반 환경에 대하여 논의해보자. 
기본적으로는 XML을 쓸것이다라고 여러분들에게 이야기했는데, 여기서 사용될 것 말고 기본적으로
엔진에서 사용되는 XML은 무엇인가? 

web.xml 이 맞는가? 그곳에는 filter, listener등의 초기에 로딩되어져야 할 클래스 및 각종 환경정보들이
들어가게 된다. 거기에 우리는 framework엔진을 시작프로그램형식으로 등록시켜 모든 정보를 로딩하게
할 예정이다.



  1. 서블릿 컨테이너가 동작을 시작하여 web.xml의 환경정보를 읽어들인다. 이 web.xml에는 기본적으로 프레임웍 엔진을 startup으로 탑재시켜 놓는다
  2. 프레임웍엔진이 스타트 되어짐과 동시에 미리 설정된 XML Configuration을 이용하여 사용될 클래스및 모든 구동정보를 메모리상에 로드하도록 한다.
  3. 컨테이너로 BL에 대한 요청이 들어올시 프레임웍은 들어온 URI를 해석하여 해당 URI에 대한 구동클래스 의 객체를 생성하고, 라이프 사이클에 대한 호출을 시도한다.
  4. 처리되어진 로직의 결과값은 미리 정의된 XML Configuration의 JSP페이지측으로 자동으로 세팅되어져 넘어가도록 한다
위에서 XML을 이용하여 framework의 configuration을 잡는다고 하였다. 기본적으로 두가지의 xml이 필요할것으로 보이는 하나는 request URI가 실제 들어왔을 경우 어떤 클래스로 BL을 처리해야 할지를 결정하는 파일과, 처리된 결과가 어떤 JSP로 매핑되어져야 할지를 결정하는 설정파일로 나눌수 있다. 이런 기법은 어디에 적용되어 있는가? Sun blueprint의 petstore및 Apache Struts Framework에서도 이 기법이 사용되어지고 있다. 우리는 이미 web.xml의 servlet-mapping설정에 아래와 같은 url명이 들어오면 해당 클래스가 자동으로 실행되도록 미리 설정을 하자. 참조 : web.xml

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.
//DTD Web Application 2.2//EN" 
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>

  <servlet>
    <icon>
      <small-icon>FrameworkEngine</small-icon>
      <large-icon>FrameworkEngine</large-icon>
    </icon>
    <servlet-name>Engine</servlet-name>
    <display-name>FrameworkEngine</display-name>
    <servlet-class>com.javapattern.framework.FrameworkEngine</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet>
    <icon>
      <small-icon>FrameworkComposer</small-icon>
      <large-icon>FrameworkComposer</large-icon>
    </icon>
    <servlet-name>Composer</servlet-name>
    <display-name>FrameworkComposer</display-name>
    <servlet-class>
    com.javapattern.framework.composer.FrameworkComposer
    </servlet-class>
    <load-on-startup>2</load-on-startup>
  </servlet>



  <servlet-mapping>
    <servlet-name>Engine</servlet-name>
    <url-pattern>*.main</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>Composer</servlet-name>
    <url-pattern>*.view</url-pattern>
  </servlet-mapping>

  <session-config>
    <session-timeout>20</session-timeout>
  </session-config>

</web-app>
            
        
그렇다면 그러한 URI정보를 해석하여 어떤 클래스를 구동시킬지 환경을 어떻게 설정하면 될까? 아래처럼 간단하게 정의해보도록 하자. 참조:request_config.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<request-root>
	<request name="test" nextJsp="test.view" isLogin="false">
		<action-class>ccc</action-class>
	</request>
	<request name="viewRequestList" nextJsp="list.view" >
		<action-class>temporary.RequestAdminAction</action-class>
	</request>

</request-root>
   
<request-root> : Root element로서 하위 요소로 사용자의 request시 처리되어야 할 정보 <request> : Attribute정보로 name, nextJsp, isLogin을 가지고 있으며, 하위 element로 action-class를 가지고 있다. - name : 해당 request를 유일하게 식별할 수 있는 이름요소 - nextJsp : Action-bean의 business logic처리후 어떤 화면을 보여줄 것인지를 설정하는 부분 보통 확장자 .view의 형태를 가진 페이지로 설정이 되면 .view에 대한 내용은 webpages_config.xml파일을 이용하여 세팅하도록 하자. - isLogin : 해당 request URI입력시 action-bean클래스를 처리할 경우 login이 필요한 페이지에 대한 설정정보를 넣을 수 있도록 하나의 속성값으로서 정의 <action-class> : 이곳에는 여러분들이 개발한 action-class파일의 이름이 위치. 패키지명을 포함한 클래스이름이 주어져야 하며 request설정시 action-class태그가 존재한다면 engine은 lifecycle에 의하여 세가지 메소드(위의 라이프사이클에서 설명)를 자동적으로 호출하도록 한다. 즉 주소창에 viewRequestList.main이라는 입력이 들어오게 되면 자동으로 엔진은 temporary.RequestAdminAction이라는 클래스의 객체를 생성한 후 그 클래스가 가진 setServletContext, perform, doEnd메소드를 차례로 실행시키고, 그 BL을 처리할 view측으로 list.view를 이용하여 처리하겠다는 환경설정이다. Struts 및 Petstore를 조금이나마 공부한 사람이라면 쉽게 이해할 수 있을 것이다. 그렇다면 xx.view는 어떻게 처리되는가? 위의 web.xml에서 확장자 view가 들어오게 되면 자동으로 com.javapattern.framework.composer.FrameworkComposer클래스가 동작이 되도록 반영시켜 놓았다. 위의 Composer에서는 들어오는 request를 읽어들여 자신이 보여줘야 할 페이지에 대한 정보를 이용하여 각각의 jsp파일들을 하나의 템플릿을 이용하여 브라우져에 보여질 수 있도록 만들어내게 하면 될것이다. 참조 : webpage_config.xml


<?xml version="1.0" encoding="euc-kr"?>

<webpage-definitions>
  
<!-- Sample -->
  <webpage>
  	<name>test</name>
  	<description>설명</description>
  	<title>화면 제목</title>
	<layout>/template.jsp</layout>
  	<page key="header" 	url="/sample/header.jsp"/>
  	<page key="menu" 	url="/sample/menu.jsp"/>
  	<page key="body" 	url="/sample/body.jsp"/>
	<page key="sidebar" 	url="/sample/sidebar.jsp"/>
  	<page key="footer" 	url="/sample/footer.jsp"/>
  </webpage>
<!-- Sample end. -->
<webpage>
  	<name>poll.admin</name>
	<layout>/template.jsp</layout>
  	<page key="menu" 	url="/menu.jsp"/>
  	<page key="body" 	url="/component/poll/admin/pollAdmin.jsp"/>
  </webpage>

</webpage-definitions>
   
위의 환경설정 정보는 화면의 레이아웃을 구성할 경우 FrameworkComposer 클래스를 이용하여 presentation을 구성하는 중요정보이다. webpage tag의 경우 중요한 element를 가지고 있다. - name : request시 들어오는 URI의 name으로서 페이지를 식별하는 가장 중요한 정보이다 - description : 이 웹 페이지에 대한 개요를 사용할 수 있다. 주석의 의미로 생각하자. - title : 웹브라우져에 나타나는 html title정보를 설정 - layout : 보여지는 웹 페이지는 여러 가지 template형태로 구성되어질 수 있으므로 서로 다른 레이아웃을 가진 페이지가 필요하다면 이 부분에 설정하도록 만든다. - page : 보통 jsp파일에 대한 url을 가지고 있으며, 중복되어 선언되어 질 수 있도록 한다. 기본적으로 5개의 레이아웃으로 웹페이지를 구성하게 되면 기본적으로 가질 수 있는 header, menu, body, sidebar, footer로 구성되는 페이지를 가질 수 있도록 설정하자.
설정파일의 로딩 자. 위처럼 설정을 해놓고 사용한다고 가정을 했을 경우 어느 시점에 저러한 설정파일을 로딩시킬 것인가? web.xml과 같은 개념이라고 생각하자. 즉 엔진은 request가 들어오는시점에 저런 설정을 로딩하여 처리한다면 지연시간이 만만치 않을 것이므로, 최초 request이전에 메모리에 로딩되어 있어야 한다. 그렇다면 이제 무엇이 필요한가? 설정은 XML로 되어 있다고 하였다. 파싱도구..바로 그것이다. 이전 아티클인 SAX, DOM파서를 이용할 시점이 드디어 온것이다. 아직 안읽어보았는가? 아래의 글을 읽어보도록 하자. About DOM & DOM Programming with Java http://www.javapattern.info/viewArticle.do?articleId=1047519735641 About SAX & SAX programming with Java http://www.javapattern.info/viewArticle.do?articleId=1047437421002 자. 읽어보았다면 위의 최초 로딩되어지는 서블릿을 정의하여 init()메소드에 위의 설정파일들을 로딩하는 코드를 넣어보도록 하자. 아까 위의 xml에서 startup 1번으로 설정된 클래스가 무엇이었는가? <servlet-name>Engine</servlet-name> <display-name>FrameworkEngine</display-name> <servlet-class>com.javapattern.framework.FrameworkEngine</servlet-class> <load-on-startup>1</load-on-startup> 위의 FrameworkEngine 클래스였다. 위의 엔진핵심클래스에 init()메소드에 xml을 파싱할 수 있도록 환경을 읽어들이는 부분을 삽입해보도록 하자.


public class FrameworkEngine extends HttpServlet
{
    private ServletContext context;

    public void init()
    {
        context = getServletContext();
        try {
            URL xmlURL = context.getResource("/WEB-INF/request_config.xml");
            RequestDAO.loadXml(xmlURL);
        } catch(MalformedURLException e) {e.printStackTrace();}
    }
}
   
RequestDAO는 무엇을 하는 것일까? JAXB에 대하여는 들어보았는가? XML의 자바데이터 매핑.. 들어보지 못했다면 jakarta-commons부분의 digester에 대해서는 알고 있는가? 풀어서 설명하자면 xml의 문서형식이 나타나게 되면 그것은 데이터베이스와 같은 구조로 설명이 되어 질 수 있고, XML의 각 태그들은 자바의 data model object로 변환이 가능하다. 위에서 설명되었던 request_config.xml파일은 멤버변수로 private String name; private String nextJsp; private String action; private String isLogin; 를 가진 자바클래스파일이 될 수 있다는 것이다. RequestDAO는 이전 아티클인 XML DatabaseQuery Manager구현하기의 공통클래스의 기능을 갖는다. XML을 이용한 Database Query Manager 구현하기 http://www.javapattern.info/viewArticle.do?articleId=1047864524403 위의 정보는 request에 관련된 정보를 각각 파싱하여 HashMap의 형태로 XML데이터를 Java Object로 변형시킨 후에 그 정보를 가지고 있게 된다. 자, 그러면 Framework엔진의 코드를 모두 보도록 하자.

package com.javapattern.framework;

import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class FrameworkEngine extends HttpServlet
{
    private ServletContext context;

    public void init()
    {
        context = getServletContext();
        try {
            URL xmlURL = context.getResource("/WEB-INF/request_config.xml");
            RequestDAO.loadXml(xmlURL);
        } catch(MalformedURLException e) {e.printStackTrace();}
    }

    public void doProcess(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException
    {
        String reqName = getRequestName(req);
        trace("★★★ com.javapattern.framework Engine Working Start!!! ★★★  by "+reqName);
        Request config = RequestDAO.getRequest(reqName);

        if( config == null ) {
            RequestDispatcher rd = req.getRequestDispatcher("default.view");
            rd.forward(req,res);
            return;
        }
        trace(config.toString());
        
        if(config.getAction()!=null && !config.getAction().equals(""))  {
            try {
                if( config.isLogin().equals("true") ) {
                    RequestDispatcher rd = req.getRequestDispatcher("/login.jsp");
                    rd.forward(req,res);
                    return;
                }
                if(config.getAction() != null) {
                
                    Class cl = Class.forName(config.getAction());
                    RequestAction ra = (RequestAction)cl.newInstance();
                    ra.setServletContext(context);
                    ra.perform(req);
                    ra.doEnd(req);
               
                }
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
        RequestDispatcher rd = req.getRequestDispatcher(config.getNextJsp());
        rd.forward(req,res);
    }

    private static String getRequestName(HttpServletRequest request)
    {
        String uri = request.getRequestURI();
        String contextPath = request.getContextPath();

        int idx = uri.lastIndexOf(".main");
        
        return uri.substring(contextPath.length()+1,idx);
    }

    public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException
    {
        doProcess(req,res);
    }

    public void doPost(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException
    {
        doProcess(req,res);
    }

    public void trace(String msg)
    {
        System.out.println("com.javapattern.frameworkEngine ] "+msg);	
    }	
}

   
위의 bold체로 쓰여진 부분들이 핵심부분이다. 주소창에 xx.main이라는 입력이 들어오게 되면 위의 클래스가 작동된다고 설명하였다. 그렇다면 그 이름을 어떻게 XML의 환경정보와 매핑을 시킬 것인가? 들어온 request는 getRequestName()메소드를 통하여 .main을 substring()하여 실제 매핑이름만을 얻어내게 되는 만약 viewRequestList.main라는 요청이 들어오게 된다면 위 클래스는 .main을 제외한 viewRequestList라는 스트링을 얻어내게 되고, XML을 자바로 변환시킨 Request객체의 이름중에 viewRequestList라는 객체가 존재하는 지 찾아내게 된다. 그역할을 하는 부분이 Request config = RequestDAO.getRequest(reqName); 이다. 이렇게 해서 찾아낸 객체가 있을 경우 만약 처리해야할 action class가 존재하는지를 찾아본다. 존재한다면 어떻게 할것인가? 또 그 클래스의 이름은 일반적인 스트링의 형태이다. 스트링을 객체로? 자. 이제 reflection을 도입할때가 되었다. 가장 간단한 reflection으로 클래스 객체의 생성이 있는데 그것을 이용해보도록 하자. Class cl = Class.forName(config.getAction()); RequestAction ra = (RequestAction)cl.newInstance(); ra.setServletContext(context); ra.perform(req); ra.doEnd(req); config.getAction()메소드를 호출하게 되면 viewRequestList의 request에 해당하는 action클래스를 반환한다 어떤 클래스인지는 request_config.xml의 <request name="viewRequestList" nextJsp="list.view" > <action-class>temporary.RequestAdminAction</action-class> </request> temporary.RequestAdminAction로서 객체를 생성하게 되고 라이프사이클 메소드인 ra.setServletContext(context); ra.perform(req); ra.doEnd(req); 를 호출하도록 지시한다. 여기서 한가지 주의해야 할 것은 우리가 나름대로의 RequestAction이라는 interface를 선언하고 개발자들에게 이 클래스를 상속받아야 한다는 것을 주지시켜야만 한다. 오버라이딩의 개념을 이용하여 해당 클래스를 호출하려면 이러한 설정을 필수이기 때문이다. 이해가 가는가? 이렇게 되면 요청과 동시에 요청에 해당하는 Business를 처리할 수 있는 기본적인 controller의 환경설정을 볼 수 있었다. 다음 편에서는 실제 요청의 수행과 그 후의 jsp로 forward시켰을때 웹페이지를 어떻게 조립할 것인지에 대한 전략을 수립해보도록 하자.
 
1
References
 
Copyright ⓒ 2003 www.javapattern.info & www.jlook.com, an jLOOK co.,LTD