달력

52024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
JavaServer Faces 입문
hanbit (2003-10-18 09:15:02)

저자: 부디 쿼니아완(Budi Kurniawan), 역 이상화

자바 웹 프로그래밍 분야에서 JavaServer Faces(이하 JSF)는 새로운 화두로 떠오르고 있다. JSF로 여러분들은 사용자에 의해 발생된 이벤트를 잡아 내거나 웹 페이지에서 웹 컴포넌트를 사용할 수 있다. 가까운 미래에 자바 개발 도구들이 이런 기술을 지원할 것이다. 웹 애플리케이션을 개발하는 것은 현재 드래깅(dragging)과 드로핑(dropping) 및 이벤트 리스너 작성이 가능한 스윙처럼 될 것 이다. 이 기사는 JSF입문 기사정도로 보면 될 것이다. 이 기사는 JSF에서 가장 중요한 것 중에 하나인 이벤트-드리븐(event-driven) 방식에 초점을 맞추고 있다. 본 기사를 무리없이 이해하기 위해서는 서블릿, JSP, 자바빈즈, 커스텀 태그에 관한 선행 지식이 필요하다.

무엇보다도 JSF 애플리케이션은 서블릿/JSP 애플리케이션이다. 웹 디스크립터 파일, JSP 페이지, 커스텀 태그 라이브러리, 정적인 파일들을 가지고있다. 기존 것들과 다른점은 JSF 애플리케이션은 이벤트-드리븐 방식이라는 것이다. 이벤트 리스너 클래스를 사용함으로써 애플리케이션의 작동 방법을 결정할 수 있다. 다음과 같은 단계를 통해 JSF 애플리케이션을 작성할 수 있다.
  1. HTML를 캡슐화하는 JSF 컴포넌트를 사용하여 JSP 페이지를 작성한다.
  2. 사용자 입력과 컴포넌트 데이터의 값을 저장할 수 있는 자바빈즈 파일을 만든다.
  3. 사용자가 버튼을 클릭하거나 폼을 제출할 때 발생하는 이벤트를 처리할 수 있는 이벤트 리스너를 작성한다. JSF는 ActionEventValueChangedEvent 2가지를 지원한다. ActionEvent는 버튼을 클릭하거나 폼을 제출할 때 발생되며, ValueChangedEvent는 JSF 컴포넌트가 변경될 때 일어난다.
이제 JSF가 어떻게 동작하는지 자세히 알아보자.


Mac OS X for Java Geeks

참고 도서

Mac OS X for Java Geeks
Will Iverson




JSF 작동방법

JSP 페이지는 JSF 애플리케이션의 유저 인터페이스 역할을 한다. 각각의 페이지는 폼, 입력박스, 버튼과 같은 웹 컨트롤을 나타내는 JSF 컴포넌트를 포함하고 있다. 폼 내부에 입력박스가 포함 되듯이 컴포넌트들은 다른 컴포넌트 내에 속할 수 있다. 각각의 JSP는 컴포넌트 트리 구조로 표현되며 자바빈즈는 사용자 요청으로부터 데이터를 저장한다.

흥미로운 점은 사용자가 버튼을 클릭하거나 폼을 제출할 때마다 매번 이벤트가 발생한다는 것이다. 모든 이벤트에 의해 발생된 메시지는 HTTP를 통해 서버로 전달된다. 서버는 Faces 서블릿이 실행되고 있는 웹 컨테이너이다. javax.faces.webapp.FacesServlet 클래스인 Faces 서블릿은 모든 JSF 애플리케이션의 실행 엔진이다. 동일한 웹 컨테이너에서 작동하는 각각의 JSF 애플리케이션은 자신만의 Faces 서블릿을 가지고 있다. 다른 중요한 객체는 현재 요청과 관계된 모든 필수 정보를 캡슐화하고 있는 javax.faces.context.FacesContext이다.

애플리케이션의 이면에서 Faces 서블릿이 처리하는 과정은 복잡하지만 세부사항에 대해 모두 알 필요는 없다. 단지 Faces 서블릿이 이벤트를 발생시키는 웹 컨트롤을 포함하고있는 JSP 페이지의 컴포넌트 트리를 생성한다는 것만 알면 된다. Faces 서블릿은 애플리케이션내 모든 페이지에 접근할 수 있기 때문에 트리를 어떻게 구성 할 것인지 알고있다. 또한 Faces 서블릿은 Event 객체를 생성하거나 적절한 리스너에게 통보한다. 요청에 연결된 FacesContext 객체를 통해서 JSP 페이지의 컴포넌트 트리를 얻을 수 있다.

클라이언트 브라우저속 웹 컨트롤에 의해 발생하는 이벤트는 브라우저 종류, 요청 URL과 같은 HTTP 요청으로 캡슐화된다. 그러므로 Faces 서블릿 처리가 필요한 모든 요청은 바로 서블릿으로 전달되어야 한다. 그렇다면 어떻게 모든 HTTP 요청을 Faces 서블릿으로 처리하게 할 것인가? 특정한 URL 패턴을 Faces 서블릿으로 매핑하기 위해 배치 디스크립터에다가 servlet-mapping 요소를 기술하면 된다. 관례상 다음과 같이 /faces/* 패턴을 사용한다.
<!-- Faces Servlet --><servlet>    <servlet-name>Faces Servlet</servlet-name>    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>    <load-on-startup>1</load-on-startup></servlet><!-- Faces Servlet Mapping --><servlet-mapping>    <servlet-name>Faces Servlet</servlet-name>    <url-pattern>/faces/*</url-pattern></servlet-mapping>
요청하는 URL은 반드시 <url-pattern>에 선언된 패턴을 포함하고있어야 한다. 설정하는 것은 별로 어렵지않으며 Faces 서블릿을 포함하고 있는 <servlet> 요소가 애플리케이션이 시작될 때 서블릿을 로딩 시키는 <load-on-startup> 요소를 가지고 있다는 것을 기억해두자.

컴포넌트에 의해 발생하는 모든 이벤트를 처리하기 위해 반드시 이벤트 리스너를 작성해야 하고 모든 컴포넌트를 등록해야 한다. 이는 컴포넌트를 나타내는 커스텀 태그 속
<action_listener> 요소를 사용하면 된다. 예를 들어 커맨드 버튼에 의해 발생하는 이벤트를 처리하기 위해 jsfApp.MyActionListener라는 리스너를 만들었다면 JSP 페이지 안에 다음과 같이 작성해야 한다.
<h:command_button id="submitButton" label="Add" commandName="submit" >  <f:action_listener type="jsfApp.MyActionListener" /></h:command_button>
액션 리스너는 반드시 javax.faces.event.ActionListener 인터페이스를 상속 받고 값 변경에 관한 리스너는 javax.faces.event.ValueChangedListener를 상속 받아야 한다. 이제 이벤트-드리븐 방식으로 JSF가 동작 하는 방법을 보기위해 간단한 애플리케이션을 작성해 보도록 하겠다.

간단한 JSF 애플리케이션

Source Code

예제 애플리케이션을 작성하기 위한 소스 코드를 다운받는다.



여기에서는 두 개의 숫자를 더하는 간단한 JSF 애플리케이션을 작성할 것이다. 애플리케이션을 실행시키기 위해 Tomcat 5와 JWSDP에 포함된 JSF v1.0 EA4가 사용된다(이것들은 JWSDP(Java Web Services Developer Pack) 1.2에 포함되어 있다). 애플리케이션은 다음과 같은 것들로 구성되어 있다.
  • adder.jsp, JSP 페이지
  • NumberBean, 데이터 저장소 자바빈즈
  • MyActionListener, 액션 리스너
  • web.xml, 배치 디스크립터
JSF 애플리케이션을 실행시키기 위해 JSF 관련 라이브러리를 포함하고있는 .jar 파일이 필요하다. 일단 JWSDP 1.2를 설치하면 jsf/lib 디렉토리 밑에 앞서 말한 파일을 찾을 수 있을 것이다. WEB-INF/lib 디렉토리 밑에 .jar 파일 복사한다. 다음은 복사할 .jar, .tld 파일들이다.
  • jsf-api.jar 파일은 Faces 서블릿과 javax.faces 패키지 내 관련 클래스들을 포함하고 있다.
  • jfs-ri.jar JSF 실행과 관련된 파일이다.
  • jstl_el.jar는 JSTL 표현 수식 문법을 처리한다.
  • standard.jar는 JSTL 태그들을 사용하거나 JFS 실행을 위한 참조 클래스에 의해 참고 된다.
덧붙여 JSF 애플리케이션은 아파치 자카르타의 일부인 다음과 같은 라이브러리를 필요로 한다. 이 라이브러리들은 본 기사와 함께 애플리케이션에 포함되어 있다.
  • commons-beanutils.jar 파일은 JavaBeans 컴포넌트 프로퍼티에 접근해서 정의하기 위한 유틸리티를 포함한다.
  • commons-digester.jar 파일은 Apache Common Digester 클래스들을 포함하고있는 라이브러리이다.
  • commons-logging.jar 파일은 일반적인 목적의 유연한 로깅 도구이다.
아래 소 주제에서는 예제의 각 부분에 대해 논의하고 있다. 마지막 소주제인 "애플리케이션 실행과 컴파일"에서는 JSF 애플리케이션이 어떻게 동작하는지 보여준다.

디렉토리 구조 만들기

디렉토리 구조를 만드는 것부터 예제 JSF 애플리케이션을 시작해보자. 톰캣에서는 webapps 디렉토리 밑이 될 것이다. [그림 1]은 myJSFApp 애플리케이션의 디렉토리 구조를 나타낸다.


[그림 1] JSF 애플리케이션 디렉토리 구조

배치 디스크립터 작성하기

다른 servlet/JSP 애플리케이션과 마찬가지로 예제는 아래 [리스트 1]과 같이 배치 디스크립터를 작성해야 한다.

[리스트 1] 배치 디스크립터 (web.xml 파일)
<?xml version="1.0"?><!DOCTYPE web-app PUBLIC    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"    "http://java.sun.com/dtd/web-app_2_3.dtd"><web-app>    <!-- Faces Servlet -->    <servlet>        <servlet-name>Faces Servlet</servlet-name>        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>        <load-on-startup> 1 </load-on-startup>    </servlet>    <!-- Faces Servlet Mapping -->    <servlet-mapping>        <servlet-name>Faces Servlet</servlet-name>        <url-pattern>/faces/*</url-pattern>    </servlet-mapping></web-app>
배치 디스크립터에는 두 부분으로 이루어져 있다. <servlet> 요소는 Faces 서블릿을 등록하고 <servlet-mapping> 요소는 URL에 포함된 /faces/ 패턴을 가지는 모든 요청을 Faces 서블릿으로 보낸다.

JSP 페이지 만들기

[리스트 2]와 같이 adder.jsp는 사용자 인터페이스를 제공한다.

[리스트 2] adder.jsp 페이지
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %><html><head><title>Add 2 numbers</title></head><body><jsp:useBean id="NumberBean" class="jsfApp.NumberBean" scope="session" /><f:use_faces><br />    <h:form id="addForm" formName="addForm" ><br />        First Number: <br />        <h:input_number id="firstNumber" valueRef="NumberBean.firstNumber" /><br />        Second Number:         <h:input_number id="secondNumber" valueRef="NumberBean.secondNumber" /><br />        Result:         <h:output_number id="output" valueRef="NumberBean.result"/><br>        <h:command_button id="submitButton" label="Add" commandName="submit" >            <f:action_listener type="jsfApp.MyActionListener" />        </h:command_button>    </h:form></f:use_faces></body></html>
htmlcore, 두개의 JSF 태그 라이브러리를 사용하기 위해 두개의 taglib directive를 선언해야 한다. 두개 라이브러리에 대한 태그 라이브러리 디스크립터는 jsf-ri.jar 파일에 포함되어 있으므로 걱정하지 않아도 된다. 태그 라이브러리에 대한 접두어는 각각 hf다.
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<jsp:useBean> 액션 요소는 세션 범위를 가진 NumberBean 자바빈즈를 정의한다.
<jsp:useBean id="NumberBean" class="jsfApp.NumberBean" scope="session" />
JSF 컨트롤에 대해 살펴보면 JSF 컨트롤은 반드시 <f:use_faces> 안에 포함되어 있어야 한다.
<f:use_faces>...</f:use_faces>
<f:use_faces> 사이에 폼이 들어 갈 수 있다.
<h:form id="addForm" formName="addForm">...</h:form>
폼 내부에 두 개의 숫자 입력 창(input_number), 하나의 숫자 출력 창(output_numbers), 명령 버튼(command_button)이 있다.
First Number:<h:input_number id="firstNumber" valueRef="NumberBean.firstNumber" /><br />Second Number:<h:input_number id="secondNumber" valueRef="NumberBean.secondNumber" /><br />Result:<h:output_number id="output" valueRef="NumberBean.result" /><br /><h:command_button id="submitButton" label="Add" commandName="submit">    <f:action_listener type="jsfApp.MyActionListener" /></h:command_button>
명령버튼을 위한 액션 리스너에 주의를 기울이자. [그림 2]는 루트 요소가 생략된 JSP 페이지 컴포넌트 트리이다.


[그림 2] adder.jsp 페이지의 컴포넌트 트리

네 개의 자식 컴포넌트를 가지고 있는 폼이 메인 컴포넌트이다.

객체 모델 작성하기

예제를 위해 2개의 숫자와 그 결과값을 기억하고 있는 자바빈즈가 필요하다. [리스트 3]은 NumberBean 자바빈즈이다.

[리스트 3] NumberBean 자바빈즈
package jsfApp;public class NumberBean {    int firstNumber  = 0;    int secondNumber = 0;    public NumberBean () {        System.out.println("Creating model object");    }    public void setFirstNumber(int number) {        firstNumber = number;        System.out.println("Set firstNumber " + number);    }    public int getFirstNumber() {        System.out.println("get firstNumber " + firstNumber);        return firstNumber;    }    public void setSecondNumber(int number) {        secondNumber = number;        System.out.println("Set secondNumber " + number);    }    public int getSecondNumber() {        System.out.println("get secondNumber " + secondNumber);        return secondNumber;    }    public int getResult() {        System.out.println("get result " + (firstNumber + secondNumber));        return firstNumber + secondNumber;    }}
액션 리스너 작성하기

명령버튼을 위한 액션 리스너는 JSF 애플리케이션의 가장 흥미로운 부분 중 하나이다. 이것은 이벤트가 실행되기 위해 리스너를 어떻게 일으키는지 나타낸다. 리스너는 단순히 콘솔 창에 메시지를 출력한다. 하지만 이벤트가 발생한 JSP 페이지의 컴포넌트 트리나 이벤트를 일으킨 컴포넌트의 정보를 나타내는 중요한 역할을 한다.

[리스트 4] 명령버튼을 위한 액션 리스너 (
MyActionListener.java)
package jsfApp;import java.util.Iterator;import javax.faces.component.UIComponent;import javax.faces.context.FacesContext;import javax.faces.event.ActionEvent;import javax.faces.event.ActionListener;import javax.faces.event.PhaseId;import javax.faces.tree.Tree;public class MyActionListener implements ActionListener {    public PhaseId getPhaseId() {        System.out.println("getPhaseId called");        return PhaseId.APPLY_REQUEST_VALUES;    }      public void processAction(ActionEvent event) {        System.out.println("processAction called");        // the component that triggered the action event        UIComponent component = event.getComponent();        System.out.println("The id of the component that fired the action event: "            + component.getComponentId());        // the action command        String actionCommand = event.getActionCommand();        System.out.println("Action command: " + actionCommand);          FacesContext facesContext = FacesContext.getCurrentInstance();        Tree tree                 = facesContext.getTree();        UIComponent root          = tree.getRoot();        System.out.println("----------- Component Tree -------------");        navigateComponentTree(root, 0);        System.out.println("----------------------------------------");    }      private void navigateComponentTree(UIComponent component, int level) {        Iterator children = component.getChildren();        // indent        for (int i=0; i<level; i++)            System.out.print("  ");        // print component id        System.out.println(component.getComponentId());        // navigate children        while (children.hasNext()) {            UIComponent child = (UIComponent) children.next();            navigateComponentTree(child, level + 1);        }    }}
애플리케이션 컴파일과 실행하기

애플리케이션을 컴파일하기 위해 myJSFApp/WEB-INF/classes 디렉토리로 이동한다. 윈도우에서는 아래와 같이 명령어를 타이핑해 넣으면 된다.
$ javac -classpath ../lib/jsf-api.jar;../lib/jsf-ri.jar; \    ../../../../common/lib/servlet.jar jsfApp/*.java
lib 디렉토리의 라이브러리 파일과 servlet.jar 파일을 사용해야 한다는 것에 주의하자. 톰캣에서는 홈 디렉토리의 하위 common/lib에서 servlet.jar 파일을 찾을 수 있다.

리눅스나 유닉스를 사용하고 있다면 라이브러리 파일을 구분하는 세미콜론을 변경해 줘야 한다.
$ javac -classpath ../lib/jsf-api.jar:../lib/jsf-ri.jar: \    ../../../../common/lib/servlet.jar jsfApp/*.java
톰캣을 실행시키고 다음과 같은 URL로 브라우저에서 접근한다.
 http://localhost:8080/myJSFApp/faces/adder.jsp
JSP 페이지 이름 전에 /faces/ 패턴을 사용했다는 점에 주의하자. 브라우저에서는 아래 [그림 3]과 같은 결과를 보게 될 것이다.


[그림 3] 애플리케이션 실행

콘솔 창에 다음과 같은 메시지를 보게 될 것이다.
Model Object Createdget firstNumber 0get secondNumber 0get result 0getPhaseId called
이제 입력 창에 2개의 숫자를 넣고 Add 버튼을 눌러보자. [그림 4]와 같이 브라우저는 덧셈의 결과를 보여줄 것이다.


[그림 4] 덧셈의 결과

콘솔창에 나타난 메시지가 중요하다.
get firstNumber 0get secondNumber 0processAction calledThe id of the component that fired the action event: submitButtonAction command: submit----------- Component Tree -------------null    addForm        firstNumber        secondNumber        output        submitButton----------------------------------------Set firstNumber 10Set secondNumber 20get firstNumber 10get secondNumber 20get result 30
결론

본 기사를 통해 여러분들은 JSF의 가장 중요한 특징에 대해 충분히 살펴보았을 것이다. 이벤트-드리븐 방식처럼 다른 서블릿/JSP 애플리케이션과 JSF 애플리케이션이 어떻게 다른지 잘 알게 되었을 것이다. 또한 하나의 JSP 페이지로 이루어진 아주 간단한 JSF 애플리케이션도 구축해 보았을 것이다. 더욱 중요한 사실은 액션 이벤트에 반응하는 액션 리스너를 작성했다는 사실이다.

실제 JSF 애플리케이션은 훨씬 복잡할 뿐만 아니라 종종 여러 개의 JSP 페이지를 가지고 있는 경우도 있다. 이런 경우에는 한 JSP 페이지에서 다른 JSP 페이지로 이동할 수 있어야 한다. 하지만 이에 관련된 내용은 본 기사의 범위를 넘어서는 것이므로 이에 대한 것은 다른 기사에서 논의할 생각이다.
부디 쿼니아완(Budi Kurniawan)은 인터넷과 객체지향 프로그래밍을 전문으로 하는 IT 컨설턴트로 마이크로소프트와 자바 기술 모두를 가르치고 있다.

'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] MVC 모델과 Observer 패턴  (0) 2004.04.01
[펌] 스트럿츠 이디엄  (0) 2004.04.01
[펌] Jakarta Digester 학습하기  (0) 2004.04.01
[펌] Torque프로젝트 사용기  (0) 2004.03.31
[펌] Log4J...  (0) 2004.03.30
Posted by tornado
|

[펌] 방법론

JAVA/JEE 2004. 4. 1. 10:32

소프트웨어공학의 발전사를 보면 크게 3가지의 방법론이 출현하였음을 알 수 있는데 과거 방법론이 없던 무방법론이 없던 무방법론 시대에서 구조적 분석/설계(또는 방법론), 정보공학,객체지향 방법론 들이 그것이다.

  1.구조적 방법론(Structured Methodology)

 구조적 방법론의 시작은 구조적 프로그래밍의 개념이 탄생하면서부터이고,구조적 프로그램밍의 탄생은 소프트웨어공학의 출범을 알리는 것이었다.

 1950년대에 시작한 소프트웨어 개발의 역사는 구조적이지 못한 무원칙의 상향식 개발 방식이었다. 개발자의 손이 가는대로 코딩을 해 나갔으며, 조직의 구성 또한 자신만이 알아볼수 있는 그런 것있었다. 이것은 개발 생산성의 저하와 유지보수의 어려움 등 소프트웨어를 개발하는데 있어서 거의 모든 부분에 문제점을 갖게 만들었고 결국은 "소프트웨어 위기"라는 용어의 탄생을 불려왔다

 1960년대 말 "GO TO"논쟁, 즉 GO TO 문을 쓰지 말고 구조적으로 프로그래밍을 하자라는 주장이 호응을 얻으면서, 분석과 설계도 구조적으로 하자라는 의견으로 확대되면서 구조적 방법로의 틀이 완성되어 갔다.

 구조적 프로그래밍의 개념은 다음과 같다.

  *코드가 계층적인 형식과 제한된 구조로 작성된 순서대로 순차적으로 실행함
  *알고르즘을 기술하는데 순차(sequencing),반복(iteration)구조면 충분하며,단일입구/ 단일출국의 처리구조를 가짐
  *철저한 모듈화로 추상화와 정보은닉을 이루어 프로그램의 구조를 읽기 쉽게 단순화함

 구조적 방법론의 그 특징은 다음과 같다.

  데이터 흐름 지향, 즉 프로세스 위주의 분석과 설계 방식

  모듈의 분할과 정복에 의한 하향식 설계 방식

  SDLC의 구조를 가진 폭포수 모델이 기본

  소프트웨어의 개발이 목표인 프로세스와 산출물의 구성

  데이터의 구성에 대한 설계 방안이 부족

  프로젝트 관리 및 조직, 역활 등 방법론적 다른 요소들의 정의가 없음

 위의 특성들로 구조적 방법론의 단점을 추정할 수 있는데, 이것은 가장 오래전에 규정된 방법론이며, 단순한 소프트웨어 개발을 목표로 한다는 점에서 기인한 것이다.

 2. 정보공학 방법론(Information Engineering Methodology)

  구조적 방법론은 그 후 오랫동안 소프트웨어 개발의 방법론으로 사용되어 왔지만 정보기술의 발전에 따라 새로운 방법론의 필요가 생겼다.

 1970년대 이후 소프트웨어는 기업에서도 많이 응용되었고 80년대를 거치면서 경영정 보시스템(MIS:Management Information System),의사결정지원시스템(DSS:Decision Support System),임원정보시스템(EIS:Executive Information System)등의 형태로 발전하여 갔고, 급기야 전략정보시스템(SIS:Strategic Information System)의 수준에 이르렀다.이것은 단지 소프트웨어 뿐 아니라 데이터베이스,네트워크, 운영조직 등 방대한 주변 정보기술들과 연계 되면서 정보시스템(Information System)이라는 개념의 한 요소에 포함되어 버리고 말았다. 이런 정보시스템은 그 규모도 클 뿐 아니라 구성내용도 복잡해서 개발의 조직도 커지고 개발기관도 몇 년 단위로 늘어났다. 한편으로, 정보기술은 급격히 발전해서 4GT,CASE,등의 자동화 도구, 다이어그래밍 도구의 등장으로 이것들을 활용할 필요도 생겨났다.

 80년대 중반 James Martin에 의해 정보공학(Information Enfineering)의 체계가 정리되면서 정보공학은 본격적으로 활용되었다. 정보공학 방법론의 그 특징은 다음과 같다.

 ~소프트웨어공학의 기본적 사상, 즉 하향식,모듈화, 분할과 정복, 폭포수 등은 동일 ~정보공학은 개별 소프트웨어가 아닌 기업에서 사용되는 정보시스템을 목표로 함 ~단순한 업무지원 시스템의 개발은 의미가 없고 기업의 경영전략을 창출하는 정보전략 계획(ISP:Information Strategy Planning)작업이 포함됨 ~기업의 업무처리에는 안정된 데이터베이스가 중요하므로 데이터 중심의 분석과 설계를 진행 ~CASE 등 자동화를 요구

 정보공학은 대규모 정보시스템의 개발에 가장 적합한 방법론으로 인정되어 왔으나, 경직되고 복잡한 구조로 인해 많은 단점들이 생겨났고, 때 맞춰 객체지향의 개념이 바람을 타고 정보공학의 영역을 허물어 가고 있다.

 3. 객체지향 방법론 (Object Oriented Methodology)

 모든 방법론이 그렇듯이 객체지향 방법론도 객체지향 프로그래밍으로부터 출발했다. 추상화와 캡슐화, 상속, 정보은닉 등 객체를 중심으로 프로그래밍 구조를 단순화하고 재사용 성을 강화하는 프로그래밍 방식의 유용성이 증명되면서, 분석과 설계도 객체지향의 원칙을 적용하려는것이 객체지향 분석/설계요 모든 제반환경이 결합되어 객체지향 방법론이 된 것이다.

  구조적 방법론이나 정보공학 방법론 모두 프로세스와 데이터를 분리하여 처리한다는 단점은 이를 통합하여 처리하는 객체지향의 등장에 가장 큰 배경이 되었다. 프로세스와 데이터가 분리 됨으로써 이를 분석하여 설계와 개발로 연계시키는데 많은 애로를 겪어야 하면 복잡한 기법들이 활용되어야 한다.또한 완성된 시스템에 대한 요구사항의 확인 결과를 너무 늦게 확인할 수 있다는 점, 산출물의 양과 복잡도가 크다는 점 등 여러가지 문제를 객체지향 개발 방법론에서 해결하고 있다.
객체지향 방법론의 특징은 다음과 같다.

 ~반복적, 그리고 점증적인(Iterative and Incremental)개발 방식 ~재사용성의 강조 ~쉽고 표준화된 표기법 존재 ~분산객체 기술의 완벽한 지원 ~OODBMS의 원활한 연계 등 아직 개선의 여지가 많음

 최근에 많은 프로젝트들이 점차 객체지향 방법론으로 진행되고 있으며, 객체지향 모델링 언어의 표준이 UML(Unified Modeling Language)를 기반으로 RUP(Rational Unified Process) 등 상용 방법론들이 등장하여 활용되고 있다.

 3.3방법론간의 비교 이 외에도 기술의 발전에 따라 RAD(Rapid Appilcation Development)방법론 클라이언트/ 서버(Client/Server) 방법론, 패키지(Package) 개발 방법론,웹(Web) 개발 방법론 등 여려 방법론 유형들이 나올 수 있지만 아무래도 위의 3가지가 가장 큰 주류라 하겠다.또한 최근 들어 컴포넌트 기반 개발(CBD:Component Based Development)방식이 큰 반향을 얻고 있고, 조만간 표준화된 방법론도 등장(이미 여러 방법론들이 나와 있지만)할 것으로 보여 컴포넌트 방법론이 제 4의 비교 대상에 올려야 할지도 모르겠다.

 

구조적 방법론

정보공학 방법론

객체지향 방법론

  프로세스 모델링 중심

  데이터 모델링 중심

  데이터 프로세스를 함께 모델링

  모듈화가 관건
  일부 모듈의 재사용 가능

  엔티디 식별이 관견
  데이터의 재사용 기능

  객체이 식별이 관건
  거의 모든 것이 재사용됨

  프로그래밍 기법에 치우침

  기업의 전략 측면 중시, 산출

  기업의 전략 측면 포함

  비정형적 접근 방식, 잘 연계되지 않음   소규모 프로젝트 중심

  물 중심
  구조적인 연계
  대규모 프로젝트 중심

  모든 단계가 Seamless하게 연결
  모든 프로젝트에 적합

프로그래머 중심

  분석가 중심

  분석가/설계자/프로그래머와의 협동중심

Posted by tornado
|

소프트웨어 개발 방법론 - Software Development Methodology

 

방법론란 소프트웨어를 개발하기 위해 개발조직환경과 소프트웨어 및 시스템을 사용할 사용자의 환경에 적합한 소프트웨어 개발 방법을 의미한다. 소프트웨어 개발방법은 소프트웨어 공학의 대가에 의해 소프트웨어 개발방법론이라는 학문형태로 자리를 잡고 있다. 따라서 방법이라는 것은 일반적으로 개발 방법론이라 볼 수 있다. 세계적인 소프트웨어 개발방법론은 크게 구조적 분석 및 설계(Structured A/D ), 정보공학(Information Engineering), 그리고 최근에 각광받고 있는 객체지향 방법론(Object_ OrientedMethodology)이 있다. 실제적으로 보면 현재 기업이나 조직에서 사용하는 방법론은 이들을 적절하게 혼합하여 사용하고 있다. 구조적 방법론의 프로세스모델링과 정보공학의 데이타 모델링을 동시에 사용하고 있다. 이는 조직의 환경이나 개발환경에 맞게 적절하게 기본적인 방법론을 변형시켜 적용시키는 것으로 볼 수 있다.

 

  • 구조적분석 및 설계(Structured A/D): 1970년대 중반 ~ 1980년대 중반
  • 정보공학(Information Engineering) : 1980년대 후반 ~ 1990년대 중반
  • 객체지향 방법론(Object_ OrientedMethodology): 1990년대 초반 ~ 현재

 

구조적 분석 및 설계

구조적 방법론은 1970년대 이후 현재 까지 가장 널리 사용되어오던 방법론으로 구조적 분석, 구조적 설계, 구조적 프로그래밍으로 이루어져 있다. 구조적 방법론은 하향식 기능 분할(Fuctional Decomposition)에 기반 둔 프로세스 중심의 방법론으로 단순한 업무처리 시스템의 개발에는 효과적이지만 데이터 분석 및 설계 부분이 취약하며, 분석, 설계, 구현에서 상의한 모델을 사용하므로 단계 간의 변환에 많은 노력이 소요된다. 따라서 대규모의 복잡한 시스템 개발에는 적합하지 않다.

구조적 방법론은 크게 자료구조 중심적 방법론자료흐름 중심적 방법론로 분류할 수 있다("객체지향소프트웨어공학", 최영근/허계범).
데이터 구조적 방법은 업무나 기능에 의한 데이터의 변환에 관점을 둔다. 반면에 처리지향 방법은 기능을 분할하는 방법으로 문제를 독립적으로 분해하여 재검토되어 진다. 데이터 구조적 방법론이 문제에 관심을 둔것이라면 처리 지향적 벙법론은 해결에 촛점을 맞춘 것이라 할 수 있다. 구조적 방법론을 지원하고 있는 기법은 대표적으로
DFD(Data Flow Diagram), DD(Data Dictionary), Mini- Spec(Mini-Specification)이 있다.

 

정보공학

정보공학 방법론은 1980년대 후반에 조직 전반에 걸친 정보 모형화 전략을 채택하며 대두되었다. 정보공학 방법론은 전사적인 차원에서 정보시스템 기획을 통하여 단위시스템을 식별하고  이를 CASE 도구를 이용하여 분석, 설계, 구현하는 방법론으로 기획단계가 대폭 강화되고 CASE를 이용한 생산성 향상 등의 장점으로 인하여 대규모의 시스템 개발에 적합하다. 즉 전체의 계획과 모델 작성을 하고 이에따라 연관관계를 가지고 각각의 시스템을 개발한다. 그러나 정보공학 방법론은 복잡한 시스템의 모델링에 부적합하여 현재는 객체지향 방법론으로 대치되고 있는 추세이다.

정보공학 방법을 지원하고 있는 기법은 대표적으로 ERD(Entity Relation Diagram), PHD(Process Hierachy Diagram), 프로세스대엔티티 Matrix 등이다. 각 기법에 대한 자세한 사항은 기법을 참조한다. 이러한 정보공학의 특징은 아래와 같다.("객체지향소프트웨어공학", 최영근/허계범)

 

정보공학은 전체를 고려하여 시스템을 개발할 수 있도록 돕는다.
정보공학은 정보전략계획, 업무영역분석, 시스템설계, 제작, 구현의 단계를 통하여 하향식으로 개발한다.
정보공학은 각 단계에 맞춰 기업의 정보, 데이터모델, 프로세스모델, 시스템 설계에 대한 정보를 리파지토리에 축적할 수 있도록 돕니다.
정보공학은 기업전체의 정보시스템화를 실현하기 위해 틀을 만들고, 개개의 시스템은 그 틀에 맞춰 개발된다.

 

객체지향 방법론

소프트웨어와 관련된 방법론, 도구 등은 우수한 소프트웨어 구현을 위하여 지금까지 많이 연구개발되어 왔다. 그러나 현재 소프트웨어에 있어 가장 심각한 문제점은 수요가 확대되는 반면에 공급이 부족하여 소프트웨어 개발이 수요를 따라가지 못하고 있으며, 유지보수 비용의 비약적인 증가로 소프트웨어 개발 비용이 급증하고 있다는 것이다. 이러한 문제점은 근본적으로 우수한 소프트웨어 개발방법이 없었다고 할 수 있다. 이러한 문제점을 해결하기 위향 제안된 방법론이 객체지향적 접근법이다. 객체지향 접근법은 소프트웨어의 확장이나 변화를 용이하게 해줄 뿐만 아니라 기존 소프트웨어의 재사용성을 증가시켜 준다. 객체지향 시스템에서는 데이터를 다루는 프로시저를 하나로 묶어 객체라는 개념을 사용하여 실세계를 표현하고 모델링한다. 객체지향의 개념은 객체(Object), 메시지(Message), 상속(Inheritance)의 세가지 요소가 근간이 된다. 실세계의 엔티티들은 시스템 내에서 객체로 모델링되어 각 엔티티들은 인스턴스로 표현되고 공통된 특성을 가진 인스턴스들이 모여 하나의 클래스로 구성하게 된다. 즉, 객체지향 방법은 시스템을 연산과 데이터를 함께 갖는 객체의 집합체로 보고, 문제영역에서 데이터와 함수를 분리할 수 없는 것으로 취급한다. 함수들과 함수의 처리 대상이 되는 데이터는 문제 범위내에 있는 객체로 취급함으로써 문제 영역의 엔티티가 모델 세계로의 객체로 직접 사상이 가능하기 때문에 응용 영역의 구조와 밀접하게 대응하여 개발 비용의 감소를 가져오며, 시스템을 쉽게 이해할 수 있다. 객체지향 방법론을 발전시켜온 삼인방(Booch, Rumbaugh, Jacobson)에 의해 90년대 중반에 표기법을 통일되었는데, 최근에 객체지향 하면 떠오르는 것이 UML(Unified Modeling Language)이다. 이는 객체지향 방법론이 아닌 이를 지원하는 표기법(Modeling Language)이다. 객체지향을 지원하고 있는 기법으로는 Class Diagram, Use Case Diagram 외에 6개정도의 UML표기법에 의한 다이어램이 있다. Class Diagram과 Use Case Diagram에 대한 사항은 기법(Technique)을 참조한다.

 

이상으로 구조적 방법, 정보공학, 객체지향 방법을 서로 비교해 보면 아래 표와 같다("객체지향소프트웨어공학", 최영근/허계범)

 

 
정보공학 방법론
객체지향 방법론
분석
1.현 물리적 모형화
2.현 논리적 모형화
3.신논리적 모형화
4.구조적 설계
1.업무 문맥 결정
2.업무기능 파악
3.업무 결과 모형화

정보전략계획
데이터모델 작성
액티비티 모델 작성
현행시스템작성
현행시스템분석
확인
업무시스템정의

문제 정의
요구 명세화
객체와 객체특성 추출
객체의 속성 식별
객체의 연산 식별
객체의 연관성 정의
설계
1.상세 DFD
2.자료흐름 유형변환
3.자료흐름 트랜잭션
4.개략적 프로그램구조도
5.프로그램구조도 평가 및개선
6.검토
7.상세설계
1.데이터 구조 특성평가
2.데이터 기본형태 표현
3.자료구조를 SW제어계층구조로 Mapping
4.SW 계층구조 개선
5.SW프로시저 기술
1. 프로시저 정의
2.다이얼로그 설계
레이아웃설계
4.온라인 대화의 프로토타이핑
5. 프로시저 로직 설계
1. 객체의 인터페이스설정
2.객체들간의 가시성설정
접근방법
Top_DownTop_DownBottom_UpBottom_Up
DBMS
전통적인DB와 RDB전통적인DB와 RDB전통적인DB와 RDB
CASE의 저장소
전통적인 DB와 OODB
CASE
상위레벨(다이어그램)상위레벨(다이어그램)통합레벨지원통합레벨지원
방법제시자
YourdonJackson
Warnier_Orr
MartinBooch
Rumbaugh
Jacobson외

 

Posted by tornado
|

Jakarta Digester 학습하기

등록일: 2002년 11월 05일

저자: 필립(Philipp K. Janert Ph.D), 역 김대곤

XML문서를 문서와 대응되는 계층구조를 가진 자바 빈 객체로 변환하는 것은 분명 빈번하게 요구되는 작업 중의 하나이다. 필자가 이전에 논했었던 'Simple XML Parsing with SAX and DOM'라는 기사에서 SAX와 DOM이라는 표준 API를 사용하여 이러한 작업을 수행하는 방법에 대해 기술하였다.

SAX과 DOM이 강력하고, 유연함에도 불구하고, 실제로 이 두 가지 API는 특정한 작업을 간단하게 수행하기에는 너무 저수준인 API이다. 뿐만 아니라 unmarshalling 작업(XML문서를 객체로 변환하는 작업) 자체에 상당한 코딩을 해주어야 한다. 즉, SAX를 사용할 때에는 반드시 parse-stack이 관리되어야 하며, DOM를 사용할 때에는 반드시 DOM-tree를 사용해야 한다는 말이다.

Unmarshalling 작업은 Apache Jakarta Commons Digester 프레임워크가 해결할 수 있는 부분이다.

Jarkarta Digester 프레임워크

Jakarta Digester 프레임워크는 Jakarta Struts Web toolkit의 일부로 시작하여 발전되었다. 원래는 주요한 struts-config.xml를 처리하기 위해 개발되었으나 보다 일반적으로 유용하다는 것이 인식되어, Jakarta Commons 프로젝트로 옮기게 되었다. 이 프로젝트는 재사용 가능한 자바 컴포넌트의 리포지토리(repository)를 제공하는 것을 목표로 하고 있다. 최신 버전인 Digester1.3이 2000년 8월 13일에 발표되었다.

Digester 클래스를 통해 애플리케이션 프로그래머는 파서가 XML 문서의 간단한 특정 패턴을 만났을 때, 필요한 작업이 자동적으로 수행되게 해준다. Digester 프레임워크는 미리 정의된 10개의 규칙을 가지고 있다. 이러한 규칙들은 XML문서를 unmashalling하는데 필요한 대부분의 작업들을 포함하고 있지만 필요할 때마다 각 사용자들이 직접 규칙을 정의할 수도 있게 되어있다.

예제 문서와 빈즈

이제 보게 될 예제에서는 이전 기사에서 사용한 XML 문서을 unmarshall할 것이다.
<?xml version="1.0"?><catalog library="somewhere">   <book>      <author>Author 1</author>      <title>Title 1</title>   </book>   <book>      <author>Author 2</author>      <title>His One Book</title>   </book>   <magazine>      <name>Mag Title 1</name>      <article page="5">         <headline>Some Headline</headline>      </article>      <article page="9">         <headline>Another Headline</headline>      </article>   </magazine>   <book>      <author>Author 2</author>      <title>His Other Book</title>   </book>   <magazine>      <name>Mag Title 2</name>      <article page="17">         <headline>Second Headline</headline>      </article>   </magazine></catalog>
자바 빈(Java Bean) 클래스도 한가지 주요한 변화를 제외하고는 XML 파일과 동일하다. 이전 기사에서 빈 클래스들을 하나의 소스 파일로 정의하기 위해 package를 선언하였다. 그렇지만 Digester 프레임워크를 사용할 때에는 이러한 방법을 사용할 수 없다. 모든 클래스는 public으로 선언되어야 한다. public으로 선언하는 것은 자바 빈즈의 규약에서도 요구되는 부분이다.
import java.util.Vector;public class Catalog {   private Vector books;   private Vector magazines;   public Catalog() {      books = new Vector();      magazines = new Vector();   }   public void addBook( Book rhs ) {      books.addElement( rhs );   }   public void addMagazine( Magazine rhs ) {      magazines.addElement( rhs );   }   public String toString() {      String newline = System.getProperty( "line.separator" );      StringBuffer buf = new StringBuffer();      buf.append( "--- Books ---" ).append( newline );      for( int i=0; i < books.size(); i++ ){         buf.append( books.elementAt(i) ).append( newline );      }      buf.append( "--- Magazines ---" ).append( newline );      for( int i=0; i < magazines.size(); i++ ){         buf.append( magazines.elementAt(i) ).append( newline );      }      return buf.toString();   }}
public class Book { private String author; private String title; public Book() {} public void setAuthor( String rhs ) { author = rhs; } public void setTitle( String rhs ) { title = rhs; } public String toString() { return "Book: Author='" + author + "' Title='" + title + "'"; }}
import java.util.Vector;public class Magazine { private String name; private Vector articles; public Magazine() { articles = new Vector(); } public void setName( String rhs ) { name = rhs; } public void addArticle( Article a ) { articles.addElement( a ); } public String toString() { StringBuffer buf = new StringBuffer( "Magazine: Name='" + name + "' "); for( int i=0; i < articles.size(); i++ ){ buf.append( articles.elementAt(i).toString() ); } return buf.toString(); }}
public class Article { private String headline; private String page; public Article() {} public void setHeadline( String rhs ) { headline = rhs; } public void setPage( String rhs ) { page = rhs; } public String toString() { return "Article: Headline='" + headline + "' on page='" + page + "' "; }}
패턴과 규칙

Digester 클래스는 패턴(pattern)과 규칙(rule)을 기준으로 입력되는 XML 문서를 처리한다. 패턴들은 XML 문서 Tree 구조의 이름과 위치에 근거해서 XML Element와 일치해야 한다. 매칭 패턴을 정의하는 문법은 Xpath의 매칭 패턴과 유사하다. Catalog패턴은 <catalog> Element와 일치하고, catalog/book 패턴은 <catalog> Element 안에 포함된(문서의 다른 부분에서 사용되지 않는) <book> Element와 일치한다.

모든 패턴은 절대 경로이다. 즉 루트(root) Element에서 시작되는 전체 경로가 명시되어야 한다. *(와일드카드 문자)가 사용된 패턴만이 예외로 인정된다. 즉, */name 패턴은 문서에 어느 곳에 위치한 <name> Element와 매칭될 수 있다. 또한 모든 경로가 절대경로이기 때문에 root Element를 특별히 명시할 필요가 없다.

Digester 클래스는 정의된 패턴을 만나면 패턴과 연관된 작업을 수행한다. 이때, Digester 프레임워크는 물론 SAX 파서을 사용한다. 실제로 Digester 클래스는 org.xml.sax.ContentHandler를 구현하고 있으며, parse stack를 관리하고 있다. Digester클래스에서 사용하는 모든 규칙(rule)은 org.apache.commons.digester.Rule 클래스를 상속받아야 한다. 이 Rule 클래스는 내부에서 SAX의 ContentHandler의 재귀함수(매칭된 Element가 시작 태그와 종료 태그를 만났을 때 호출되는 begin, end 메소드)와 유사한 메소드들을 사용한다.

Body() 메소드는 매칭된 Element안에 포함된 내용들을 처리하기 위해 호출된다. 마지막으로, finish() 메소드가 있는데, 이것은 종료 태그의 처리가 완료되었을 때, 필요한 마지막 정리작업을 위해 호출된다. 그러나 프레임워크에 포함된 표준 규칙들은 원하는 모든 기능들을 이미 제공하기 때문에 대부분의 애플리케이션 개발자들은 이러한 함수에 대하여 신경쓸 필요가 없다.

XML문서를 unmarshall하기 위해서는 org.apache.commons.digester.Digester 객체를 생성하고, 필요한 경우 객체을 설정하고, 처리하기 원하는 패턴과 규칙들을 정의하고, 마지막으로 XML문서에 대한 참조값을 parse() 메소드에 넘겨준다. 이러한 일련의 작업들은 아래에 있는 DigesterDriver 클래스에 나타나 있다. XML문서의 파일명은 실행시 인자로 넘겨주어야 한다.
import org.apache.commons.digester.*;import java.io.*;import java.util.*;public class DigesterDriver {   public static void main( String[] args ) {      try {         Digester digester = new Digester();         digester.setValidating( false );         digester.addObjectCreate( "catalog", Catalog.class );         digester.addObjectCreate( "catalog/book", Book.class );         digester.addBeanPropertySetter( "catalog/book/author", "author" );         digester.addBeanPropertySetter( "catalog/book/title", "title" );         digester.addSetNext( "catalog/book", "addBook" );         digester.addObjectCreate( "catalog/magazine", Magazine.class );         digester.addBeanPropertySetter( "catalog/magazine/name", "name" );         digester.addObjectCreate( "catalog/magazine/article", Article.class );         digester.addSetProperties( "catalog/magazine/article", "page", "page" );         digester.addBeanPropertySetter( "catalog/magazine/article/headline" );          digester.addSetNext( "catalog/magazine/article", "addArticle" );         digester.addSetNext( "catalog/magazine", "addMagazine" );         File input = new File( args[0] );         Catalog c = (Catalog)digester.parse( input );         System.out.println( c.toString() );      } catch( Exception exc ) {         exc.printStackTrace();      }   }}
이 예제에서는 DTD를 사용하여 XML문서의 유효성을 검사할 수 없다. Catalog 문서에 DTD를 지정하지 않았기 때문이다. 패턴과 관련된 규칙들을 직접 정의하였다. ObjectCreateRule은 지정된 클래스의 인스턴스를 생성하여 parse stack에 넘겨준다. SetPropertiesRule은 bean 속성에 현재 Element의 attribute 값을 지정한다. 이 메소드의 첫 번째 인자는 attribute의 이름이고, 두 번째 인자는 속성의 이름을 나타낸다.

SetPropertiesRule이 attribute로부터 값을 읽어온다면, BeanPropertySetterRule은 현재 Element에 안에 있는 raw character data를 가져온다. BeanPropertySetterRule를 사용할 때에는 Bean 속성의 이름을 특별히 정의할 필요가 없다. BeanPropertySetterRule은 특정한 설정이 없는 경우, Element의 이름을 사용하기 때문이다. 위의 예제에서는 catalog/magazine/article/headline에 규칙을 지정하면서 이러한 설정법을 사용하였다. 마지막으로 남은 SetNextRule은 생성한 객체를 parse stack의 처음으로 옮기고, 생성된 객체를 가진 상위 객체의 메소드로 넘겨준다. 이것은 주로 만들어진 Bean를 상위 클래스에 추가할 때 사용된다.

동일한 패턴에 대해 다수의 규칙을 적용할 수도 있다. 이때에는 Digester 클래스에 입력된 순서대로 규칙이 실행될 것이다. 예를 들어 위 프로그램에서 catalog/magazine/article<article> Element를 다룬 것을 살펴보면 알맞은 Bean 객체를 먼저 생성하고, page의 속성값을 지정하고, 마지막으로 생성된 객체를 상위 객체인 magazine에 추가하였다는 것을 알 수 있다.


Java & XML Data Binding

참고 도서

Java & XML Data Binding
Brett McLaughlin




임의의 함수 호출

Bean의 속성을 설정하는 것 뿐만 아니라 객체에 정의된 임의의 메소드를 호출할 수도 있다. 이것은 CallMethodRule를 사용하여 메소드의 이름과 필요시 인자의 수와 속성을 넘겨줌으로써 가능하다. CallParamRule를 통해 호출되는 메소드에 파라미터 값을 전달할 수 있다. 이 값은 Element의 속성값, 또는 Element의 데이터에서 가져올 수 있다. 예를 들어 위의 예제에서 BeanPropertySetterRule를 사용하기 보다는, 데이터를 파라미터로 넘기면서, 속성 setter 메소드를 직접 호출하여 동일한 작업을 수행할 수 있다.
digester.addCallMethod( "catalog/book/author", "setAuthor", 1 );digester.addCallParam( "catalog/book/author", 0 );
첫번째 라인은 호출할 메소드의 이름과 전달되는 인자의 수를 지정하고, 두 번째 라인은 <author> Element의 데이터를 함수의 인자값으로 사용하고, 인자의 array의 첫번째 값으로 전달함을 의미한다. 이 때 Element의 attribute값을 인자로 넘길 수 있다. (digester.addCallParam("catalog/book/author", 0, "author");)

여기서 주의할 점이 하나 있다. Digester.addCallMethod("pattern", "methodName", 0)이 전달되는 인자가 없다는 의미가 아니라는 것이다. 이것은 호출되는 메소드의 인자값을 현재 Element의 데이터 값을 사용하겠다는 의미이다. 그러므로 위의 예제에서 사용한 BeanPropertySetterRule을 다음과 같이 변환할 수 있다.
digester.addCallMethod( "catalog/book/author", "setAuthor", 0 );
인자를 취하는 않는 메소드를 호출하는 경우에는 digester.addCallMethod("pattern", "methodName")를 사용한다.

표준 규칙 요약

다음은 표준 규칙에 대한 간단한 설명이다.

Creational
  • ObjectCreateRule: 객체의 기본 생성자를 사용하여 객체를 생성하고, 이를 stack에 넘겨준다. Element의 처리가 끝날 때 객체는 반환된다. 객체를 지정하는 방법은 Class 객체를 사용하는 법과 전체 클래스의 이름을 사용하는 법이 있다.
  • FactoryCreateRule: 특정 Factory 객체를 사용하여 객체를 생성하고, 이를 스택에 넘겨준다. 기본 생성자를 제공하지 않는 클래스를 사용할 때 유용하다. 이 Factory 클래스는 org.apache.commons.digester.ObjectCreationFactory 인터페이스를 구현해야 한다.
Property Setters
  • SetPropertiesRule: Element의 속성값을 사용하여 top-level 수준의 하나 또는 여러 개의 속성값을 지정한다. Attribute의 값과 속성의 값은 String 배열을 사용하여 전달한다.
  • BeanPropertySetterRule: Element의 데이터를 top-level 수준의 속성값을 지정한다.
  • SetPropertyRule: 현재 XML Element의 attribute과 attribute의 값을 Bean의 속성과 속성값으로 지정한다.
Parent/Child Management
  • SetNextRule: 생성한 객체를 stack에 넘겨주고, 상위 객체에 정의된 동일한 이름의 메소드를 호출한다. 일반적으로 완성된 객체를 상위 객체에 추가할 때 사용한다.
  • SetTopRule: stack의 second-to-top 객체를 top-level 객체로 전달한다. 이것은 Child 객체에서 setParent 메소드가 사용될 경우에 유용하게 사용된다.
Arbitrary Method Calls
  • CallMethodRule: top-level Bean의 임의의 메소드를 호출한다. 호출되는 메소드의 인자의 수는 제한이 없다. CallParamRule에 의해 인자의 값이 전달된다.
  • CallParamRule: 메소드의 인자의 값을 표시한다. 인자의 값은 Element의 attribute나 데이터로부터 얻어올 수 있다. 이 규칙은 정수 인덱스로 지정되는 인자의 위치를 요구한다.
XML에서의 규칙: xmlrules 패키지 사용하기

지금까지 패턴과 규칙을 컴파일 시점에 설정하는 것에 대하여 살펴보았다. 개념적으로는 간단하고, 직접적으로 보이지만 이러한 방법은 불편하다. 전체 프레임워크는 실행시 각 구조와 데이터를 인식하고 처리하기 위한 것인데, 위의 예제에서는 컴파일 시점에서 이미 구조와 데이터 인식 방법이 결정되고 있기 때문이다. 이러한 방식은 많은 코드를 필요로 하며, 프로그램이라기보다는 설정에 가깝다.

org.apahce.commons.digester.xmlrules 패키지는 위에서 언급한 문제를 다루고 있다. DigesterLoader 클래스는 패턴/규칙이 정의된 XML문서를 이용하여, Digester 객체를 생성한다. Digester 클래스를 설정하는 XML파일은 xmlrules 패키지에 포함된 digester-rules.dtd에 유효한 문서여야 한다. 즉, dtd의 규약대로 작성되어야 한다.

아래는 예제 프로그램을 위한 Digester 클래스 설정 XML파일이다. 몇 가지를 살펴보자.

패턴은 두 가지 방법으로 설정될 수 있다. 첫째 규칙을 나타내는 XML의 attribute로 표시될 수 있고, 둘째 <pattern> Element로 정의될 수 있다. 두 번째 설정방법은 규칙 Element가 포함된 모든 곳에서는 모두 사용할 수 있다. 두 방식은 혼합해서 사용할 수 있으며, <pattern> Element안에 <pattern> Element가 올 수 있다.

<set-properties-rules> Element안에 사용된 <alias> Element는 XML attribute와 Bean의 속성을 매핑할 때 사용한다.

마지막으로, 현재의 Digester 패키지에서는 설정파일에서 SetPropertySetterRule를 사용할 수 없다. 대신, CallMehtodRule를 사용하여 동일한 작업을 수행할 수 있다.
<?xml version="1.0"?><digester-rules>   <object-create-rule pattern="catalog" classname="Catalog" />   <set-properties-rule pattern="catalog" >      <alias attr-name="library" prop-name="library" />   </set-properties-rule>   <pattern value="catalog/book">      <object-create-rule classname="Book" />      <call-method-rule pattern="author" methodname="setAuthor"	                paramcount="0" />      <call-method-rule pattern="title" methodname="setTitle" 	                paramcount="0" />      <set-next-rule methodname="addBook" />   </pattern>   <pattern value="catalog/magazine">      <object-create-rule classname="Magazine" />      <call-method-rule pattern="name" methodname="setName" paramcount="0" />      <pattern value="article">         <object-create-rule classname="Article" />         <set-properties-rule>            <alias attr-name="page" prop-name="page" />         </set-properties-rule>         <call-method-rule pattern="headline" methodname="setHeadline" 		           paramcount="0" />         <set-next-rule methodname="addArticle" />      </pattern>      <set-next-rule methodname="addMagazine" />   </pattern></digester-rules>
실제 작업을 Digester 클래스와 DigesterLoader에서 수행하면 DigesterDriver 클래스는 아주 단순해진다. 이제 DigesterDriver 클래스를 실행하기 위해서는 catalog XML문서와 rules.xml문서를 실행하는 순서대로 인자로 넘겨줘야 한다. 이때 rules.xml 파일은 File 클래스나 org.xml.sax.InputSource 클래스가 아닌 URL을 요구한다.
import org.apache.commons.digester.*;import org.apache.commons.digester.xmlrules.*;import java.io.*;import java.util.*;public class XmlRulesDriver {   public static void main( String[] args ) {      try {         File input = new File( args[0] );         File rules = new File( args[1] );         Digester digester = DigesterLoader.createDigester( rules.toURL() );         Catalog catalog = (Catalog)digester.parse( input );         System.out.println( catalog.toString() );        } catch( Exception exc ) {         exc.printStackTrace();      }   }}
결론

이상으로 Jakarta Commons Digester 패키지에 대한 간단한 설명을 끝내겠다. 물론 더 많은 내용이 있지만. 여기에서 다루지 않았던 하나의 주제는 XML 네임스페이스(Namespace)에 관한 것이다. Digester는 특정 네임스페이스에 속한 Element들에 패턴과 규칙을 정의할 수 있다.

우리는 위에서 Rule클래스를 사용하여 사용자가 직접 작성한 규칙을 정의할 수 있다는 사실을 살펴보았다. 또한 Digester 클래스는 사용자가 정의한 push(), peek(), pop() 메소드를 통해, 개발자로 하여금 parse stack를 직접 수정할 수 있는 기능을 제공한다.

최근에 RSS(Rich-Site-Summary)를 다루는 Digester 클래스를 제공하는 추가 패키지가 제공되었다. 이 패키지는 Javadoc을 사용하여 살펴볼 수 있다.

참고사이트
필립(Philipp K. Janert Ph.D)은 소프트웨어 프로젝트 컨설턴트이자, 서버 프로그래머와 설계자로 활동하고 있다.

'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] 스트럿츠 이디엄  (0) 2004.04.01
[펌] JavaServer Faces 입문  (0) 2004.04.01
[펌] Torque프로젝트 사용기  (0) 2004.03.31
[펌] Log4J...  (0) 2004.03.30
[펌] JSF 사용하기  (0) 2004.03.29
Posted by tornado
|
 

■Torque사용을 위한 기초작업들

http://jakarta.apache.org/builds/jakarta-turbine/torque/release/3.1/에서

torque-3.1.zip  <==실행

Torque.properties의 편집

클래스 패스는 lib안의 필요한 것(어떤 것? )에 통한다

클라이언트의 코딩과 실행

torque-gen-3.1.zip  <==생성

build.properties의 편집

JDBC 드라이버는 lib에 카피

Ant 태스크를 실행


torque-3.1.zip에 있는 lib폴더 내부의 jar파일들을 사용하는 웹폴더(즉,C:\Tomcat 5.0\webapps-test)의 C:\Tomcat 5.0\webapps-test\ROOT\WEB-INF\lib 에 저장한다. lib폴더엔 스트럿의 jar파일과 중복되는 부분이 있어 torque의 lib폴더의 jar파일의 일부만 가져와서 덮어쓴다. 그 내용은 다음과같다

avalon-framework-4.1.4.jar

commons-configuration-1.0-dev-3.20030607.194155.jar

commons-dbcp-20030825.184428.jar

commons-pool-20030825.183949.jar

jcs-20030822.182132.jar

jdbc-2.0.jar

jndi-1.2.1.jar

junit-3.8.1.jar

log4j-1.2.8.jar

logkit-1.0.1.jar

stratum-1.0-b3.jar

torque-3.1.jar

village-2.0-dev-20030825.jar

xercesImpl-2.0.2.jar

xmlParserAPIs-2.0.2.jar


C:\javatool\torque-3.1에 있는 설정파일들을 사용하는 웹폴더(C:\Tomcat 5.0\webapps-test\ROOT\WEB-INF)의 WEB-INF에 복사한다.


web.xml 에 Torque를 설정해준다.

<servlet>

      <servlet-name>initTorque</servlet-name>

<!--torque를 사용할 때 매번 초기화 하는부분을 이 설정파일(web.xml)에서 설정해준다.-->

      <servlet-class>initialize.TorqueInit</servlet-class>

      <init-param>

      <param-name>config</param-name>

<!--torque를 사용할 때 참조할 properties파일 위치 설정-->

      <param-value>/WEB-INF/classes/properties/Torque.properties</param-value>

      </init-param>

      <init-param>

         <param-name>debug</param-name>

         <param-value>0</param-value>

      </init-param>

      <load-on-startup>1</load-on-startup>

    </servlet>


 ========================================================

<!--torque를 사용할 때 매번 초기화 하는부분을 이 설정파일(web.xml)에서 설정해준다.-->

      <servlet-class>initialize.TorqueInit</servlet-class>  파일이름 : TorqueInit.java(임의로 작성)

package initialize;


/**

* @author  : Kwoen BongJe(2002-12-21)

*/


import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;


import org.apache.torque.Torque;


public class TorqueInit extends HttpServlet{

    private static boolean hasInitialized = false;


        public void init() throws ServletException{

                

                synchronized(TorqueInit.class){

                        

                        if(!hasInitialized){

                                try{

                                        String propFileName = getServletConfig().getInitParameter("config");

                                if(propFileName==null) {

                                   propFileName="/WEB-INF/classes/properties/Torque.properties";

                                }

                                String propFile = getServletContext().getRealPath(propFileName);

                                        Torque.init(propFile);

                                        hasInitialized = true;

                                }catch(Exception e){

                                        throw new ServletException("\nComstar.framework : Can't initialize Torque!\n"+e);

                                }

                        }

                }

                

        }

}

 

===================================================

<!--torque를 사용할 때 참조할 properties파일 위치 설정-->

      <param-value>/WEB-INF/classes/properties/Torque.properties</param-value> 프로퍼티 파일을 사용환경에 맞게 변경해주어야 한다. 아래는 예제파일


 

torque.applicationRoot = /usr/local/tomcat

log4j.category.org.apache.torque = ALL, org.apache.torque

log4j.appender.org.apache.torque = org.apache.log4j.FileAppender

log4j.appender.org.apache.torque.file = ${torque.applicationRoot}/logs/testdb/torque.log

log4j.appender.org.apache.torque.layout = org.apache.log4j.PatternLayout

log4j.appender.org.apache.torque.layout.conversionPattern = %d [%t] %-5p %c - %m%n

log4j.appender.org.apache.torque.append = false


torque.defaults.pool.logInterval = 0

torque.defaults.pool.connectionWaitTimeout = 10

torque.defaults.pool.defaultMaxConnections = 80

torque.defaults.pool.maxExpiryTime = 3600


torque.defaults.connection.driver = org.gjt.mm.mysql.Driver

torque.defaults.connection.url = jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=euc-kr

torque.defaults.connection.user = test

torque.defaults.connection.password = test


torque.database.default=testdb

torque.database.testdb.adapter=mysql


torque.dsfactory.testdb.factory=org.apache.torque.dsfactory.SharedPoolDataSourceFactory

torque.dsfactory.testdb.pool.defaultMaxActive=10

torque.dsfactory.testdb.pool.testOnBorrow=true

torque.dsfactory.testdb.pool.validationQuery=SELECT 1

torque.dsfactory.testdb.connection.driver = org.gjt.mm.mysql.Driver

torque.dsfactory.testdb.connection.url = jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=euc-kr

torque.dsfactory.testdb.connection.user = test

torque.dsfactory.testdb.connection.password = test


torque.idbroker.cleverquantity=true

torque.manager.useCache = true


======

 

설치한 torque-gen폴더 아래에 C:\javatool\torque-gen-3.1 torque가 사용될 폴더를 만들어준다 폴더이름은 임으로 만들지만 프로젝트 명으로 하는 것이 좋겠다.


C:\javatool\torque-gen-3.1\webapps-address 아래에 schema 폴더를 만든다.


schema 폴더에 사용할 DB에서의 사용할 태이블 구조를 xml형식으로 만든다

이때 파일 이름의 형식은 “*-schema.xml”의 형식을 따라야 한다. 이는 C:\javatool\torque-gen-3.1에 있는build-torque.xml파일을 torque가 참조한다. build-torque.xml파일에는 DB를 생성과 참조 등등이 정의 되어있다.

참고자료 : http://db.apache.org/torque-31/generator/schema-reference.html

<예>

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

<!DOCTYPE database SYSTEM "http://db.apache.org/torque/dtd/database_3_1.dtd">


<!-- ==================================================================== -->

<!--                                                                      -->

<!-- I D  B R O K E R  S C H E M A                                        -->

<!--                                                                      -->

<!-- ==================================================================== -->

<!-- This is the XML schema use by Torque to generate the SQL for         -->

<!-- ID_TABLE table used by the id broker mechanism in Torque.            -->

<!-- ==================================================================== -->

<!-- @author: <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>       -->

<!-- @version $Id: id-table-schema.xml,v 1.2 2003/07/24 12:40:41 mpoeschl Exp $ -->

<!-- ==================================================================== -->


<database name="struts-address">

'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] JavaServer Faces 입문  (0) 2004.04.01
[펌] Jakarta Digester 학습하기  (0) 2004.04.01
[펌] Log4J...  (0) 2004.03.30
[펌] JSF 사용하기  (0) 2004.03.29
[펌] Struts, Tiles, JavaServer Faces 통합하기  (0) 2004.03.29
Posted by tornado
|

[펌] Log4J...

JAVA/JSP_Servlet 2004. 3. 30. 14:11

베낌 + 모방  + 편집.. ^^

 

/**
 * Created on 2004. 3. 05.
 *
 *
 * @author 짱가
 *
 * *******************************************************
 *                             코드수정히스토리
 * 날짜                               작업자                      내용
 * 2004. 3. 3.                       짱가
 * *******************************************************
 *
 */
/**
 * System.out.println("[WARN]  : " +  e.toSting()  );
 * 이런식으로 코딩해본적 있을것이다. 혹시라도 로그를 빼달라고 하면 모두들
 * System.out.println("[WARN] : " +  e.toSting()  ); //라고 해본사람들이 꽤될것이다.
 * 컴파일 했다가 말았다가 어느게 시스템 Debug용이고 어느게 뭔지 .....
 * 프로그래밍이 끝나고 운용에 들어가면서 속도 향상을 위해 클라이언트가 FATAL에러만 빼고 모두 빼달라고 했다던지
 * 혹은 운용중에 에러를 잡기위해 어느단계까지는 모두 나오게 해야한다던지 할때 이런기능이 없다면
 * 아마 소스코드는 IF문과 System.out.println() 으로 뒤범벅이 될것이다.
 *
 *
 * 로그레벨이라는 개념을 사용하면 약간 수고를 덜수 있다.
 * DEBUG < INFO < WARN < ERROR < FATAL
 * 보통 로그level에서 DEBUG가 가장작고 FATAL이 가장크다. 그래서 위의 예제 결과는 아래와 같이 나온다.
 * 결과를 봐서 알겠지만 예제에서 WORN을 LogLevel로 삼았기 때문에 위의 그림과 같게 아래의 결과가 나온다.
 * java.lang.Object
 *|
 * +--org.apache.log4j.Category
 *       |
 *       +--org.apache.log4j.Logger
 * 보는 바와 같이 Logger class는 Category의 child class였다.
 *
 *
 *
 *
 * API의 일부....
 *
 * Constructor Summary
 * protected Logger(String name)
 *
 * Method Summary
 *
 * 1) static Logger getLogger(Class clazz) --- Same as calling getLogger(clazz.getName()).
 *
 * 2) static Logger getLogger(String name) --- Retrieve a logger by name.
 *
 * 3) static Logger getLogger(String name, LoggerFactory factory) ---Like getLogger(String)
 *     except that the type of logger instantiated depends on the type
 *     returned by the LoggerFactory.makeNewLoggerInstance(java.lang.String) method of the factory parameter.
 *
 * 4) static Logger getRootLogger() --- Retrieve the root logger. 
 *
 * ----------------------------------------------------------------------------------------------------
 * 1)과 2번은 클라스로 근본적으로 같고 예를들면 Logger.getLogger( xxxx.class.getName( ) )
 * 이런식으로 쓰므로 1번과 2번은 근본적으로 같다고 볼수 있다.
 * 3)은 LoggerFactory에 의해 불리우는것에 따라 logger type이 달라지고....
 * 4)째는 모든 Logger는 부모가 있는데 이 부모logger를 사용가능하지만 별로 권장하지 않는다고 한다.
 * http://www.onjava.com/lpt/a/2525 을 참조하기 바란다.
 * 이글에 보면 Tomcat에서의 사용법까지 나와있으며 어떻게 로그를 남기는것이 효율적인지에 대하여 적고 있다.
 *--------------------------------------------------------------------------------------
 *
 * logger class는 Category 클라스의 setLevel( )을 호출하게 되면 level이 정해진다.
 * http://logging.apache.org/log4j/docs/api/org/apache/log4j/Level.html 에서 보는바와같이 level의 종류는
 * 코드:
 * static Level ALL
 * static Level DEBUG
 * static Level ERROR
 * static Level FATAL
 * static Level INFO
 * static Level OFF
 * static Level WARN 와같다.
 * http://logging.apache.org/log4j/docs/api/org/apache/log4j/Category.html
 * 실제 로그를 뿌리는 메서드들을 발견할수 있을것이다. 
 *

 * Log4j 는 기본적으로 다섯개의 우선권(이하 Priority) 등급으로 메세지를 로깅할 수 있다.
 * 1. 완성된 어플리케이션에서는 출력되지 않아야 할 디버깅 메세지들을 쓰기위해 debug 를 사용하라.
 * 2. 어플리케이션의 verbose 모드에서 출력될만한 메세지들을 로깅하기 위해 info 를 사용하라. 
 * 3. 어플리케이션이 이상없이 계속 실행될 수 있는 정도의 경고메세지를 로깅하기 위해 warn 을 사용하라.
 * 4. 어플리케이션이 그럭저럭 돌아갈만한 정도의 에러베세지를 로깅하기 위해 error 를 사용하라.
   예를들어 관리자에 의해 주어진 설정인자가 올바르지 않아 하드코딩된 기본값을 사용해야 할 경우.
 * 5. 로깅후에 애플리케이션이 비정상적으로 종료될 치명적인 메세지를 로깅하기 위해 fatal 을 사용하라. 

 *****************************************************************************
 *
 *  여기서 작성한 클래스는 각 기능 별로 txt로 생성하는 로그
 *                                          html로 생성하는 로그
 *           pattern matching을 사용하는 로그
 *            xml로 생성하는 로그
 *           그리고 configuration file로 xml을 사용하여 log파일로 생성하고
 *           그 log파일이 일정 사이즈가 될때 증가시키는 로그로 나뉘어져 있다
 *  참고로 각 실행시에 main으로 실행한후 메소드 명만 수정하였으므로 실행은 그렇게 하면 실행이 가능하다.
 *
 *****************************************************************************
 *
 */

import java.io.FileOutputStream;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.HTMLLayout;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.SimpleLayout;
import org.apache.log4j.WriterAppender;
import org.apache.log4j.xml.DOMConfigurator;
import org.apache.log4j.xml.XMLLayout;

public class Logging4J {

 //  Logger.getInstance()의 실체는 Category.getInstance()이며,
 //  Category가 반환되기때문이다. 그리고 이 Category 클래스나 getInstance()
 //     메소드는 추천 되지 않는 API인것으로 알려져있다.
 //  Logger의 취득은 Logger.getLogger()로 하기 바란다
 static Logger logger = Logger.getLogger(Logging4J.class);

 public void consoleLog() {

  Logger logger = Logger.getLogger("loggerTest");
  BasicConfigurator.configure();
  logger.setLevel(Level.WARN);
  logger.debug("this is debug!!!");
  logger.info("this is info!!!");
  logger.warn("this is warn!!!");
  logger.error("this is error!!!");
  logger.fatal("this is fatal!!!");

 }
 /**
  * 생성 로그  txt
  * DEBUG - debug!!!
  * INFO - info!!!
  * WARN - warn!!!
  * ERROR - error!!!
  * FATAL - fatal!!!     *
  */
 public void txtLog() {

  SimpleLayout layout = new SimpleLayout();

  FileAppender appender = null;
  try {
   appender = new FileAppender(layout, "Logging4J.txt", false);
  } catch (Exception e) {
  }

  //  FileAppender(Layout layout, String filename)  : constructor
  //  FileAppender(Layout layout, String filename, boolean append)

  logger.addAppender(appender);

  logger.setLevel(Level.DEBUG);
  logger.debug("debug!!!");
  logger.info("info!!!");
  logger.warn("warn!!!");
  logger.error("error!!!");
  logger.fatal("fatal!!!");
 }
 // html은 너무 길다.. ^^;;
 public void htmlLog() {

  HTMLLayout layout = new HTMLLayout();

  WriterAppender appender = null;
  try {
   FileOutputStream output = new FileOutputStream("Logging4J.html");
   appender = new WriterAppender(layout, output);
  } catch (Exception e) {
  }

  //  FileAppender(Layout layout, String filename)  : constructor
  //  FileAppender(Layout layout, String filename, boolean append)
  //  Logger.getInstance()의 실체는 Category.getInstance()이며,
  //  Category가 반환되기때문이다. 그리고 이 Category 클래스나 getInstance() 메소드는
  //     추천 되지 않는 API인것으로 알려져있다.
  //  Logger의 취득은 Logger.getLogger()로 하기 바란다

  logger.addAppender(appender);

  logger.setLevel(Level.DEBUG);
  logger.debug("HERE is some Debug!!!");
  logger.info("HERE is some info!!!");
  logger.warn("HERE is some WARN!!!");
  logger.error("HERE is some ERROR!!!");
  logger.fatal("HERE is some FATAL!!!");
 }
 /**
  * Classname: Logging4J
  * Date in ISO8601 format: 2004-03-04 11:08:43,950
  * 이벤트 위치: Logging4J.main(Logging4J.java:194)
  * 메시지 : Here is some DEBUG
  * 
  * Classname: Logging4J
  * Date in ISO8601 format: 2004-03-04 11:08:43,950
  * 이벤트 위치: Logging4J.main(Logging4J.java:195)
  * 메시지 : Here is some INFO
  * 
  * Classname: Logging4J
  * Date in ISO8601 format: 2004-03-04 11:08:43,950
  * 이벤트 위치: Logging4J.main(Logging4J.java:196)
  * 메시지 : Here is some WARN
  * 
  * Classname: Logging4J
  * Date in ISO8601 format: 2004-03-04 11:08:43,950
  * 이벤트 위치: Logging4J.main(Logging4J.java:197)
  * 메시지 : Here is some ERROR
  * 
  * Classname: Logging4J
  * Date in ISO8601 format: 2004-03-04 11:08:43,950
  * 이벤트 위치: Logging4J.main(Logging4J.java:198)
  * 메시지 : Here is some FATAL
  *
  *
  */
 public void patternLog() {
  // Note, %n is newline
  String pattern = "Classname: %C %n";
  pattern += "Date in ISO8601 format: %d{ISO8601} %n";
  pattern += "이벤트 위치: %l %n";
  pattern += "메시지 : %m %n %n";

  PatternLayout layout = new PatternLayout(pattern);
  ConsoleAppender appender = new ConsoleAppender(layout);

  logger.addAppender(appender);
  logger.setLevel((Level) Level.DEBUG);

  logger.debug("Here is some DEBUG");
  logger.info("Here is some INFO");
  logger.warn("Here is some WARN");
  logger.error("Here is some ERROR");
  logger.fatal("Here is some FATAL");
 }
 // xml로 기록
 public void xmlLog() {
  XMLLayout layout = new XMLLayout();

  //option setting
  layout.setLocationInfo(true);

  FileAppender appender = null;
  try {
   appender = new FileAppender(layout, "Logging4J.xml", false);
  } catch (Exception e) {
  }

  logger.addAppender(appender);
  logger.setLevel((Level) Level.DEBUG);

  logger.debug("Here is some DEBUG");
  logger.info("Here is some INFO");
  logger.warn("Here is some WARN");
  logger.error("Here is some ERROR");
  logger.fatal("Here is some FATAL");
 }

 //--------참고.    http://www.vipan.com/htdocs/log4jhelp.html  --------//
 // for using xml conf

 // properties를 이용하는 것은 다루지 않았다.

 /**
  * conf file
  * <?xml version="1.0" encoding="UTF-8" ?>
  * <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
  * 
  * <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  * 
  *    <appender name="cad" class="org.apache.log4j.FileAppender">
  *       <param name="File"   value="Logging4J.xml" />
  *       <param name="Append" value="false" />
  *       <layout class="org.apache.log4j.xml.XMLLayout"/>
  *    </appender>
  * 
  *    <root>
  *       <priority value ="debug" />
  *       <appender-ref ref="cad"/>
  *    </root>
  * 
  * </log4j:configuration>
  *
  */
 public void confLog() {
  //dom conf start
  String conf = "log4jconf.xml";
  DOMConfigurator.configure(conf);
  //dom conf end

  logger.debug("Here is some DEBUG");
  logger.info("Here is some INFO");
  logger.warn("Here is some WARN");
  logger.error("Here is some ERROR");
  logger.fatal("Here is some FATAL");
 }
 //LollingFileAppender
 //log4j가 쌓는 로그의 순서
 //최근것은 항상 Logging4J.log 에 쌓이고 이것이 예에서 1000바이트가 차들어가면
 //Logging4J.log를 복사하여
 //Logging4J.log.1을 만들고 Logging4J.log에는 최근게 계속 쌓인다.
 /**
  * <?xml version="1.0" encoding="UTF-8" ?>
  * <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
  * 
  * <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  * 
  *    <appender name="cad" class="org.apache.log4j.RollingFileAppender">
  *       <!--<param name="File"   value="Logging4J.xml" />-->
  *       <param name="File"   value="Logging4J.log" />
  *       <param name="Append" value="true" />
  *       <param name="MaxFileSize" value="1000"/>
  *       <param name="MaxBackupIndex" value="3"/>
  * 
  * 
  *       <!--<layout class="org.apache.log4j.xml.XMLLayout">
  *       </layout>-->
  * 
  *       <layout class="org.apache.log4j.PatternLayout">
  *          <param name="ConversionPattern"
  *                 value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
  *       </layout>
  * 
  *    </appender>
  * 
  *    <root>
  *       <priority value ="debug" />
  *       <appender-ref ref="cad"/>
  *    </root>
  * 
  * </log4j:configuration>
  */
 public void lollingLog() {
  //dom conf start
  String conf = "log4jconfLolling.xml";
  DOMConfigurator.configure(conf);
  //dom conf end

  logger.debug("Here is some DEBUG");
  logger.info("Here is some INFO");
  logger.warn("Here is some WARN");
  logger.error("Here is some ERROR");
  logger.fatal("Here is some FATAL");
 }

 // pattern layout
 // --참고 -- http://logging.apache.org/log4j/docs/api/org/apache/log4j/PatternLayout.html
 /**
  * 출력패턴
  *
  * * ------+-------------------------------------------------------------------------------
 * %c    | 로그 이벤트의 카테고리명을 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %C    | 로깅 요구를 실시하는 클래스명을 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %d    | 로그 이벤트의 일시를 출력한다.
 *       | %d{HH:mm:ss} 나 %d{dd MMM yyyy HH}로서보다 유연하게 일시 정보를 출력할 수가 있다. 
 * ------|-------------------------------------------------------------------------------
 * %F(*) | 로그 요구가 발생한 파일명을 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %l(*) | 로그가 생성되었을 때에 불려 간 위치(소스명, 행)를 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %L(*) | 로깅 요구를 행한 행 번호를 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %m    | 로깅이벤트로 설정된 메세지를 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %M(*) | 로그 요구가 행해진 메소드명을 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %n    | 플랫폼 의존의 개행 문자를 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %p    | 로그의 우선도를 출력합니다. 
 * ------|-------------------------------------------------------------------------------
 * %r    | 어플리케이션이 개시하고 나서, 로그가 출력될 때까지의 시간을 밀리 세컨드 단위로 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %t    | 로그를 생성한 thread의 이름을 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %x    | 로그가 생성된 thread의 NDC(네스트화 진단 문맥)를 출력한다. 
 * ------|-------------------------------------------------------------------------------
 * %%    | %를 출력한다. 
 * ------+-------------------------------------------------------------------------------
 *
 *(*)이것들을 출력할 때의 퍼포먼스는 좋지 않기 때문에, 어플리케이션의 실행 속도가 문제가 되지 않는 경우에게만
 *사용하는 것이 추천 되고 있다
 *
 *
 ** 여러가지 출력포멧 : 위의 xml에 아래의 포멧들을 적용해서 이것저것 찍어보도록 하자코드:
 * ----------+-------------------------------------------------------------------------
 * %10m      | 출력 캐릭터 라인이 10 문자 이하의 경우,
 *           | 캐릭터 라인의 좌측으로 공백이 삽입된다. 
 * ----------+-------------------------------------------------------------------------
 * %-10m     | 출력 캐릭터 라인이 10 문자 이하의 경우,
 *           | 캐릭터 라인의 우측으로 공백이 삽입된다. 
 * ----------+-------------------------------------------------------------------------
 * %.10m     | 출력 캐릭터 라인이 10 문자를 넘는 경우,
 *           | 캐릭터 라인의 오른쪽으로부터 세어 11 문자눈 이후의 문자(선두의 문자)가 잘라내진다. 
 * ----------+-------------------------------------------------------------------------
 * %10.20m   | 출력 캐릭터 라인이 10 문자 이하의 경우,
 *           | 캐릭터 라인의 좌측으로 공백이 삽입된다.
 *           | 출력 캐릭터 라인이 20 문자를 넘는 경우,
 *           | 캐릭터 라인의 오른쪽으로부터 세어
 *           | 21 문자 이후의 문자(선두의 문자)가 잘라내진다. 
 * ----------+-------------------------------------------------------------------------
 * %-10.20m  | 출력 캐릭터 라인이 10 문자 이하의 경우,
 *           | 캐릭터 라인의 우측으로 공백이 삽입된다.
 *           | 출력 캐릭터 라인이 20 문자를 넘는 경우,
 *           | 캐릭터 라인의 오른쪽으로부터 세어 21 문자이후의 문자(선두의 문자)가 잘라내진다. 
 * ----------+-------------------------------------------------------------------------
 *
 *
 *  패턴 layout을 쓰는 예제 xml conf 파일코드:
 * <?xml version="1.0" encoding="UTF-8" ?>
 * <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
 *
 * <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
 *
 *    <appender name="cad" class="org.apache.log4j.RollingFileAppender">
 *       <!--<param name="File"   value="Logging4J.xml" />-->
 *       <param name="File"   value="Logging4J.log" />
 *       <param name="Append" value="true" />       
 *       <param name="MaxFileSize" value="1000"/>
 *       <param name="MaxBackupIndex" value="3"/>
 *      
 *      
 *       <!--<layout class="org.apache.log4j.xml.XMLLayout">
 *       </layout>-->
 *      
 *       <layout class="org.apache.log4j.PatternLayout">
 *          <param name="ConversionPattern"
 *                 value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/>
 *       </layout>           
 *       
 *    </appender>
 *
 *    <root>
 *       <priority value ="debug" />
 *       <appender-ref ref="cad"/>
 *    </root>
 *
 * </log4j:configuration> 
  */
 
 public static  void main(String[] args) {
   //dom conf start
   String conf = "log4jPattern.xml";
   DOMConfigurator.configure(conf);
   //dom conf end

   logger.debug("Here is some DEBUG");
   logger.info("Here is some INFO");
   logger.warn("Here is some WARN");
   logger.error("Here is some ERROR");
   logger.fatal("Here is some FATAL");
  }
/**
 *  결과
 * 2004-03-04 11:27:57,038 DEBUG [main] Logging4J (Logging4J.java:440) - Here is some DEBUG
 * 2004-03-04 11:27:57,038 INFO  [main] Logging4J (Logging4J.java:441) - Here is some INFO
 * 2004-03-04 11:27:57,038 WARN  [main] Logging4J (Logging4J.java:442) - Here is some WARN
 * 2004-03-04 11:27:57,038 ERROR [main] Logging4J (Logging4J.java:443) - Here is some ERROR
 * 2004-03-04 11:27:57,038 FATAL [main] Logging4J (Logging4J.java:444) - Here is some FATAL
 */
 
}

'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] Jakarta Digester 학습하기  (0) 2004.04.01
[펌] Torque프로젝트 사용기  (0) 2004.03.31
[펌] JSF 사용하기  (0) 2004.03.29
[펌] Struts, Tiles, JavaServer Faces 통합하기  (0) 2004.03.29
[펌] 리팩토링(Refactoring)  (0) 2004.03.29
Posted by tornado
|

출처 : http://network.hanbitbook.co.kr/view_news.htm?serial=698

 

JSF 사용하기

등록일: 2003년 11월 11일

저자: 안드레이 키오뢰아누(Andrei Cioroianu), 역 이상화

JSF는 자바 개발자에게 웹 기반의 사용자 인터페이스를 작성하기 위한 API와 태그 라이브러리를 제공한다. 아파치 스트럿츠 프레임워크를 만든 저작자이면서 썬의 JSF 프로젝트의 공동작업자로 활동하고 있는 크레이그 맥클라나한(Craig McClanahan)은 이러한 것들이 아파치 프로젝트를 JSF 표준으로 쉽게 갈 수 있게 해준다고 믿고있다. 스트럿츠 프레임워크와 같이 JSF는 자바 빈즈 속성에 연결된 HTML 폼 요소를 생성하는 JSP 태그 집합을 가지고 있다. 개발자의 관점에서는 이 두 프레임워크가 비슷해 보이지만 JSF는 자바 표준이기 때문에 좀더 많은 개발 도구의 지원을 받게 될 것이다. 앞으로는 모든 J2EE 애플리케이션 서버가 JSF를 지원하게 될 것으로 예상된다.

Sun은 최근 JSF 1.0 EA4 버전을 JWSDP 1.2(
Java Web Services Developer Pack 1.2)에 포함하였다. EA4 버전은 액션, 빈즈 관리, 네비게이션 규칙과 같은 새로운 기능을 가지고 있다. 본 기사는 이러한 새로운 모습에 초점을 두고 폼을 작성하거나 사용자 입력을 검증하고 사용자 인터페이스 컴포넌트를 자바 빈즈에 연결하기 위한 JSF의 장점을 보여줄 것이다.

관련기사:

JavaServer Faces 입문 -- JSF는 서버측 프로그래밍의 차세대 주자이다. JSF는 프로그래밍환경을 좀더 편하고 즐겁게 그리고 유지보수가 쉽게 만들 것이다. Budi Kurniawan는 JSF가 유용성과 요구사항을 어떻게 만족 하는지 설명하고 있다.



본 기사는 4개의 메인 컴포넌트로 이루어진 웹 애플리케이션을 포함하고 있다. 자바 빈즈 클래스(PBean.java)는 폰트, 크기, 컬러, 정렬과 같은 속성을 가지고있는 데이터 모델 역할을 한다. JSF 기반의 폼(edit.jsp)은 사용자가 자바 빈즈의 속성값을 변경할 수 있게 해준다. 다른 자바 클래스(PBuilder.java)는 텍스트와 속성들로 구성된 HTML 문단을 생성해낸다. 마지막으로 JSP페이지(view.jsp)는 생성된 문단을 보여준다.


[그림 1] JSF 기반 폼

JSF 폼 만들기

소스 코드

여기에서 소스코드를 다운받을 수 있다.

HTML 폼을 처리하는 작업은 웹 애플리케이션 개발에 필수적인 일상작업 중 하나이다. 좋은 프레임워크의 경우 반복적인 작업들을 자동화 할 수 있거나 XML 파일 내에 관련 설정을 줄일 수 있기 때문에 개발시간을 단축시킬 수 있다. 또한 JSP 태그 라이브러리를 사용하여 웹 개발을 단순화시키는 것도 가능하다. JSF 프레임워크는 HTML 폼을 표현할 수 있는 JSP 태그 라이브러리를 제공하며, 폼의 상태를 유지하고 있거나, 사용자 입력을 검증하고, 자바빈즈 속성과 사용자 인터페이스 컴포넌트를 묶는 작업을 한다. 이런 사항들 외에도 생산성을 높이기 위해 다른 많은 일들을 한다. JSF는 또한 커스텀 사용자 인터페이스, 커스텀 검증 클래스, 서버측 이벤트 리스너를 위한 풍부한 API를 가지고 있다.

JSF는 Core와 HTML Basic의 두 가지 태그 라이브러리를 포함하고 있다. 전자는 UI 컴포넌트에 이벤트 리스너, 검증자를 등록하는 태그와 일반적인 태그들을 제공한다. 후자는 버튼, 텍스트 입력창, 체크박스와 같은 HTML UI 컴포넌트를 표현하는 JSP 태그를 가지고 있다. edit.jsp 페이지는 폼을 만들기 위해 이러한 많은 태그를 이용한다. 두개의 태그 라이브러리는 f와 h라는 표준 접두어를 갖고 edit.jsp 상단에 선언된다.
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %><%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<f:use_faces> 태그는 페이지에 쓰이는 모든 JSF 태그를 반드시 포함해야 하는 컨테이너 태그이다. HTML 내용을 만들어 내지는 않지만 내부 JSF 메커니즘을 호출한다. <h:form> 태그는 UI 컴포넌트를 포함하고 있는 <form> HTML 요소를 생성한다.
<html><head><title>Edit</title></head><body><f:use_faces>    <h:form formName="pform">    ..........    </h:form></f:use_faces></body></html>
상단의 JSP 코드는 다음과 같은 HTML 코드를 만들어 낸다.
<html><head><title>Edit</title></head><body>    <form method="post" action="/usingjsf/faces/edit.jsp">    ..........    </form></body></html>
다음 부분에서는 애플리케이션의 자바 빈즈 모델을 설명한다.

빈즈 다루기

다른 웹 프레임워크처럼 JSF는 데이터와 애플리케이션 로직을 캡슐화 하는 모델 객체로 부터 UI를 분리시킨다. HTML UI가 JSF 태그로 부터 생성되면 JSF 프레임워크는 자바 빈즈 모델으로 부터 데이터를 얻거나 HTML 폼을 만들어내는 UI 컴포넌트의 상태를 조정한다. 사용자가 폼을 제출하면 JSF는 사용자 입력을 검증한다. 검증 결과가 정상이면 JSF는 사용자 입력을 자바 빈즈에 저장하고 HTTP 요청은 "네비게이션 규칙"에 따라 다른 페이지로 이동하게 된다.

자바 빈즈 패턴을 따르고 java.io.Serializable를 상속받은 PBean 클래스는 속성들(텍스트, 크기, 폰트, 컬러, 정렬, 볼드체/이탤릭체/밑줄 등과 같은 폰트 종류: text, size, font, color, align, bold, italic, underline)의 get , set 메소드를 제공한다. JSF는 각각의 애플리케이션 사용자에 대해 faces-config.xml 파일에 설정된 PBean ID를 가지는 session 범위의 PBean 인스턴스를 만든다. 또한 JSF는 faces-config.xml 파일에 제공되어지는 값들을 가지고 자바 빈즈 인스턴스의 속성값을 초기화 한다. 이 파일은 다음 기사에서 소개할 네비게이션 규칙과 같은 JSF 설정 파라미터를 가지고 있다.

다음 XML 부분은 JSF에 의해 유지되는 자바 빈즈에 관련된 설정을 보여주고 있다.
<?xml version="1.0"?><!DOCTYPE faces-config PUBLIC    "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"    "http://java.sun.com/dtd/web-facesconfig_1_0.dtd"><faces-config>    ..........    <managed-bean>       <managed-bean-name>pbean</managed-bean-name>       <managed-bean-class>            com.devsphere.articles.usingjsf.PBean        </managed-bean-class>        <managed-bean-scope>session</managed-bean-scope>        <managed-property>            <property-name>text</property-name>            <null-value/>        </managed-property>        <managed-property>            <property-name>size</property-name>            <value>3</value>        </managed-property>        <managed-property>            <property-name>font</property-name>            <values>                <value>Arial</value>                <value>Courier New</value>            </values>        </managed-property>        <managed-property>            <property-name>color</property-name>            <value>green</value>        </managed-property>        <managed-property>            <property-name>align</property-name>            <value>left</value>        </managed-property>        <managed-property>            <property-name>bold</property-name>            <value>false</value>        </managed-property>        <managed-property>            <property-name>italic</property-name>            <value>true</value>        </managed-property>        <managed-property>            <property-name>underline</property-name>            <value>false</value>        </managed-property>    </managed-bean></faces-config>
JSF에 의해 생성된 빈은 <managed-bean-scope> 요소에 따라 요청, 세션, 애플리케이션 범위 안에 저장될 수 있다. 정해진 범위 안에서 주어진 ID가 객체가 이미 등록되어 있다면 JSF는 자바 빈즈 인스턴스를 생성하지 않는다.

UI 컴포넌트와 검증

edit.jsp의 <h:form> 요소는 다음 부분에서 상세하게 설명할 몇몇 UI 컴포넌트를 포함하고 있다. 각 컴포넌트의 HTML 코드는 <h:input_textarea> 태그나 <f:validate_required>와 같이 사용자 입력의 검증여부를 JSF에게 알리는 역할의 태그에 의해 생성된다. 사용자 입력을 처리하는 컴포넌트들은 valueRef="pbean.property"를 통해 자바 빈즈 속성들과 연결되어 있다. JSF 는 이전에 설명한 빈즈 속성들을 위해 get, set 메소드를 사용한다. JSF 컴포넌트 태그들 중에서 사용자 입력을 처리하지 않는 것도 있다. 예를 들어 <h:output_text> 태그는 텍스트 출력이나 읽기 전용 자바 빈즈 속성값을 위해 사용된다. 각각의 컴포넌트는 id 속성에 의해 정의 되거나 JSF에 의해 자동 설정된 유일한 ID를 가지고 있다. 검증이 필요한 UI 컴포넌트는 에러를 출력하기 위한 <h:output_errors for="id"/> 태그를 사용하기 위해 id 속성이 필요하다.


[그림 2] 검증 에러들

텍스트 영역

JSF 폼의 텍스트 영역 컴포넌트는 PBuilder.java, view.jsp에 포함되어 사용자로 하여금 내용을 입력할 수 있게 한다. edit.jsp 페이지는 <h:output_text> 태그를 사용하여 라벨을 나타내고 30행 3열의 <textarea> HTML 요소를 표현하기 위해 <h:input_textarea> 태그를 사용하였다. <f:validate_required> 태그는 사용자가 텍스트 공간에 어떤 것도 입력하지 않을 때 에러를 보내는 JSF 검증자를 등록한다. 에러가 발생하면 <h:output_errors> 태그에 의해 에러가 출력되고 그렇지 않으면 화면에 아무것도 나타나지 않는다. <h:output_errors> 태그를 위한 속성은 <h:input_textarea> 태그의 id 속성과 같은 값을 갖는다.
<f:use_faces>    <h:form formName="pform">        <p><h:output_text value="Text:"/><br>        <h:input_textarea id="text" valueRef="pbean.text"                rows="3" cols="30">            <f:validate_required/>        </h:input_textarea>        <br><h:output_errors for="text"/>        ..........    </h:form></f:use_faces>
상단의 JSP 코드는 다음의 HTML을 만들어 낸다.
<form method="post" action="/usingjsf/faces/edit.jsp">    <p>Text:<br>    <textarea name="text"        cols="30" rows="3">JavaServer Faces</textarea>    <br>    ..........</form>
<h:input_textarea> 태그의 valueRef="pbean.text" 속성은 JSF에게 자바 빈즈 인스턴스의 ID가 pbean인 것을 찾고 사용자가 입력한 텍스트를 인스턴스의 text 속성에 저장하는 것을 의미한다. HTML 폼이 생성되면 JSF는 <textarea> HTML 요소에 텍스트 속성의 값을 삽입한다. PBean 클래스는 속성의 값을 변경, 검색하기 위해 JSF에 의해 호출된 get, set 메소드를 실행한다.
public class PBean implements java.io.Serializable {    private String text;    public String getText() {        return text;    }    public void setText(String text) {        this.text = text;    }    ..........}
<h:input_textarea> 태그와 더불어 JSF는 텍스트 필드를 생성하는 <input_text>, <h:input_number>, <input_secret> (패스워드용), <input_date>, <input_datetime>, <input_time>과 같은 몇몇 태그를 생성한다. <input_hidden> 태그는 숨겨진 폼 필드를 만든다.

텍스트 필드

edit.jsp의 텍스트 필드는 1부터 7까지의 숫자만을 입력받는다. <h:input_number> 태그에 의해 생성된 HTML 코드는 2개의 검증자를 가지고 있다. <f:validate_required> 태그는 이전에 설명하였다. <f:validate_longrange> 검증자는 사용자 입력이 주어진 범위의 값을 만족하는지 검사한다. 그렇지 않다면 <h:output_errors>를 사용하여 에러를 사용자에게 보여준다.
<f:use_faces>    <h:form formName="pform">        ..........        <p><h:output_text value="Size: [1-7]"/><br>        <h:input_number id="size" valueRef="pbean.size" size="2">            <f:validate_required/>            <f:validate_longrange minimum="1" maximum="7"/>        </h:input_number>        <br><h:output_errors for="size"/>        ..........    </h:form></f:use_faces>
상단의 JSP 코드는 다음의 HTML 코드를 만든다.
<form method="post" action="/usingjsf/faces/edit.jsp">    ..........    <p>Size: [1-7]<br>    <input type="text" name="size" id="size" value="3" size="2">    <br>    ..........</form>
텍스트 필드는 int형의 size 속성과 연결된다. value 속성의 값 3은 HTML 폼이 생성될 때 숫자 입력 값의 기본값을 의미한다. 검증을 통해 에러가 없었다고 가정하면 JSF는 value 속성 값을 포함하는 사용자 입력을 받을 때 자바 빈즈를 갱신 한다. <h:input_number> 태그의 size 속성은 텍스트 필드의 문자열 길이를 정의하고 자바 빈즈 속성에는 영향을 미치지 않는다.
public class PBean implements java.io.Serializable {    ..........    private int size;    public int getSize() {        return size;    }    public void setSize(int size) {        this.size = size;    }    ..........}
<f:validate_required>, <f:validate_longrange> 태그 이외에도 JSF는 <validate_doublerange>, <validate_stringrange>, <validate_length>, <validator> 태그들의 검증 관련 태그들을 제공한다. 마지막 태그는 UI 컴포넌트에 커스텀 검증자를 등록할 수 있게 한다. 또한 자신만의 검증 태그 라이브러리를 만들 수도 있다.

리스트 박스

<h:selectone_listbox> 태그와 <h:selectmany_listbox> 태그는 웹 브라우저에서 리스트 박스를 나타내는 <select> 요소를 생성한다. <h:selectone_listbox> JSF 태그는 사용자에게 단일 선택만 허용하고 <h:selectmany_listbox> 태그는 다중선택을 지원한다. edit.jsp 페이지는 여러 개의 폰트를 포함하고 있는 리스트 박스를 생성하기 위해 <h:selectmany_listbox> 태그를 사용한다. 리스트 아이템을 나타내는 HTML <option> 요소는 <h:selectitem> 태그에 의해 생성된다.
<f:use_faces>    <h:form formName="pform">        ..........        <pgt;<h:output_text value="Font:"/gt;<br>        <h:selectmany_listbox id="font" valueRef="pbean.font">            <h:selectitem itemValue="Arial"                itemLabel="Arial"/>            <h:selectitem itemValue="Courier New"                itemLabel="Courier New"/>            <h:selectitem itemValue="Times New Roman"                itemLabel="Times New Roman"/>        </h:selectmany_listbox>        ..........    </h:form></f:use_faces>
상단의 JSP 코드는 다음의 HTML 코드를 만든다.
<form method="post" action="/usingjsf/faces/edit.jsp">    ..........    <pgt;Font: <br>    <select name="font" multiple size="3">        <option value="Arial" selectedgt;Arial</option>        <option value="Courier New" selected>Courier New</option>        <option value="Times New Roman">Times New Roman</option>    </select>    ..........</form>
리스트 박스는 String[] 속성을 갖는 font 속성에 연결된다. 처음 getFont() 메소드는 외부 접근으로부터 보호 받아야 하는 내부 배열의 복사본을 반환하는 clone() 메소드를 사용한다. setFont() 메소드는 두 번째 setFont() 메소드가 수정할 수 있는 배열의 복사본을 유지하기 위해 clone()를 사용한다.
public class PBean implements java.io.Serializable {    ..........    private String fontArray[];    public String[] getFont() {        return (String[]) fontArray.clone();    }    public void setFont(String fontArray[]) {        this.fontArray = (String[]) fontArray.clone();    }    public String getFont(int index) {        return fontArray[index];    }    public void setFont(int index, String font) {        if (fontArray == null)            fontArray = new String[index+1];        else if (fontArray.length <= index) {            String oldFontArray[] = fontArray;            fontArray = new String[index+1];            for (int i = 0; i < oldFontArray.length; i++)                fontArray[i] = oldFontArray[i];        }        fontArray[index] = font;    }    ..........}
HTML 폼이 생성될 때 JSF는 selected HTML 속성을 자바 빈즈 모델의 폰트 속성 배열 값과 일치하는 리스트 아이템에 적용한다. 검증을 통해 에러가 없었다고 가정하면 JSF는 새로운 폰트를 선택할 때 자바 빈즈를 갱신한다.

드롭-다운 리스트

<h:selectone_menu> 태그는 몇 가지 색깔을 가지고 있는 드롭-다운 리스트를 생성하며, 리스트 박스에서처럼 <h:selectitem> 태그를 가지고 있다.
<f:use_faces>    <h:form formName="pform">        ..........        <p><h:output_text value="Color:"/><br>        <h:selectone_menu id="color" valueRef="pbean.color">            <f:validate_required/>            <h:selectitem itemValue="black" itemLabel="Black"/>            <h:selectitem itemValue="red" itemLabel="Red"/>            <h:selectitem itemValue="blue" itemLabel="Blue"/>            <h:selectitem itemValue="green" itemLabel="Green"/>        </h:selectone_menu>        <br><h:output_errors for="color"/>        ..........    </h:form></f:use_faces>
상단의 JSP 코드는 다음의 HTML 코드를 만든다.
<form method="post" action="/usingjsf/faces/edit.jsp">    ..........    <p>Color:<br>    <select name="color" size="1">        <option value="black">Black</option>        <option value="red">Red</option>        <option value="blue">Blue</option>        <option value="green" selected>Green</option>    </select>    <br>    ..........</form>
드롭-다운 리스트는 String 형을 갖는 color 속성과 연결된다.
public class PBean implements java.io.Serializable {    ..........    private String color;    public String getColor() {        return color;    }    public void setColor(String color) {        this.color = color;    }    ..........}
HTML 폼이 생성될 때 JSF는 selected HTML 속성을 자바 빈즈 모델의 color 속성이 가지고 있는 값과 같은 리스트 아이템에 적용한다. 검증을 통해 에러가 없었다면 JSF는 새로운 색깔을 포함하는 사용자 입력을 받을 때 자바 빈즈를 갱신할 것이다.

라디오 버튼

<h:selectone_radio>, <h:selectitem> 태그는 라디오 버튼 그룹을 생성한다.
<f:use_faces>    <h:form formName="pform">        ..........        <p><h:output_text value="Alignment:"/><br>        <h:selectone_radio id="align" valueRef="pbean.align"                layout="LINE_DIRECTION">            <f:validate_required/>            <h:selectitem itemValue="left" itemLabel="Left"/>            <h:selectitem itemValue="center" itemLabel="Center"/>            <h:selectitem itemValue="right" itemLabel="Right"/>        </h:selectone_radio>        <br><h:output_errors for="align"/>        ..........    </h:form></f:use_faces>
상단의 JSP 코드는 다음과 같은 HTML 코드를 만든다.
<form method="post" action="/usingjsf/faces/edit.jsp">    ..........    <p>Alignment:<br>    <table border="0">        <tr>            <td><input type="radio" checked                name="align" value="left"> Left</td>            <td><input type="radio"                 name="align" value="center"> Center</td>            <td><input type="radio"                name="align" value="right"> Right</td>        </tr>    </table>    <br>    ..........</form>
라디오 버튼은 align 속성에 연결되어 있다.
public class PBean implements java.io.Serializable {    ..........    private String align;    public String getAlign() {        return align;    }    public void setAlign(String align) {        this.align = align;    }    ..........}
HTML 폼이 생성될 때 JSF는 checked HTML 속성을 자바 빈즈 모델의 align 속성이 가지고 있는 값과 같은 라디오 버튼에 적용한다. 검증을 통해 에러가 없었다면 JSF는 새로운 정렬 값을 포함하고 있는 사용자 입력을 받을 때 자바 빈즈를 갱신한다.

체크박스

edit.jsp 페이지는 <h:selectboolean_checkbox> 태그에 의해 생성된 체크박스를 가지고 있다. 이것들은 <h:panel_grid>, <h:panel_group> 태그에 의해 생성된 HTML 테이블 내에 포함된다.
<f:use_faces>    <h:form formName="pform">        ..........        <p><h:output_text value="Style:"/><br>        <h:panel_grid columns="3" border="0" cellspacing="5">            <h:panel_group>                <h:selectboolean_checkbox id="bold"                    valueRef="pbean.bold"/>                <h:output_text value="Bold"/>            </h:panel_group>            <h:panel_group>                <h:selectboolean_checkbox id="italic"                    valueRef="pbean.italic"/>                <h:output_text value="Italic"/>            </h:panel_group>            <h:panel_group>                <h:selectboolean_checkbox id="underline"                    valueRef="pbean.underline"/>                <h:output_text value="Underline"/>            </h:panel_group>        </h:panel_grid>        ..........    </h:form></f:use_faces>
상단의 JSP 코드는 다음의 HTML 코드를 만든다.
<form method="post" action="/usingjsf/faces/edit.jsp">    ..........    <p>Style:<br>    <table border="0" cellspacing="5">        <tr>            <td><input type="checkbox"                 name="bold">Bold</td>            <td><input type="checkbox"                 name="italic" checked>Italic</td>            <td><input type="checkbox"                   name="underline">Underline</td>        </tr>    </table>    ..........</form>
세 개의 체크박스는 bold, italic, underline Boolean 속성들에 연결되어 있다.
public class PBean implements java.io.Serializable {    ..........    private boolean bold;    public boolean isBold() {        return bold;    }    public void setBold(boolean bold) {        this.bold = bold;    }    private boolean italic;    public boolean isItalic() {        return italic;    }    public void setItalic(boolean italic) {        this.italic = italic;    }    private boolean underline;    public boolean isUnderline() {        return underline;    }    public void setUnderline(boolean underline) {        this.underline = underline;    }    ..........}
HTML 폼이 생성될 때 자바 빈즈와 연결된 속성 값이 ture이면(참이면), JSF는 checked HTML 속성을 체크박스에 적용한다. 검증을 통해 에러가 없었다면 JSF는 사용자 입력을 받을 때 자바 빈즈를 갱신 한다.

예제에서 체크박스는 분리되어 생성된다. JSF는 체크박스 그룹을 생성할 수 있는 <h:selectmany_checkboxlist> 태그를 제공한다. 이외에도 JSF는 테이블을 생성할 수 있는 <h:panel_list>, <h:panel_data> 두 개의 태그를 갖고있다.

명령 버튼

faces-config.xml 파일은 네비게이션 규칙을 정의한다. 규칙이란 사용자가 페이지 버튼 중 하나를 클릭했을 때 <from-tree-id> 태그에 설정된 경로를 JSF에게 알려주는 것이다. <navigation-case> 요소 안에는 두 개의 네비게이션 케이스가 있다.
..........<faces-config>    <navigation-rule>        <from-tree-id>/edit.jsp</from-tree-id>        <navigation-case>            <from-outcome>editOutcome</from-outcome>            <to-tree-id>/edit.jsp</to-tree-id>        </navigation-case>        <navigation-case>            <from-outcome>viewOutcome</from-outcome>            <to-tree-id>/view.jsp</to-tree-id>        </navigation-case>    </navigation-rule>    ..........</faces-config>
/edit.jsp 페이지는 <h:command_button> 태그에 의해 생성되는 두 개의 버튼을 가지고 있다. 버튼 각각은 ID, 라벨, JSF에 의해 사용되는 명령이름, action, actionRef 속성을 가지고 있다.
<f:use_faces>    <h:form formName="pform">        ..........        <p>        <h:command_button id="view" label="View"            commandName="viewCmd" action="viewOutcome"/>        <h:command_button id="boldUpperCase"            label="Bold Upper Case / View"            commandName="boldUpperCaseCmd"            actionRef="pbean.boldUpperCaseAction"/>    </h:form></f:use_faces>
상단의 JSP 코드는 다음의 HTML 코드를 만든다.
<form method="post" action="/usingjsf/faces/edit.jsp">    ..........    <p>    <input type="submit" name="view" value="View">    <input type="submit" name="boldUpperCase"         value="Bold Upper Case / View"></form>
JSF는 매번 사용자 입력이 브라우저로부터 제출될 때 폼 데이터를 검증한다. 이상이 없고 데이터 형 변환 에러가 없다면 JSF는 네비게이션 규칙을 고려한다. 첫번째 버튼의 경우 JSF는 두 번째 이동 케이스의 <from-outcome> 요소 값과 같은 action 속성을 얻는다. 그러므로 JSF는 HTTP 요청을 <to-tree-id> 요소 값인 view.jsp 페이지로 포워드 한다.

사용자가 두 번째 버튼을 눌렀을 때 JSF는 PBean 객체의 getBoldUpperCaseAction() 메소드를 호출한다. 이 메소드는 PBean의 내부 클래스 BoldUpperCaseAction 인스턴스를 반환한다. 그러면 JSF는 HTML에 하드 코딩되어있을 때가 아니라 런타임에서 출력을 반환하는 invoke() 메소드를 호출한다.
public class PBean implements java.io.Serializable {    ..........    public BoldUpperCaseAction getBoldUpperCaseAction() {        return new BoldUpperCaseAction();    }    public class BoldUpperCaseAction            extends javax.faces.application.Action {        public String invoke() {            String ucText = getText().toUpperCase();            if (isBold() && getText().equals(ucText))                return "viewOutcome";            else {                setBold(true);                setText(ucText);                return "editOutcome";            }        }    }}
bold 속성이 true(참)이고 text의 값이 같으면 JSF는 두 번째 이동 케이스를 따라 HTTP 요청을 view.jsp 페이지로 포워딩한다. 그렇지 않으면 invoke() 메소드는 bold 속성을 true(참)으로 만들고 모든 텍스트 속성의 문자를 대문자로 변환한 다음 editOutcome을 반환한다. JSF는 현재 페이지 /edit.jsp를 유지하는 첫번째 네비게이션 케이스를 따른다.

폼 데이터 처리

Pbuilder 클래스는 Pbean 인스턴스를 파라미터로 받아 빈즈의 속성을 가지고 HTML 문단을 만들어내는 static 메소드를 포함하고 있다.
package com.devsphere.articles.usingjsf;public class PBuilder {    public static String toHTML(PBean pbean) {        StringBuffer buf = new StringBuffer();        buf.append("<p align=\"");        buf.append(pbean.getAlign());        buf.append("\">");        buf.append("<font size=\"");        buf.append(pbean.getSize());        buf.append("\" color=\"");        buf.append(pbean.getColor());        buf.append("\"");        Object font[] = pbean.getFont();        if (font != null && font.length > 0) {            buf.append(" face=\"");            for (int j = 0; j < font.length; j++) {                if (j > 0)                    buf.append(',');                buf.append(font[j]);            }            buf.append("\"");        }        buf.append(">");        if (pbean.isBold())            buf.append("<b>");        if (pbean.isItalic())            buf.append("<i>");        if (pbean.isUnderline())            buf.append("<u>");        String s = pbean.getText();        int n = s.length();        for (int i = 0; i < n; i++) {            char ch = s.charAt(i);            switch (ch) {                case '<':                    buf.append("<");                    break;                case '>':                    buf.append(">");                    break;                case '&':                    buf.append("&");                    break;                case '"':                    buf.append(""");                    break;                default:                    buf.append(ch);            }        }        if (pbean.isUnderline())            buf.append("</u>");        if (pbean.isItalic())            buf.append("</i>");        if (pbean.isBold())            buf.append("</b>");        buf.append("</font>");        buf.append("</p>");        return buf.toString();    }}
view.jsp 페이지는 JSF에 의해 관리되는 자바빈즈 인스턴스를 얻기위해 <jsp:useBean>를 사용하고 PBuildertoHTML() 메소드를 호출하여, 결과적으로 HTML 문단을 출력한다. JSF의 <h:command_hyperlink> 태그는 edit.jsp를 가리키는 링크를 제공한다.
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %><%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %><html><head><title>View</title></head><body><jsp:useBean id="pbean" scope="session"    class="com.devsphere.articles.usingjsf.PBean"/><%= com.devsphere.articles.usingjsf.PBuilder.toHTML(pbean) %><f:use_faces>    <h:command_hyperlink label="Back to Editing" href="edit.jsp"/></f:use_faces></body></html>


[그림 3] view.jsp에 의해 나타나는 텍스트

애플리케이션 설정

JSF 프레임워크는 JSP 페이지로 향하는 요청을 가로채는 FacesServlet 제어 서블릿을 가지고있다. 서블릿은 web.xml 파일 안에 설정되고 /faces/* URL 패턴에 매핑된다. 서블릿을 활성화시키기 위해 JSP 페이지는 반드시 "faces"로 시작해야 한다. 애플리케이션의 index.jsp 홈페이지는 브라우저 요청을 faces/edit.jsp 페이지로 보내기 위해 response.sendRedirect()를 사용한다.
<% response.sendRedirect("faces/edit.jsp"); %>
faces-config.xml 파일의 위치는 web.xml의 context 파라미터 값에 의해 결정된다. web.xml 파일은 리스너 등록 설정과 JSF 실행에 관련된 파라미터 값을 가지고 있다. 다음은 web.xml 파일의 내용이다.
<?xml version="1.0"?><!DOCTYPE web-app PUBLIC  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  "http://java.sun.com/dtd/web-app_2_3.dtd"><web-app>    <context-param>        <param-name>avax.faces.application.CONFIG_FILES</param-name>        <param-value>/WEB-INF/faces-config.xml</param-value>    </context-param>    <context-param>        <param-name>com.sun.faces.saveStateInClient</param-name>        <param-value>false</param-value>    </context-param>    <context-param>        <param-name>com.sun.faces.validateXml</param-name>        <param-value>true</param-value>    </context-param>    <listener>        <listener-class>com.sun.faces.config.ConfigListener</listener-class>    </listener>    <servlet>        <servlet-name>FacesServlet</servlet-name>        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>        <load-on-startup>1</load-on-startup>    </servlet>    <servlet-mapping>        <servlet-name>FacesServlet</servlet-name>        <url-pattern>/faces/*</url-pattern>    </servlet-mapping></web-app>
처음 폼이 생성될 때 JSF는 컴포넌트 트리, UI 컴포넌트에 관한 정보를 가진 객체, 객체에 등록된 검증자를 만든다. 사용자가 폼 데이터를 제출하면 JSF는 컴포넌트 트리로부터 정보를 얻어 사용자 입력을 검증한다. 기본적으로 JSF는 session 범위에 컴포넌트 트리를 저장한다. 이 작업은 애플리케이션이 끝날 때 한번 일어난다. 개발기간 동안 UI 컴포넌트를 추가하거나 제거하여 JSF 폼을 변경 하면 JSF는 JSP 페이지가 수정될 때 컴포넌트 트리 값을 버리지 않기 때문에 예외를 던진다. web.xml 파일에 선언된 saveStateInClient 플래그가 true(참)으로 바뀌면 JSF는 session 속성으로 저장하지 않고 컴포넌트 트리를 HTML hidden 필드로 직렬화시킨다.

요약

본 기사에서는 JSF 태그로 폼 작성하는 방법을 학습하면서 프레임워크의 기본적인 모습을 살펴보았다. 여타의 EA 패키지처럼 JSF EA4도 아직 배포단계는 아니지만 대부분의 기능을 사용할 수 있을 것이다. 자바 개발자에게 웹 사용자 인터페이스 작성을 위한 표준 태그 라이브러리와 커스텀 웹 컴포넌트 작성을 위한 표준 API가 절실했기 때문에 JCP에서 2년의 시간을 보낸 JSF는 현재 환영 받고 있다.

참고자료

'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] Torque프로젝트 사용기  (0) 2004.03.31
[펌] Log4J...  (0) 2004.03.30
[펌] Struts, Tiles, JavaServer Faces 통합하기  (0) 2004.03.29
[펌] 리팩토링(Refactoring)  (0) 2004.03.29
[펌] 스트럿츠 이디엄  (0) 2004.03.29
Posted by tornado
|

출처 : http://www-903.ibm.com/developerworks/kr/java/library/j-integrate.html

Struts, Tiles, JavaServer Faces 통합의 힘

Level: Advanced

Srikanth Shenoy, J2EE Consultant, Objectseek Inc.
Nithin Mallya , J2EE Consultant, Objectseek Inc.

2003년 9월 23일

JavaServer Faces (JSF)의 프론트-엔드 성능, Tiles의 콘텐트 포맷 능력, Struts 콘트롤러의 유연성이 J2EE 웹 애플리케이션에 들어있다면? 엔터프라이즈 자바 전문가 Srikanth Shenoy와 Nithin Mallya는 이 세 개의 특징을 통합하는 방법을 보여준다.

Struts, Tiles, JavaServer Faces (JSF)를 함께 사용함으로서 개발자들은 관리 및 재활용이 쉬운 강력하고 표현성이 뛰어난 웹 애플리케이션을 만들 수 있다.

Struts 프레임웍은 꽤 오랫동안 존재했고 사실상 J2EE 웹 애플리케이션을 개발할 때 의존하게 되는 표준이다. Tiles 프레임웍은 Struts 이후에 등장했고 컴포넌트 부분들을 사용하여 프리젠테이션 페이지를 어셈블링하는 기능을 선보임으로서 위상을 다졌다. 웹 애플리케이션 프레임웍의 신생아인 JSF는 인풋의 타당성검사와 유저 이벤트의 핸들링을 위한 메커니즘을 제공한다. 무엇보다 중요한것은 이것이 프로토콜에서 독립적인 방식으로 사용자 인터페이스 컴포넌트들을 렌더링한다는 점이다. ("The major players")

Struts와 JSF의 몇몇 기능들은 겹치기는 하지만 다른 방식으로 보완이 된다. 이 세 기술의 결합은 웹 애플리케이션을 개발하고 프리젠테이션을 구성하고 커스텀 유저 인터페이스 컴포넌트를 프로토콜과 독립적으로 렌더링하는 효율적인 방법을 제공할 수 있다.

이 글의 샘플 코드를 실행하려면 Struts 1.1, Tiles, JavaServer Faces Reference Implementation (JSF-RI) Early Access Release 4.0, Struts-Faces 0.4가 필요하다.

JSF
JSF 애플리케이션은 JSF 프레임웍을 사용하는 일반적인 J2EE 웹 애플리케이션이다. 이 프레임웍은 풍부한 GUI 컴포넌트 모델을 제공한다. 어떤 특정 기술이 놀라워도 룩앤필은 더욱 발전할 필요가 있다는 말을 들어본적이 있을 것이다. HTML 컴포넌트로 평이한 바닐라 페이지가 있던 시절은 지나갔고 뛰어난 GUI 룩앤필을 구현할 수 있다. 트리 컴포넌트, 메뉴 컴포넌트, 그래프는 기존에 존재했던 UI 컴포넌트이고 이것은 JSF가 당연히 제공해야 하는 것이다. JSF는 이에 더하여 쉽게 API를 사용할 수 있는 방법을 제공하여 커스텀 컴포넌트의 생성을 돕는다.

Note: 위에 언급한 UI 컴포넌트는 Sun에서 제공한 샘플의 일부이다.

Model-View-Controller (MVC) 패턴을 사용하는 전통적인 웹 애플리케이션에서, GUI 컴포넌트들은 프리젠테이션과 비지니스 로직을 핸들하는 커스텀 태그에 의해 표현된다. 이는 클라이언트 장치까지 코딩해야하는 문제를 낳았다.

JSF는 구조적으로 프리젠테이션 로직과 UI 컴포넌트의 비지니스 로직이 분리되어있다. JSP 페이지에서 JSF 태그를 사용하여 렌더러(RENDERER)와 UI 컴포넌트를 결합할 수 있다. 하나의 UI 컴포넌트는 다른 렌더러를 사용하여 다양한 방식으로 렌더링 될 수 있다. UI 컴포넌트 스팩의 코드는 서버에서 실행되고 사용자 액션으로 생성된 이벤트에 응답한다.

JSF-RI는 UI 컴포넌트에서 HTML을 렌더링하는 데 쓰이는 커스텀 태그 라이브러리와 함께 렌더 킷을 제공한다. 컴포넌트들의 룩앤필을 커스터마이징 하는 기능도 제공한다. 특별한 컴포넌트가 필요하면 특정 클라이언트 장치용 커스텀 태그를 만들 수 있고 UI 컴포넌트와 커스텀 렌더러와 이를 결합할 수 있다. 다른 디바이스의 경우 다양한 렌더러를 지정하면 된다.

JSF와 UI 컴포넌트
Java AWT 또는 Swing API를 사용하여 Java GUI 애플리케이션을 만든 경험이 있다면 JSF의 UIComponent 에도 익숙할 것이다. 이것은 자식 컴포넌트의 트리를 저장하고 클라이언트 쪽에서 발생한 액션들에 대해 표준 이벤트를 만든다. 이 이벤트들은 FacesContext에 캐싱된다. 커스텀 태그를 사용하여 이들 각 이벤트용 핸들러들을 결합할 수 있다. 예를 들어 커스텀 ActionListener가 있다면 사용자 클릭 또는 형식 제출을 핸들하게 된다.

JSF UIComponent, Renderer, 태그는 언제나 함께한다. 모든 JSF 커스텀 태그는 UIComponentTag를 하위클래스로 나눔으로서 생성된다. doStartdoEnd 메소드는 UIComponentTag 클래스에 이미 구현되었다. 이 태그 클래스에 추가 기능만 제공하면 된다.

그림 1은 커스텀 태그, UI 컴포넌트, 렌더러의 관계이다. 클라이언트 브라우저가 UI 컴포넌트 (MyComponent)용 JSF 태그(jsf:myTag)를 이용하여 JSP 페이지에 액세스한다. UI 컴포넌트는 서버에서 실행되고 적절한 렌더러(MyRenderer)를 사용하는 HTML로서 다시 클라이언트로 렌더링된다. JSP 페이지는 HTML에서 이들을 코딩하기 보다는 JSF-RI의 커스텀 태그를 사용하여 사용자 인터페이스 컴포넌트를 표현한다.

예를 들어, 그림 1은 h:panel:group 태그의 사용방법을 보여주고 있다. 이 태그는 한 부모 밑의 컴포넌트들을 그룹핑하는데 사용된다. panel_gridpanel_data 같은 다른 패널 태그와의 결합에 사용될 때 이것은 런타임시 HTML 테이블의 칼럼을 위한 마크업을 만든다. JSF-RI가 제공되는 html_basic 태그 라이브러리는 텍스트 필드, 버튼 등의 HTML 컴포넌트를 표현하는데 사용된다.

그림 1. JSF 페이지 렌더링
Rendering a JSF page

JSF 수명 주기
JSF 수명 주기는 6개의 단계로 구성되어 있다. 인커밍 리퀘스트는 모든 것을 통과하거나 리퀘스트, 밸리데이션, 수명 주기안에 일어나는 변환 에러, 응답 유형에 따라 어떤 단계도 통과하지 않을 수 있다. JSP 페이지에서 생성된 faces request는 JSF 프레임웍에 의해 핸들되고 faces 또는 non-faces 응답이 리턴된다.

JSF 폼(form)이 제출되거나 사용자가 URL에 /faces 라는 접두사를 가진 페이지를 가르키는 링크를 클릭할 때 faces request가 발생한다. 모든 faces requestFacesServlet에 의해 핸들된다.

어떤 JSF 컴포넌트도 없이 요청이 서블릿이나 JSP 페이지로 보내진 리퀘스트는 non-faces request 라고 한다. 결과 페이지에 JSF 태그가 있으면 faces response 라고 한다. JSF 태그가 없으면 이것은 non-faces response이다.

JSF 수명 주기에는 여섯 단계가 있다:

  • Reconstitute request tree(리퀘스트 트리 재구성)
  • Apply request values(리퀘스트 값 적용)
  • Process validations(타당성 검사)
  • Update model values(모델 값 업데이트)
  • Invoke application(애플리케이션 호출)
  • Render response(응답 렌더링)

JSF 스팩에 따르면 각 단계는 리퀘스트 프로세싱 수명주기의 논리적 개념을 나타내고 있다. 하지만 JSF-RI에서 이 단계들은 실제 클래스와 이것의 이름으로 표현된다.

faces 리퀘스트 다루기
JSF 리퀘스트 프로세싱을 이해하려면 FlightSearch.jsp를 연구해야 한다.(Listing 1) JSF 형식에는 도착/출발 도시용 인풋 텍스트 필드, 출발/도착 날짜, 형식 제출 및 리셋용 버튼이 포함되어 있다.

리퀘스트가 FacesServlet에 의해 받아들여지면 응답이 클라이언트에게로 렌더링되기 전에 다양한 단계를 거친다. 그림 2는 JSF 리퀘스트가 처리되는 방법이다.

1. 리퀘스트 받기
FacesServlet은 리퀘스트를 받아 FacesContextFactory 에서 FacesContext의 인스턴스를 취한다.

2. 수명 주기 프로세싱 전달하기
FacesServlet은 faces 콘텍스트로 전달된 Lifecycle 구현에 대한 execute 메소드를 호출함으로서 Lifecycle 인터페이스로 수명 주기 프로세싱을 전달한다.

3. 각 단계 마다 라이프사이클 실행
Lifecycle 구현은 Reconstitute Component Tree 단계를 시작으로 하여 각 단계를 실행한다.

4. 컴포넌트 트리 생성
Reconstitute Component Tree 단계에서 컴포넌트 트리가 travelForm의 컴포넌트들과 함께 만들어진다. 이 트리는 루트(root)로서 UIForm을, 이것의 자식으로서 다양한 텍스트 필드와 버튼을 갖는다.

fromCity 필드는 공백이 될 수 없음을 지정하는 타당성 규칙을 갖고있다. (validate_required 태그) 이 태그는 JSF Validator가 있는 fromCity 텍스트 필드를 링크한다.

JSF는 여러 빌트인 밸리데이터가 있다. 상응하는 Validator는 이 단계에서 초기화된다. 이 컴포넌트 트리는 FacesContext에 캐싱되고 콘텍스트는 다음 단계에서 사용되어 트리에 액세스하고 이벤트 핸들러를 호출한다. UIForm 스테이트는 자동으로 저장된다. 따라서 이 페이지가 리프레쉬 되면 이 형식의 원리 콘텐츠가 디스플레이된다.

5. 트리에서 값 뽑아내기
Apply Request Values 단계에서 JSF 구현은 컴포넌트 트리를 트래버스하고 decode 메소드를 사용하여 리퀘스트에서 값을 뽑아 각 컴포넌트에 맞게 로컬로 설정한다. 이 프로세스 동안 에러가 있다면 FacesContext에 놓이게되고 Render Response 단계에서 사용자에게 디스플레이된다.

또한 사용자 액션의 결과로 만들어져서 이 단계에 놓이게 된 이벤트들은 등록된 리스너에게 브로드캐스팅된다. 리퀘스트 버튼을 클릭하면 텍스트 필드의 값이 원래 값으로 설정된다.

6. Process Validations
Process Validations 단계에서 각 컴포넌트와 관련된 밸리데이션은 Apply Request Values 단계에서 설정된 로컬 값에 대해 수행한다. 이것은 JSF 구현이 각각의 등록된 밸리데이터에 대해 validate 메소드를 호출할 때 발생한다.

밸리데이션이 실패하면 수명 주기는 같은 페이지가 렌더링되는 Render Response 단계로 진행된다. 하지만 에러 메시지도 함께다. 이 단계에서 놓여진 이벤트들은 등록된 리스너에게 브로드캐스팅 된다.

JSF 구현은 소스 필드에 대한 밸리데이터를 처리한다. 데이터가 무효가 되면 콘트롤은 Render Response 단계로 전달되는데 이곳은 FlightSearch.jsp가 관련 컴포넌트에 대해 디스플레이된 밸리데이션 에러와 함께 다시 렌더링 되는 곳이다. JSP 페이지에서 output_errors를 선언함으로서 이 페이지의 모든 에러는 페이지 하단에 디스플레이된다.

7. 모델 객체 값 설정하기
Update Model Values 단계에서 모든 밸리데이션이 성공적으로 처리된 후에 JSF 구현은 각 컴포넌트에 대한 updateModel 메소드를 호출하여 유효한 것으로 모델 객체 값을 설정한다. 로컬 데이터를 모델 객체 속성에 의해 지정된 유형으로 변환하는 동안 에러가 발생하면 수명 주기는 Render Response 단계로 옮겨간다. 이곳에서 에러가 디스플레이된다. 형식 필드 속성의 값은 모델 객체의 애트리뷰트 값으로 파퓰레이트 된다.

8. ActionListener 호출
ActionListener와 사용자 액션을 결합할 수 있다.(Listing 1) Invoke Application 단계에서 processAction 메소드는 FlightSearchActionListener에 호출된다. 호출 시 processAction 메소드는 실제 시나리오에서 기준에 맞는 비행 데이터베이스를 검색하고 컴포넌트의 액션 애트리뷰트에서 결과를 검색한다.

9.응답 렌더링
Render Respons 단계에서 faces 설정 파일에서 검색되어 얻어진 페이지(FlightList.jsp)는 faces 콘텍스트에 에러가 없다면 디스플레이된다. 이전 페이지의 에러 때문에 콘트롤이 이 단계에 오면 FlightSearch.jsp는 에러 메시지와 함께 다시 디스플레이된다.

그림 2. JSF 리퀘스트 프로세스
그림보기.

Listing 1. FlightSearch.jsp
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <f:use_faces> <h:form id="flightForm" formName="flightForm" > <h:input_text id="fromCity" valueRef="FlightSearchBean.fromCity"> <f:validate_required/> <h:input_text/> <h:input_text id="toCity" valueRef="FlightSearchBean.toCity"> <h:input_text id="departureDate" valueRef="FlightSearchBean.departureDate"> <h:input_text id="arrivalDate" valueRef="FlightSearchBean.arrivalDate"> <h:command_button id="submit" action="success" label="Submit" commandName="submit" > <f:action_listener type="foo.bar.FlightSearchActionListener"/> </h:command_button> <h:command_button id="reset" action="reset" label="Reset" commandName="reset" /> <h:output_errors/> </h:form> </f:use_faces> 

JSF-RI의 두 개의 태그 라이브러리가 이 코드에 사용된다. html_basic 태그 라이브러리는 일반적으로 사용되는 HTML 컴포넌트용 태그를 정의하고 jsf-core 태그 라이브러리에는 리스너와 벨리데이터를 등록하는데 사용되는 태그가 포함되어 있다. 기타 태그로는:

  • f:use_faces 태그는 JSF 구현에 이 태그가 faces 태그라는 것을 가르킨다.

  • f:validate_required 태그는 어태치 되는 필드가 형식이 제출되기 전에 값을 가져야 한다는것을 나타낸다.

  • h:formh:input_text 태그는 flightSearchForm이라고 하는 HTML 형식과 다양한 텍스트 필드를 각각 나타낸다.

  • h:command_button 태그는 Submit과 Reset버튼을 구현에 사용된다.

  • 마지막으로 h:output_errors 태그는 Struts html:errors 태그와 비슷하고 형식 필드의 밸리데이션 동안 발생한 에러를 디스플레이한다.

FlightSearchBean이라고 하는 JavaBean은 Updated Model Values 단계에 있는 동안 UIComponent 데이터 부터 업데이트되는 모델을 나타낸다. 일반적으로 JavaBean은 jsp:useBean 태그와 함께 JSP 페이지에서 선언된다. FlightSearch.jsp에서 수행되지 않았다는 것에 주목하라. faces 설정 파일의 JSP 페이지들에 의해 사용되는 모든 JavaBean 컴포넌트들을 선언할 수 있는 곳에서 Managed Beans 라고 하는 JSF 기능을 사용할 수 있기 때문이다. 설정할 때 서블릿 콘테이너는 이러한 JavaBean 컴포넌트를 초기화한다. faces-config.xml 파일의 FlightSearchBean용 엔트리는 Listing 2에 나타나있다:

Listing 2. TravelInfoBean용 faces-config.xml 엔트리
 <managed-bean> <managed-bean-name>FlightSearchBean</managed-bean-name> <managed-bean-class> foo.bar.FlightSearchBean </managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> 

faces 응답 렌더링
결과 JSP 페이지들이 JSF 태그를 포함하고 있을 때 faces 응답은 Faces 애플리케이션에 의해 생성된다. 이 응답은 faces 또는 non-faces 리퀘스트의 결과가 될 수 있다.

우리 예제에서 Listing 1의 페이지 렌더링은 faces 응답이다. doStartTag()doEndTag()메소드에 익숙할 것이다. JSF와 Struts-Faces에서 각 태그는 UIComponentTag에서 확장된다. UIComponentTagdoStartTag()doEndTag() 메소드를 구현한다.

또한 추상 메소드인 getComponentType()getRendererType()을 갖고있다. 이 구체적 태그 클래스에 이 두 개의 메소드를 구현함으로서 컴포넌트와 렌더러를 각각 지정할 수 있다.

텍스트 필드를 가진 JSF 형식을 생각해보자. 다음 순서는 JSF 형식이 렌더링 될 때 실행된다.

1. doStartTag() 메소드 호출하기
Servlet 콘테이너는 FormTagdoStartTag() 메소드를 호출한다.

2. UIComponent 얻기
FormTaggetComponentType() 메소드에서 UIComponent를 얻는다. UIComponentTaggetComponentType()을 사용하여 faces-config.xml 파일로부터 이 컴포넌트에 대한 클래스 이름을 살피고 UIComponent(FormComponent)의 인스턴스를 만든다.

3. 렌더러 얻기
FormTaggetRendererType 메소드에서 Renderer를 얻는다. 컴포넌트 유형에서처럼 렌더러 이름은 faces-config.xml 파일에서 검색된다.

4.인코딩 메소드 호출
FormComponentFormRenderer가 만들어진 후에 encodeBegin() 메소드는 FormComponent에 호출된다. 각 태그를 위해 렌더링은 encodeBegin()으로 시작하고 encodeEnd()로 끝난다. encodeBegin() 메소드는 중첩 순서로 호출된다.

5. 태그의 끝과 HTML 렌더링
서블릿 컨테이너는 이 태그에 대해 doEndTag() 메소드를 호출한다. encodeEnd() 메소드는 각 컴포넌트에 대해 중첩의 역순으로 호출된다. 결국 Form과 모든 중첩된 컴포넌트들은 HTML로서 렌더링된다. 이 지점에서 HTML 생성은 종료되고 JSP와 동등한 HTML이 렌더링된다.

그림 3은 faces 응답의 생성을 구성하는 이벤트 순서이다.

그림 3. faces 응답 렌더링
그림보기.

통합의 이유
JSP와 관련 스팩이 발전함에 따라 JSF와 JSP Standard Tag Library 같은 새로운 표준이 등장하고 있다. 다음은 이 새로운 기술들을 하나의 통합된 전체로서 사용할 때의 이점들이다:

  • 작동과 프리젠테이션의 깨끗한 분리. 태그, 렌더러, 컴포넌트의 분리로 페이지 작성자와 애플리케이션 개발자의 역할은 정의가 더 분명해진다.

  • 컴포넌트의 프리젠테이션 변경이 큰 영향을 끼치지 못한다. 이제는 렌더러만 쉽게 변경하면 된다. 전통적인 MVC 모델에서 태그의 모든 변경은 비지니스 로직의 변경까지 수반했다.

  • 렌더러 독립. 다중의 렌더러로 다중의 프리젠테이션 디바이스용 컴포넌트 로직을 재사용함으로서 프로토콜 독립도 가능하다. 다양한 렌더러를 사용할 수 있다는 것은 특정 디바이스에 대한 전체 프리젠테이션 티어의 코딩 필요성도 감소한다는 것을 의미한다.

  • 커스텀 컴포넌트의 어셈블링과 재사용 표준. JSF는 "형식과 필드"를 넘어 생각하고 커스텀 GUI 컴포넌트 렌더링에 풍부한 컴포넌트 모델을 제공한다. JSF를 사용하여 각 페이지마다 컴포넌트의 모습과 작동을 커스터마이징 할 수 있다. 개발자들은 그들만의 GUI 컴포넌트를 만들 수 있는 데다가 간단한 커스텀 태그로 모든 JSP 페이지에 쉽게 포함시킬 수 있다. 자바 프론트엔드 GUI 컴포넌트인 AWT와 Swing과 마찬가지로 웹 페이지에 대한 커스텀 컴포넌트들이 있다!

Struts는 이미 사용자 기반이 넓게 형성되어 있는 프레임웍이다. 많은 IT 부서들은 MVC 프레임웍의 가치를 인식했고 꽤 오랫동안 사용해왔다. JSF는 Struts의 강력한 컨트롤러 아키텍쳐를 갖고있지 않다. 마찬가지로 표준화된 ActionFormActions도 없다. Tiles를 통합하면 완벽한 방식으로 레이아웃을 재사용 및 변경할 수 있다.

JSF가 가능한 Struts 애플리케이션의 마이그레이션은 두 가지 도전과제가 있다. 하나는 Struts 태그가 JSF 호환이 아니라는 점이다. 다시말하면 JSF 스팩에서 요구하는 대로 UIComponentTag를 확장하지 않는다. 따라서 JSF는 UIComponentRenderers를 해석할 수도 결합할 수도 없다.

두 번째, FacesServletRequestProcessor 사이에 어떤 연결도 없다. Struts 애플리케이션에서 RequestProcessor는 콜백 메소드로 ActionFormActions 클래스로의 모습을 관리한다. ActionForm 프로퍼티용 게터와 세터와 validate()ActionForm의 콜백 메소드이다. Action의 경우 execute()가 콜백 메소드이다. RequestProcessor가 호출되지 않는한 Struts ActionFormActions 클래스의 콜백 메소드는 비지니스 로직을 호출할 기회를 가질 수 없다.

Struts와 JSF를 Struts-Faces와 통합하기
이쯤되면, Struts와 JSF를 통합할 수 있는 소프트웨어가 있다면 또는 통합 소프트웨어를 직접 작성하는 방법에 대해 궁금할 것이다.

좋은 소식이 있다. 소프트웨어는 이미 존재하고 있다. Struts-Faces는 Struts JSF 통합 라이브러리의 초기 릴리스이다. 이 라이브러리는 Craig McClanahan이 만들었고 기존 Struts 애플리케이션을 JSF에 마이그레이션할 수 있도록 했다. Struts-Faces는 JSF를 이용한 깔끔한 통합을 위해 노력하고 있다. JSF가 프론트엔드에서 사용되고 백엔드에는 익숙한 Struts 컴포넌트가 있도록 말이다.

그림 4는 Struts-Faces와 JSF 클래스 관계이다. 파란색 클래스들은 Struts-Faces에 속한다.

그림 4. Struts-Faces 클래스 다이어그램
그림보기.

다음은 Struts-Faces의 주요 컴포넌트들이다:

  • FacesRequestProcessor 클래스는 모든 faces 리퀘스트를 핸들한다. Non-faces 리퀘스트는 부모인 RequestProcessor로 보내진다.

  • ActionListenerImpl 클래스는 ActionEvents를 핸들한다.

  • FormComponent는 JSF Form Component 로 부터 확장하지만 Struts 수명 주기 내에 호출된다.

  • 렌더러 FormComponent용 태그.

  • 아웃풋 전용으로 렌더링되는 데이터용 태그와 렌더러.

  • ServletContextListener의 구현은 초기화 동안 적절한 RequestProcessor를 등록한다.

  • filefaces-config.xml 파일. struts-faces.jar 파일에 번들된다.

Listing 3은 Struts-Faces 태그를 사용하는 FlightSearch.jsp 이다.

Listing 3. Struts-Faces 태그를 사용하는 FlightSearch.jsp
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://jakarta.apache.org/struts/tags-faces" prefix="s" %>  <f:use_faces> <s:form action="/listFlights"> <h:input_text id="fromCity" valueRef="FlightSearchForm.fromCity"/> <h:input_text id="toCity" valueRef="FlightSearchForm.toCity"/> <h:input_text id="departureDate" valueRef="FlightSearchForm.departureDate"> <h:input_text id="arrivalDate" valueRef="FlightSearchForm.arrivalDate"> <h:command_button id="submit" action="success" label="Submit" commandName="submit" /> <h:command_button id="reset" action="reset" label="Reset" commandName="reset" /> <s:errors/> </s:form> </f:use_faces> 

s:form 태그는 HTML 형식을 만드는데 사용된다. 이 형식 액션 애트리뷰트는 Listing 1에 지정된 형식 이름 flightForm 대신 /listFlights 이다. JSF에서 형식 이름은 UIForm에 할당된 이름일 뿐이다.

FlightSearchBean은 JSF Form용 모델이고 Update Model Values 단계에서 값을 얻는다. 하지만 Struts에서 형식 액션은 Struts Configuration File, struts-config.xml의 ActionMapping을 가르킨다. 작동 방법을 알려면 Listing 4의 struts-config.xml 파일을 참조한다.

/listFlightsActionMapping이, URI-path용 ActionFormfoo.bar.FlightSearchForm 이고 Action 클래스는 foo.bar.FlightSearchAction을 나타낸다는 것을 알게될 것이다.

Listing 4. struts-config.xml의 Action 선언
 <form-bean name="FlightSearchForm" type="foo.bar.FlightSearchForm"/> <!-- ========== Action Mapping Definition ========================= --> <action-mappings> <!-- List Flights action --> <action path="/listFlights" type="foo.bar.FlightSearchAction" name="FlightSearchForm" scope="request" input="/faces/FlightSearch.jsp"> <forward name="success" path="/faces/FlightList.jsp"/> </action> </action-mappings> 

Struts와 Tiles를 통합하는 다섯 단계

1. JSP를 만들어 사이트 레이아웃을 표현한다. 이것이 마스터 JSP로서 헤더, 바디, 풋터를 위한 플레이스홀더를 갖고있다. 이들 각각은 Tiles 태그를 사용하여 메인 JSP 페이지에 추가된다.

2. Tiles 정의 파일을 만들고 어떤 JSP 페이지가 전체 페이지의 플레이스홀더에 포함되어야 하는지를 정한다. 모든 페이지를 고유의 이름으로 지정한다.

3. struts-config.xml 파일에서 글로벌과 로컬 포워드를 변경하여 이전 단계에서 지정한 고유 이름들을 사용한다.

4. TilesPlugIn을 사용하여 시작하는 동안 Tiles 정의 파일을 로딩한다. TilesPlugIn 엔트리를 struts-config.xml 파일에 추가한다.

5. TilesRequestProcessor 엔트리를 struts-config.xml 파일에 추가한다. 이것이 Tiles-Struts 애플리케이션용 디폴트 요청 프로세스이다.

.do에 액션 애트리뷰트가 없는것을 눈치챘을 것이다. Struts-Faces가 형식 이름으로 형식 액션을 사용하기 때문이다.

또한 JSF validation 태그를 여기서는 사용하지 않았다. Struts에서 밸리데이션은 ActionForm 클래스에 대한 validate() 메소드에서 발생하기 때문이다. s:errors 태그는 Struts 에러 태그와 비슷하고 밸리데이션 동안 발생한 에러 메시지를 디스플레이하는데 사용된다.

어떤 ActionListener도 Submit 버튼과 결합되지 않았다. ActionListener가 이미 Struts-Faces에 제공되고 언제나 ActionEvents와 함께 faces 리퀘스트를 FacesRequestProcessor로 보내기 떄문이다.

Struts 애플리케이션을 JSF에 마이그레이션 하기
다음은 Struts 웹 애플리케이션을 JSF와 통합하는 단계이다:

  • JSF-specific JARs (jsf-api.jar, jsf-ri.jar)와 함께 struts-faces.jar 파일을 웹 애플리케이션의 WEB-INF/lib 디렉토리에 추가한다.

  • JSF와 JSTL을 사용 할 계획이면 JSTL-specific JARs (jstl.jar, standard.jar)를 WEB-INF/lib 폴더에 추가한다. 이 단계는 일반적 Tomcat에 전개 할 경우에만 필요하다. JWSDP는 이미 이러한 JAR를 제공한다.

  • 웹 애플리케이션 전개 디스크립터 (/WEB-INF/web.xml)를 변경하여 Faces Servlet 정의(Listing 5)용 엔트리를 갖는다.

  • JSP 페이지를 변경하여 Struts 태그 대신 JSF와 Struts-Faces 태그를 사용한다. 특히 html, base, form, errors 태그를 Struts-Faces의 해당하는 것으로 대체한다. text, textarea, radio 태그를 이에 상응하는 JSF 태그로 대체한다. Struts-Faces는 이를 위한 개별 태그를 갖고있지 않다. 필수사항은 아니지만 Struts Logic 태그를 해당 JSTL 태그로 대체하는 것도 고려할 수 있다.

  • JSF 태그를 사용하고 struts-config.xml 파일을 변경하여 JSP을 가르키는 Action Mappings의 global-forwardslocal-forwards/faces 접두사를 추가한다.

  • 웹 애플리케이션이 자신이 만든 커스텀 컴포넌트를 사용한다면 이들을 JSF 구현의 디폴트 RenderKit으로 등록해야한다. WEB-INF 폴더의 faces-config.xml에 파일을 만들고 각 컴포넌트와 렌더러용 엔트리를 추가하여 수행한다. 하지만 faces-config.xml 파일은 Struts-Faces자 파일에 이미 번들되어 있다. struts-faces.jar 파일에서 이를 빼어내어 콘텐츠에 추가하고 WEB-INF 폴더 밑에 놓는다.

Listing 5. web.xml의 FacesServlet 선언
 <!-- JavaServer Faces Servlet Configuration --> <servlet> <servlet-name>faces</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- JavaServer Faces Servlet Mapping --> <servlet-mapping> <servlet-name>faces</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> 

Struts-Faces와 Tiles 통합의 문제점
Struts-Faces 라이브러리는 Struts와 JSF 사이에 효율적인 다리를 제공한다. J2EE 웹 애플리케이션에 풍부한 프리젠테이션 레이어를 가능하게 한다. 이 결합물에 Tiles를 추가하여 보다 풍부하게 프리젠테이션 레이어를 만들 수 있다. 따라서 Struts와 JSF 결합에서 이득도 보고 다양한 JSP 페이지를 효율적으로 재사용할 수 있다.

이 글에서는 이미 Struts와 JSF의 통합을 설명했다. 그렇다면 Tiles를 여기에 추가하는 것은 식은죽 먹기 아니겠는가?

불행히도 JSF는 아직 초기 단계에 머물러있다. Struts-Faces 통합 소프트웨어는 계속 개발되어 JSF의 다양한 기능을 수용하지만 아직 Tiles는 지원하지 않는다.

Struts와 Tiles는 완벽히 상호 작동할 수 있다. 하지만 통합할 때 장애도 있다. 다음 섹션에서는 Struts-Faces 통합 라이브러리를 Tiles와 함께 사용할 때의 일반적인 문제점을 설명한다.

Listing 6은 Flight Search 페이지용 레이아웃이다. 이를 FlightSearch.jsp가 아닌 Flight Search로 부르는것에 주목하라. FlightSearch JSP가 foobar 여행사 웹 사이트상에서 사용자가 보는 전체 페이지의 바디이기 때문이다.

이제부터 FlightSearch.jsp를 있는 그대로 적용할 것이다. 앞으로 진행해가면서 변경할 것이다. 여러분은 Flight Search 페이지용 정의와 함께 Tiles 정의 파일을 만들어야 한다. Listing 7은 Tiles 정의 파일의 Flight Search Page용 엔트리이다. 마스터 레이아웃 템플릿과 extends 애트리뷰트가 재사용되었다.

Listing 6. Flight Search 레이아웃 예제
 <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <%@ taglib uri="http://jakarta.apache.org/struts/tags-faces"prefix="s" %> <!-- Layout component parameters: header, menu, body, footer --> <s:html> <head> <title> <tiles:getAsString name="title"/></title> <s:base/> </head> <body> <TABLE border="0" width="100%" cellspacing="5"> <tr> <td><tiles:insert attribute="header"/></td> </tr> <tr> <td><tiles:insert attribute="body"/></td> </tr> <tr><td><hr></td></tr> <tr> <td><tiles:insert attribute="footer" /></td> </tr> </TABLE> </body> </s:html> 


Listing 7. Flight Search Page에 대한 Tiles 정의.
 <!-- Master Layout definition --> <definition name="foobar.master-layout" path="/faces/layout/MasterLayout.jsp"> <put name="title" value="Welcome to Foo Bar Travels" /> <put name="header" value="/faces/common/header.jsp" /> <put name="footer" value="/faces/common/footer.jsp" /> <put name="body" value="" /> </definition> <!-- Definition for Flight Search Page --> <definition name="/foobar.flight-search" extends="foobar.master-layout"> <put name="body" value="/faces/FlightSearch.jsp" /> </definition> 

응답 커밋
Flight Search Form에 액세스를 시도하자마자 보게될 첫 번째 문제가 바로 이것이다. 스택 트레이스를 자세히 살펴보자. 문제는 com.sun.faces.lifecycle.ViewHandlerImpl 클래스에 있다는 것을 알 수 있다. 이것은 ViewHandler 인터페이스를 구현하는 JSF-RI 클래스이다.

그림 2ViewHandler가 수행하는 역할이다. 이것은 리퀘스트를 다음 페이지로 보내는 클래스이다. 리퀘스트를 보낼 때 보내기 전에 응답 상태를 검사하지 않는다. 이것은 Tiles를 사용할 때만 발생하는데 Tiles는 JSP 페이지를 응답에 포함시키기 때문이다. 그리고 JSF-RI는 첫 번째 포워드 후에 응답을 커밋하고 그런다음 다시 다음 Tiles로 보내기를 시도한다.

이 문제를 해결하려면 응답이 커밋 되었는지의 여부를 결정할 수 있도록 응답 상태를 검사할 커스텀 ViewHandler를 구현해야 한다. 응답이 커밋되지 않으면 리퀘스트는 다음 페이지로 넘어간다. 그렇지않으면 리퀘스트는 포함되고 적당한 JSP가 디스플레이된다. ViewHandler 인터페이스를 구현하고 필요한 renderView() 메소드를 구현하는 STFViewHandlerImpl 클래스를 만들 것이다. (Listing 8):

Listing 8. STFViewHandlerImpl의 renderView() 메소드
 RequestDispatcher rd = null; Tree tree = context.getTree(); String requestURI = context.getTree().getTreeId(); rd = request.getRequestDispatcher(requestURI); /** If the response is committed, include the resource **/ if( !response.isCommitted() ) { rd.forward(request, context.getServletResponse()); } else { rd.include(request, context.getServletResponse()); } 

커스텀 ViewHandler를 구현했으니 JSF-RI에게 디폴트 구현 대신 자신의 ViewHandler를 사용한다고 어떻게 공지하겠는가? 이 질문에 대한 답으로 FacesServlet의 작동을 이해해야 한다.

Faces 초기화 과정 중 FacesServletLifecycleFactory 구현에 Lifecycle 클래스 구현을 리턴할 것을 의뢰한다. (Listing 9):

Listing 9. FacesServlet의 Faces 초기화
 //Get the LifecycleFactory from the Factory Finder LifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory("javax.faces.lifecycle.LifecycleFactory"); //Get the context param from web.xml String lifecycleID = getServletContext().getInitParameter("javax.faces.lifecycle.LIFECYCLE_ID"); //Get the Lifecycle Implementation Lifecycle lifecycle = factory.getLifecycle(lifeCycleID); 

Lifecycle 구현 객체는 Render Response 단계에서 사용될 ViewHandler를 갖고 있다. Lifecycle 구현에 setViewHandler 메소드를 호출하여 ViewHandler 구현이 디폴트가 되도록 할 수 있다.

이제 질문은 디폴트 Lifecycle 구현을 어떻게 얻을 수 있느냐이다. 답은 이럴 필요가 없다는 것이다. 그저 새로운 구현을 만들어 고유 ID를 가진 LifecycleFactory로 이를 등록하면된다. (Listing 10):

Listing 10. 커스텀 ViewHandler와 Lifecycle 등록하기
 //Get the LifecycleFactory from the Factory Finder LifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory("javax.faces.lifecycle.LifecycleFactory"); //Create a new instance of Lifecycle implementation - //com.sun.faces.lifecycle.LifecycleImpl //According to the documentation, factory.getLifecycle("STFLifecycle") //should work, but JSF-RI has a defect. //Hence this workaround of creating a RI class explicitly. LifecycleImpl stfLifecycleImpl = new LifecycleImpl(); //Create a new instance of our STFViewHandler and set it on the Lifecycle stfLifecycleImpl.setViewHandler(new STFViewHandlerImpl()); //Register the new lifecycle with the factory with a unique //name "STFLifecycle" factory.addLifecycle("STFLifecycle", stfLifecycleImpl); 

lifecycleIdSTFLifecycle 처럼 하드코딩된 것을 볼 수 있다. 사실 그렇지 않다. Listing 9를 다시 보면 명확해진다. FacesServlet은 lifecycle ID를 javax.faces.lifecycle.LIFECYCLE_ID라는 이름과 함께 web.xml 파일에서 선언된 콘텍스트 매개변수에서 취한다:

 <context-param> <param-name>javax.faces.lifecycle.LIFECYCLE_ID</param-name> <param-value>STFLifecycle</param-value> </context-param> 

FacesServlet이 초기화 동안 Lifecycle 구현 클래스에 대해 결정하기 때문에 Listing 10의 코드는 FacesServlet이 초기화 되기 전에 실행되어야 한다. 또 다른 서블릿을 만들어 FacesServlet 전에 이를 초기화하여 이를 수행할 수 있다.

보다 현명한 방법은 ServletContextListener 인터페이스를 구현하는 것이다. 이 클래스는 두 개의 메소드인 contextInitialized()contextDestroyed()를 선언한다. 이것은 웹 애플리케이션이 구현될 때와 웹 애플리케이션이 없어지기 전에 호출된다. Listing 10의 코드는 contextInitialized() 메소드에서 실행되고 커스텀 ViewHandlerSTFLifecycle이라는 이름으로 구분된 Lifecycle로 등록된다. FacesServlet도 가능하다. ServletContextListener 클래스 자체는 web.xml 파일에서 선언된다.

 <listener> <listener-class>foo.bar.stf.application.STFContextListener </listener-class> </listener> 

이것이 Lifecycle을 커스텀 ViewHandler로 등록하는 유일한 접근방식은 아니다. 사실 FactoryFinder는 고유의 발견 알고리즘을 구현하여 LifecycleFactory를 포함하여 Factory 객체를 발견한다. 이 메커니즘은 쉽기는 하지만 매력적이지는 않다.

404 Resource Not Found
커밋된 응답 문제가 해결되면 아무 Tiles 스팩 링크를 클릭하거나 Faces 응답을 렌더링하는 URL을 입력한다. 이 경우 URL을 입력하여 FlightSearchForm을 디스플레이 할 수 있다.

이렇게 하면 foobar.flight-search - 404 Resource Not Found 에러가 나타난다. foobar.flight-search는 Flight Search 페이지용 Tiles 정의 이름이다. FacesRequestProcessor는 Tiles 리퀘스트를 처리 할 능력이 없다. 따라서 에러가 생긴다.

이 문제를 해결하려면 STFRequestProcessor라는 새로운 리퀘스트 프로세서를 만들어야 한다. 지금까지는 FacesRequestProcessor의 모든 코드를 새로운 클래스에 복사했다. 유일한 차이점은 일반적인 RequestProcessor를 하위클래스로 나누는 대신 STFRequestProcessorTilesRequestProcessor를 하위클래스로 나눈다는 점이다. 이 새로운 RequestProcessor가 Tiles 리퀘스트를 핸들할 수 있다. (Listing 11)

Listing 11. STFRequestProcessor.java
 public class STFRequestProcessor extends TilesRequestProcessor { protected void doForward(String uri, HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException { //copy code from FacesRequestProcessor } protected void doInclude(String uri, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { //copy code from FacesRequestProcessor } protected String processPath(HttpServletRequest request, HttpServletResponse response) throws IOException { //copy code from FacesRequestProcessor } protected String processPopulate(HttpServletRequest request, HttpServletResponse response) throws IOException { //copy code from FacesRequestProcessor } private void selectTree(FacesContext context, String uri) { //copy code from FacesRequestProcessor } } 

알다시피 Struts 프레임웍용 RequestProcessor는 struts-config.xml 파일에서 지정된다. STFRequestProcessor는 다음 엔트리가 struts-config.xml 파일에 추가될 때 프로세서가 된다.

 <controller processorClass="foobar.stf.application.STFRequestProcessor" /> 

Form submission
STFRequestProcessor 덕택에 Flight Search 페이지를 검색하고 볼 수 있다. 하지만 Flight Search 형식을 제출하자마자 응답으로 같은 형식을 받게된다. 그렇지만 헤더와 풋터가 없다. 밸리데이션 에러도 없다. 사실 밸리데이션 자체가 없는것이다.

어떤 상황인지 알고싶다면 Flight Search 페이지로 다시가서 HTML 소스를 보자. 다음과 같은 엔트리를 보게 될 것이다.

 <form name="FlightSearchForm" method="post" action="/flightapp/faces/FlightSearch.jsp"> 

형식 액션이 대신 JSP 페이지를 가르키고 있다. 이것이 바로 문제이다. Tiles와 Struts-Faces를 사용했기 때문에 발생하는 새로운 문제는 아니다. 이것은 형식 액션과 같은 JSP 이름을 갖기위한 Struts-Faces의 디폴트 작동이다. 이것은 하나의 JSP 페이지를 갖게 되면 문제없이 실행된다. Listing 3은 원래 FlightSearch.jsp이다. 액션을 다음과 같이 변경해보자.

 <s:form action="/listFlights.do> 

물론 변경 자체로는 문제를 해결할 수 없다. 이정도의 변경으로는 STFRequestProcessorActionForm을 찾을수 없다는 것도 알게될 것이다. 더 많은 변경이 필요하다.

계속 진행하기 전에 그림 5를 보자. Struts-Faces 형식용 faces 응답을 렌더링할때의 이벤트 결과를 보여주고 있다. FormComponent의 하이라이트된 createActionForm()메소드를 제외하고는 그림 3과 같다. Struts-Faces API에 의해 제공된 FormComponent 클래스는 javax.faces.component.UIForm의 특별한 서브클래스고 리퀘스트 또는 세션 범위에서 형식 빈들의 자동 생성을 지원한다.

그림 5. Struts-Faces 응답 렌더링
그림보기.

createActionForm() 메소드는 액션 이름을 사용하여 Struts 설정 파일에서 ActionMapping을 얻는다. /listFlights.doActionMapping이 없기때문에 Struts는 ActionForm을 찾을 수 없다.

org.apache.struts.util.RequestUtils를 사용하여 문제를 해결할 수 있다. RequestUtils의 정적 메소드인 getActionMappingName()은 경로(/x/y/z)나 접미사(.do) 매핑을 적절한 ActionMapping 으로 분해할 수 있다.

Listing 12는 createActionForm 메소드의 변경사항을 굵게 표시하여 보여주고 있다. Struts-Faces의 FormComponent를 변경하는 대신 FormComponent를 하위클래스로 나누고 createActionForm()메소드를 오버라이드 하여 새로운 STFFormComponent를 만든다.

Listing 12. FormComponent의 변경된 createActionForm() 메소드
 // Look up the application module configuration information we need ModuleConfig moduleConfig = lookupModuleConfig(context); // Look up the ActionConfig we are processing String action = getAction(); String mappingName = RequestUtils.getActionMappingName(action); ActionConfig actionConfig = moduleConfig.findActionConfig(mappingName); .... .... 

새로운 STFFormComponent에 변경이 더 필요하다. Struts-Faces는 액션 이름을 형식 이름으로 취급한다. 액션에는 .do 접미사가 있고 형식 이름에는 .do 접미사가 없기 때문에 변경이 필요하다. 따라서 새로운 속성인 actionSTFFormComponent에 추가하고 getAction()setAction() 메소드를 오버라이드한다.

FormRenderer 변경
Listing 10에서 했던 변경을 FormRendererencodeBegin 메소드에도 비슷하게 적용해야 한다.

FormRenderer를 하위클래스로 나누면 된다. HTML로 작성된 형식 액션도 변경해야 한다. (Listing 13):

Listing 13. FormRenderer 변경
 protected String action(FacesContext context, UIComponent component) { String treeId = context.getTree().getTreeId(); StringBuffer sb = new StringBuffer (context.getExternalContext().getRequestContextPath()); sb.append("/faces"); // sb.append(treeId); -- This is old code, replaced with // the two lines below. STFFormComponent fComponent = (STFFormComponent) component; sb.append(fComponent.getAction());  return (context.getExternalContext().encodeURL(sb.toString())); } 

FormTag 변경
이미 알고있겠지만 컴포넌트와 렌더러를 변경할 때 이 태그 역시 변경해야 한다. 이 경우 Struts-Faces의 FormTag에서 하위클래스로 나누어 STFFormTag 라는 새로운 태그를 만든다. 기능적인 부분은 바

'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] Log4J...  (0) 2004.03.30
[펌] JSF 사용하기  (0) 2004.03.29
[펌] 리팩토링(Refactoring)  (0) 2004.03.29
[펌] 스트럿츠 이디엄  (0) 2004.03.29
[펌] 동적 서블릿 이미지 메이킹을 마음대로!  (0) 2004.03.29
Posted by tornado
|

출처 : http://www-903.ibm.com/developerworks/kr/java/library/j-jmx3.html

JMX Agent를 네트워크 관리 시스템에 연결하기

Level: Intermediate

Sing Li
작가, Wrox Press
2002년 12월 17일

실제 Network Management System (NMS)를 사용하여 JMX로 만들어진 자바 애플리케이션을 모니터링한다. NMS/JMX 통합에 사용된 일반적인 기술은 물론 JMX를 전개할 때 발생하는 일반적인 어려움들을 설명한다.

JMX는 자바 플랫폼의 새로운 표준 확장으로서 현대적 Network Management Systems (NMS)를 통해 장치, 애플리케이션, 서비스들을 관리 및 제어, 감시할 수 있다. JMX가 소프트웨어 애플리케이션/서비스를 어떻게 활용하는지를 확실히 알고 싶다면 실제 오픈 소스 NMS(OpenNMS)를 사용하여 ClickMeter 애플리케이션을 모니터링한다. 광범위한 가용성과 단순한 디자인 때문에 OpenNMS를 선택했다. 여러분들도 다른 NMS 제품에 설명된 통합 기술을 적용할 수 있다. OpenNMS 스팩으로 작업하기 전에 일반적인 디자인 엘리먼트와 대부분의 NMS 제품이 공유하는 작동 모델을 보도록 하자.

네트워크 관리 시스템- 그 근본적인 복잡성
네트워크 관리 사용자 인구가 다양해진 덕택에 지금의 NMS 제품은 가장 광범위하고 복잡한 소프트웨어 시스템이 되었다. 예를 들어 분산 작동 센터를 통해 통신 네트워크 장비를 관리 및 감시하는 다국적 조직은 가상 프라이빗 네트워크(VPN), PC 자산, 데이터 통신 자산, 애플리케이션 서버 관리에 필요한 지역 비지니스의 다양한 요구를 안게 될 것이다.

전형적인 NMS는 수천, 수만, 심지어 수백만 엔드포인트(노드)를 관리해야 한다. NMS의 목표 고객들은 중간 규모 이상의 조직들이다. 이러한 고객들은 다양한 비지니스를 하기 때문에 NMS는 채택성이 높은 설정 메커니즘과 고객의 다양한 요구를 만족시킬 사용자 인터페이스를 갖춰야 한다. 전형적인 NMS 솔루션의 고비용을 정당화 할 수 있어야 한다.

관련 기술자료

Part 1, "Management, JMX 1.1 style" (2002년 9월)

Part 2, "Beans, JMX 1.1 style" (2002년 10월)

OpenNMS: 오픈 소스 NMS
OpenNMS는 오픈 소스 분야에서는 하나의 소프트웨어 시스템이다. 이것은 개발 커뮤니티에 복잡하지만 채택가능한 NMS 솔루션의 실행파일과 소스 코드를 무료로 제공하려는 시도이다.

OpenNMS는 레거시 없이 탄생했기 때문에 보다 새로운 접근방식이 필요했다. OpenNMS는 사용자 중심의 NMS이며 전형적인 네트워크 관리자를 싱글 포인트 포커스로 두어 필요한 기능을 결정하게한다. 몇몇 안되는 구성원으로 이루어진 초기 팀은 네트워크 관리 컨설턴트이며 그들의 지식을 총동원하여 객체, 태스크, 워크플로우 관련 작동 모델을 규명했다. 이것은 상용 NMS 제품에 대비되며 디바이스 중심, 네트워크 중심, 소프트웨어 서비스 중심 성격이된다.

NMS 분석
많은 NMS 제품들은 애플리케이션의 특정 도메인, 코드 유산, 벤더와 상관 없이 유사한 개념적 구성을 갖고있다. 그림 1은 이러한 일반적 구성을 설명하고 있다.

그림 1. NMS의 개념적 구성
Figure 1. Conceptual composition of an NMS

구성은 일반적으로 3-TIER 이다. 프론트(front) 티어는 관리되고 있는 디바이스와 서비스의 네트워크, 시스템 사용자, 외부 시스템과 인터페이싱한다. 미들 (middle) 티어에는 NMS의 기능 세트를 제공하는 많은 로직이 포함되어 있다. 백엔드(back-end) 티어는 데이터를 유지하고 조작한다.

광범위한 정보와 동적이지만 강력한 기반에서 트래킹되어야 할 복잡한 상호관계 때문에 NMS는 상용 관계형 데이터베이스 관리 시스템(RDBMS)을 백엔드 티어에 전개했다.

첫 번째 티어에서 모니터링, 관리, 제어 컴포넌트는 NMS 작동을 실행해야 하는 많은 동시 태스크들로 구성되어 있다. 액티브 네트워크 발견, 디바이스 정지 및 서비스 등록하기, 디바이스, 서비스, 고급 분산 에이전트에서 비동기식 이벤트 개입 및 프로세싱을 맡고 있다. 이 컴포넌트는 설정 로직, 제공 로직, 워크플로우 실행자, 미들 티어의 커스텀 로직에서 온 작동들 역시 수행한다.

OpenNMS의 역사
2002년 봄, OpenNMS 1.0이 탄생했다. 오픈 소스로 사용할 수 있는 최초의 엔터프리이즈급 네트워크 관리 플랫폼이 탄생한 것이다. 2002년 5월, 프로젝트의 담당자들은 OpenNMS에 지원과 컨설팅 서비스를 제공하는 Sortova Consulting Group으로 옮겼지만 개발 진화는 계속되고 있다.

1999년, 시스템 프로그래머들과 네트워크 관리 컨설턴트들은 관리 제품의 현황에 실망하고 해결책을 모색할 것을 결정했다. 상용 NMS 제품의 천문학적 비용을 감지한 팀은 오픈 소스 개발 모델을 사용하여 보다 나은 것을 개발하기로 결정했다.

또한, 프론트 티어는 사용자 인터페이스 컴포넌트이다. NMS는 "fat client GUI" 인터페이스는 물론 쉽게 커스터마이징 할 수 있는 웹 기반 사용자 인터페이스도 가지고 있다. 리포팅 장치가 이 레이어에 있으며 UI와 인터랙팅하여 실시간 소프트 카피 리포트는 물론 배치 하드 카피 리포트도 제공한다.

첫 번째 티어의 외부 인터페이싱 컴포넌트는 사용자와 외부 시스템에 공지 프로세스를 할 수 있다. 페이저호출, 이메일, 전호 호출, 특정 이벤트를 외부 관리 시스템에 전한다. API와 확장 플러그인 기반구조로 인해 NMS는 커스터마이징 될 수 있고 다른 시스템과 인터페이싱 및 통합될 수 있다.

미들 티어는 NMS의 로직이 존재하고 있는 곳이다. NMS에 구별된 기능과 특성을 부여한다. 재고 또는 자산 관리, 데이터 수집과 분석, 사용자 또는 역할 관리, 워크 플로우 관리/실행 등이 알려진 기능들이다.

재고(또는 자산) 관리는 대부분의 NMS 제품의 주요 기능이다. 트래킹되는 아이템들은 NMS에서 장비, 서킷, 서버, 설치된 소프트웨어 서비스와 애플리케이션에 이르기까지 다양하다. 이 컴포넌트의 사용자 인터페이스와 애플리케이션 로직은 관리되는 다른 아이템들에게도 유연하게 적용될 수 있다.

데이터 수집과 분석 기능은 관리되고 있는 디바이스와 서비스에 대한 현재와 기존의 통계를 제공한다. 네트워크와 서브 네트워크, 디바이스/서비스 가용성을 드러내는 통계적 분석을 제공한다. 이 컴포넌트는 커스텀 워크플로우를 핸들하기 위해 워크플로우 실행 로직과 인터랙팅 할 수 있다.

사용자와 역할 관리 컴포넌트는 NMS에 다양한 레벨의 접근을 제공한다. 가장 넓은 네트워크는 하나 이상의 팀이 관리한다. 사용자들에게 할당된 역할은 디자인과 커스텀 보안 정책의 구현 뿐만 아니라 커스텀 워크플로우와 이벤트 증대 플로우에 사용될 수 있다.

워크플로우 관리 및 실행은 장단기 관리 태스크의 관리 및 자동 실행을 제공한다. 자동화된 커스텀 워크플로우는 다양한 조직의 다양한 비지니스 요구사항에 NMS가 채택되는데 필수적인 요소이다.

NMS 모든 NMS가 이 모든 컴포넌트들을 갖고 있는 것은 아니고 어떤 것은 보다 구체화된 변이를 갖고 있을 수도 있다. 이 글에서 검토되는 것들은 NMS를 구성하고 있는 것들에 대한 일반적 개요라 할 수 있다.

OpenNMS 아키텍쳐
그림 1을 레퍼런스로 사용하여 레퍼런스 구성과 OpenNMS의 아키텍쳐 사이의 구체적 구성도를 그릴 수 있다. 그림 2는 OpenNMS를 구성하고 있는 컴포넌트이다.

그림 2. OpenNMS의 컴포넌트
Figure 2. Components of OpenNMS

백엔드 티어에서 OpenNMS는 RDBMS로서 PostgreSQL (참고자료)을 사용한다. 프론트 티어에서 Apache Jakarta Tomcat (참고자료)의 JSP와 서블릿 기술을 사용하여 유연하게 커스터마이징 할 수 있는 사용자 인터페이스를 제공한다. 기존 Perl 기반의 사용자 인터페이스 역시 사용할 수 있다. 여러 관리 유틸리티들은 UNIX 쉘 스크립트와 Perl로 작성된다. OpenNMS는 그림 1의 모니터링, 관리, 제어 컴포넌트에 상응하는 컨커런트 자바 태스크를 사용하여 이 기능을 제공한다. 이러한 동시 태스크들은 그림 2에 원으로 표현되었다.

OpenNMS 데몬: 컨커런트(concurrent) 관리 태스크
OpenNMS의 모니터링, 제어, 데이터 수집 기능들은 데몬(daemon) 이라고 하는 컨커런트 태스크에 의해 핸들된다. (BSD UNIX 규약). 표 1은 컨커런트 관리 태스크들 이다.

표 1. OpenNMS의 컨커런트 관리 태스크

Concurrent Taskdaemon 이름 Description
Action daemonactiond자동 액션 실행 장치. 인커밍 이벤트에 근거한 자동화된 액션.
Collection daemoncollectd관리되는 노드에서 데이터 수집.
Capability daemoncapsd발견된 노드 상에서 기능 검사 수행. 알려진 서비스 프로토콜 지원을 위한 인터페이스 포트 점검.
DHCP daemondhcpdOpenNMS에 DHCP 클라이언트 기능 제공.
Discovery daemondiscovery관리되는 네트워크 노드의 초기 및 지속적인 발견.
Events manager daemoneventd다른 컨커런트 태스크에서 나온 이벤트의 관리 및 (RDBMS에) 저장
Notification daemonnotifd외부 공지 수행.
Outage manager daemonoutaged이벤트를 결합하여 각 관리되는 노드/서비스에 outage 뷰 제공.
Poller daemonpollerd관리되는 노드/서비스를 정기적으로 등록하여 작동 상태 결정.
RTC manager daemonrtcd실시간으로 데이터를 수집하여 관리되는 노드/서비스의 사용자 정의 카테코리에 가용성 정보 제공.
SNMP trap daemontrapdSNMP 트랩(이벤트) 핸들.
Threshold service daemonthreshd특정 임계값에 도달한 애트리뷰트 값에 근거하여 노드/서비스 감시.

OpenNMS 확장하기
OpenNMS의 프론트 티어에서 특정 수직 애플리케이션 도메인에 시스템을 적용하는 것은 매우 단순하다. JSP 기술의 유연성 덕택에 OpenNMS의 사용자 인터페이스는 자바 개발자들이 쉽게 커스터마이징할 수 있다. 새로운 NMS 애플리케이션 로직은 JSP 파일과 서블릿 코딩의 결합을 통해 빠르게 추가될 수 있다.

OpenNMS는 관리되는 디바이스/서비스용 SNMP 지원이 됨으로서 표준이 되었다. SNMP는 관리가 편리한 디바이스/서비스용 산업 표준이다. 이 표준은 OpenNMS가 TCP/IP 네트워크 상에서 다양하고 광범위한 기존 디바이스를 관리 할 수 있도록 한다.

SNMP 밖에서, OpenNMS는 대중적인 소프트웨어 서비스(FTP, 파일 서버, 데이터베이스 서버)를 탐지하고 관리할 수 있다. 서비스 스팩의 플러그인을 제공하고 이를 수행하는지를 감시한다. 새로운 플러그인과 모니터를 만듦으로서 OpenNMS는 새로운 디바이스 또는 서비스를 탐지하고 감시할 정도로 확장될 수 있다.

커스텀 기능의 플러그인 만들기
OpenNMS의 디스커버리 데몬(discovery)은 관리되는 네트워크의 초기 및 지속적인 발견을 맡고있다. 일단 discovery가 노드를 발견하면 기능 데몬에게 노드가 지원하는 서비스가 무엇인지를 요청한다. 기능 데몬은 특정 프로토콜 플러그인을 통해 반복하여 서비스가 지원하는 것을 검사한다. 커스텀 프로토콜 플러그인을 작성하는 것은 단순하다. 단계는 다음과 같다:

  1. org.opennms.netmgt.capsd.Plugin 인터페이스를 구현하는 클래스를 만든다. 특히 이 인터페이스는 플러그인이 구현해야 하는 세 개의 메소드를 갖고있다. (표 2):

    표 2. org.opennms.netmgt.capsd.Plugin 인터페이스의 메소드

    메소드 이름과 사인Description
    String getProtocolName();이 플러그인의 프로토콜 이름을 리턴한다.
    Boolean isProtocolSupported(java.nnet.InetAddress address);
    boolean isProtocolSupported(java.nnet.InetAddress address, java.util.Map properties);
    필요할 경우 인커밍 매개변수용 프로퍼티 맵을 사용하여 이 프로토콜이 구체적인 InetAddress의 지원을 받는지를 결정한다.



  2. /etc/capsd-configuration.xml 파일에 프로토콜-플러그인 정의를 만든다. 이것은 capsd가 시작하는 동안 사용할 프로토콜 플러그인을 결정하기 위해 읽게된다.

커스텀 폴러(poller) 모니터 만들기
OpenNMS가 관리 노드상의 서비스를 결정한 후에 폴러 데몬(pollerd)을 사용하여 관리되는 디바이스/서비스를 정기적으로 등록한다. 그들이 작동하고 있다는 것을 확인하기 위해서이다. pollerd는 지정된 폴러 모니터 "플러그인"을 통해 반복하면서 개별 서비스 폴들을 수행한다:

  1. org.opennms.netmgt.poller.ServiceMonitor 인터페이스를 구현하는 클래스를 만든다. 특히 이 인터페이스는 모니터가 구현해야 하는 다섯 개의 메소드를 갖고있다. (표 3):

    표 3. org.opennms.netmgt.poller.ServiceMonitor 인터페이스의 메소드

    메소드 이름과 사인Description
    void initialize(java.util.Map parameters);
    void initialize(org.opennms.netmgt.poller.NetworkInterface iface);
    첫 번째 형식은 시작하는 동안 호출되어 플러그인이 초기화 중에 실행할 수 없도록 할 기회를 제공한다. 두 번째 형식은 새로운 노드가 발견될 때마다 서비스를 지원하기 위해, 그리고 첫 번째 poll()이 호출되기 전에 호출된다.
    void release();
    void release(java.net.NetworkInterface iface);
    첫 번째 형식은 OpenNMS 정지 시 호출된다. 두 번째 형식은 서비스를 지원하기 위해 발견된 노드가 더이상의 폴링 스케쥴에서 제거될 때 마다 호출된다.
    int poll(java.net.NetworkInterface iface, org.opennms.netmgt.utils.EventProxy eproxy, java.util.Map parameters);감시되고 있는 서비스를 여전히 사용할 수 있는지를 결정할 때 사용되는 메소드이다. 서비스가 실행 중일 경우에는 ServiceMonitor. SERVICE_AVAILABLE을그렇지 않으면 ServiceMonitor. SERVICE_UNAVAILABLE을 리턴해야 한다.

  2. /etc/poller-configuration.xml 파일에 서비스 정의와 모니터 정의를 만든다.

OpenNMS 다운로드 및 설치
리눅스 배포판과 OpenNMS의 시스템 요구사항을 숙지해야 한다. 자세한 설치 및 업데이트 절차는 이 글에서 다루지는 않는다. 참고자료 섹션에서 OpenNMS의 최신 버전 다운로드 정보를 검색할 수 있다.

OpenNMS의 최신 안정 버전은 1.0.2이다. 이 글의 모든 코드는 이 안정 버전으로 테스트를 한 것이다. OpenNMS1.0.2는 세 가지 리눅스 스팩(Red Hat Linux 7 & 8, Mandrake 8)에서 테스트되었다. 복잡한 시스템으로 초기 실험 시 이 세 배포판 중 하나를 사용하기 바란다.

OpenNMS를 사용하여 JMX 가능한 ClickMeter 감시하기
capsd 플러그인과 pollerd 모니터를 만듦으로서 OpenNMS와 JMX 구동의 ClickMeter를 통합할 수 있다는 것을 알고 있다. 하지만 한가지 질문이 남아있다. OpenNMS는 JMX를 지원하지 않는가?

심지어 유명한 상용 NMS 오퍼링에서도 같은 경향을 엿볼 수 있다. JMX가 기본적으로 지원되지 않는다. JMX 구동의 디바이스와 서비스는 진행중에 있기 때문이기도하고 SNMP 호환이 제 3의 디바이스와 서비스의 관리에 제공되었기 때문이기도 하다.

JMX의 통합 과정이 NMS 벤더들 사이에서 비교적 느리게 진행되는 중요한 이유가 있다. JMX 사용 레벨과 에이전트 레벨이 1.1 스팩에는 적절히 정의되어있더라도 네트워크를 통해 JMX 에이전트에 액세스 하는 방법은 아직까지는 모호하다.

JMX Remoting 딜레마
JMX 에이전트가 네트워크 관리 애플리케이션이나 분산 서비스에 의해 네트워크를 통해 액세스되는 방법은 JMX Remoting이라고 알려져있고 이것이 JSR 160의 핵심 사안이다.(참고자료).

JSR 160은 JMX 에이전트의 발견이 네트워크를 통해서나 에이전트의 기능에 원격으로 액세스하는 구체적인 방법을 통해 어떻게 수행되는지를 정한다. 여기에는 사용될 프로토콜 어댑터 관련 스팩들이 포함될 것이다. JMX Remoting 1.2 스팩이 마감될 때까지 OpenNMS와 ClickMeter MBean을 관리하는 JMX 에이전트 사이의 통신 대안을 찾아야 한다.

JMX remoting 솔루션
물론, JMX 에이전트의 주요 목적은 이것이 관리하는 JMX MBeans로의 원격 액세스를 제공하는 것이다. 에이전트들은 커넥터와 프로토콜 어댑터를 사용하여 NMS 애플리케이션 또는 분산 서비스들과 통신한다.

ClickMeter의 경우 JMX 레퍼런스 구현의 간단한 에이전트를 사용했다. 여기에는 HTTP 프로토콜 어댑터가 포함되어 있다. 이 간단한 에이전트는 HTTP 프로토콜 어댑터를 통해 모든 MBeans의 애트리뷰트와 작동을 노출한다.

HTTP 어댑터를 사용하여 작업을 최소화하기
OpenNMS와 ClickMeter MBean 사이의 통신을 위해 JMX 레퍼런스 구현에서 간단한 에이전트와 HTTP 프로토콜을 쉽게 사용할 수 있다. 그림 3은 통신 방식을 제안한 것이다. 작동중에 OpenNMS의 capsd는 탐지된 노드를 검사하여 "CLICKMETER" 서비스가 지원되는지의 여부를 본다. pollerd는 탐지된 서비스를 정기적으로 등록하여 이들이 작동중임을 확인한다. 이러한 탐침들을 수용하기 위해서 incPanelValue 호출 작동을 사용할 수 있다. 탐침 메커니즘으로서 incPanelValue 작동을 사용하는 것의 이점은 OpenNMS의 탐지 및 등록 액션을 시각적으로 볼 수 있다는 것이다. ClickMeter의 값은 탐침 때마다 증가한다.

그림 3. HTTP 어댑터로 ClickMeter에 액세스하기
Figure 3. Accessing ClickMeter with the HTTP adapter

이제 남은 일은 ClickMeter MBean 에서 incPanelValue 작동에 액세스 하기 위해 HTTP 프로토콜 어댑터를 사용하는 방법을 결정하는 일이다. 브라우저를 사용하여 다음 URL에서 JMX 에이전트에 액세스할 수 있다:

http://<host of agent>:8082/

물론 이를 수행하기 전에 에이전트가 실행되는지 확인해야 한다. 그런 다음 버튼을 클릭하여 노출된 incPanelValue 작동을 실행한다. (그림 4).

그림 4. 탐침 URL 결정하기
Figure 4. Determining the probe URL

그림 4 에서, 이 작동에 액세스하기 위해 사용된 URL은 다음과 같다:

http://<host of agent>:8082/InvokeAction//MBean:name=ClickMeter/action=incPanelValue?action=incPanelValue

ClickMeter 애플리케이션이 올바르게 기능을 발휘하고 있다면 결과페이지에는 incPanelValue Successful 이라는 문장이 뜬다. 이 정보를 사용하여 OpenNMS에서 원격으로 MBean에 액세스하겠다.

OpenNMS 통합
이제 새로운 "CLICKMETER" 서비스를 OpenNMS에 통합할 준비가 되었다. 이를 위해 capability 데몬(capsd)용 플러그인과 poller 데몬(pollerd)용 모니터를 만들것이다.

OpenNMS 디스커버리용 ClickMeterPlugin
capability 데몬 플러그인 코드는 org.opennms.netmgt.capsd.ClickMeterPlugin 클래스에 있다. 이 플러그인은 org.opennms.netmgt.capsd.Plugin 인터페이스를 구현해야 한다는 것을 알고있다. Listing 1은 AbstractPlugin 의 클래스이다. OpenNMS가 제공되는 클래스는 두 개의 매개변수 추출 메소드(getKeyedInteger() & getKeyedString())를 제공한다.

Listing 1. ClickMeterPlugin 관리 클래스

public final class ClickMeterPlugin extends AbstractPlugin {    private static final String   PROTOCOL_NAME = "CLICKMETER";    private static final int  DEFAULT_PORT  = 8082;    private final static int  DEFAULT_RETRY = 0;    private final static int  DEFAULT_TIMEOUT = 5000; // in milliseconds

getProtocolName() 메소드는 Listing 2의 코드로 간단히 구현된다:

Listing 2. getProtocolName() 메소드

public String getProtocolName() {     return PROTOCOL_NAME;     }

Listing 3은 isProtocolSupported() 메소드가 구현되는 방법이다:

Listing 3. isProtocolSupported() 메소드

public boolean isProtocolSupported(InetAddress address) {  return isClickMeter(address, DEFAULT_PORT, DEFAULT_RETRY, DEFAULT_TIMEOUT);  }

보다 긴 버전의 isProtocolSupported() 메소드에서는, 원격 JMX 에이전트에 액세스하는 URL을 만드는데 사용되는 인커밍 매개변수를 처리한다. 일단, IP와 포트가 매개변수에서 얻어지면, isProtocolSupported()isClickMeter() 메소드를 호출하여 ClickMeter가 이 노드에서 활성인지를 결정한다. Listing 4는 isClickMeter()에 대한 코드이다. URL용으로 정의된 두 개의 상수가 ClickMeter의 incPanelValue 작동을 위한 HTTP 프로토콜 어댑터 액세스 URL과 상응한다는 것에 주목하라.

Listing 4. isClickMeter() 메소드

private final static String CLICK_METER_BEAN_LOCATOR_URL = "/InvokeAction//MBean:name=ClickMeter/action=incPanelValue?   action=incPanelValue";      private final static String CLICK_METER_ID = "incPanelValue Successful";      private boolean isClickMeter(InetAddress host, int port,         int retries, int timeout) {          Category log = ThreadCategory.getInstance(getClass());          boolean foundClickMeter = false;          for (int attempts=0; attempts <= retries && !foundClickMeter;             attempts++)          {                                    URL jmxLink = null;                  InputStream iStream = null;               try               {                      String hostIP = host.getHostAddress();                      jmxLink = new URL("http", hostIP, port,                         CLICK_METER_BEAN_LOCATOR_URL);                      if (scanURL(jmxLink, CLICK_METER_ID, log)) {                            foundClickMeter = true;                            break;   // get out of the for loop                           }                     }          catch(IOException e) {                    log.info("ClickMeterPlugin: Error communicating                        with host " + host.getHostAddress(), e);                    foundClickMeter = false;              }          }          return foundClickMeter;  }

scanURL() 메소드(Listing 5)는 인자로서 URL과 스트링을 취하는 헬퍼 메소드이다. 이것은 URL에 액세스하고 결과 페이지에서 지정된 스트링을 찾는다. 스트링이 발견되면 true를 리턴한다. 그렇지 않을 경우 false를 리턴한다. 이 경우, ClickMeter의 incPanelValue 작동용 URL에 액세스한 후에 incPanelValue Successful 스트링을 찾고있다.

Listing 5. scanURL() 메소드

private boolean scanURL(URL inURL, String toSearch, Category log)    {                  InputStream iStream = null;           try {                                            iStream = inURL.openStream();                     BufferedReader urlReader =                        new BufferedReader(new InputStreamReader(iStream));                          String curLine = urlReader.readLine();                        do  {                                if (curLine.indexOf(toSearch) != -1) {                                       return true;                                      }                           curLine = urlReader.readLine();                         } while (curLine != null);                        }                 catch  (IOException e) {                              e.fillInStackTrace();                      log.debug("ClickMeterMonitor.poll:                         Error reading from URL:"                         + inURL, e);                      }               finally  {                   try  {                       if(iStream != null)                             iStream.close();                    }                    catch(IOException e) {                        e.fillInStackTrace();                        log.debug("ClickMeterMonitor.poll:                           Error closing stream to URL.", e);                    }               }      return false;   }}

플러그인 통합에 필요한 두 번째 단계는 <OpenNMS installation directory>/etc/capsd-configuration.xml 파일을 편집하고 프로토콜-플러그인 정의를 추가하는 것이다.(Listing 6):

Listing 6. 프로토콜-플러그인 정의

  <protocol-plugin     protocol="CLICKMETER"      class-name="org.opennms.netmgt.capsd.ClickMeterPlugin"     scan="on" >    <property key="port" value="8082"/>    <property key="timeout" value="2000"/>    <property key="retry" value="1"/>  </protocol-plugin>

OpenNMS가 관리되는 노드를 검사해야할 IP 범위를 제한하기 위해 discovery-configuration.xml 파일을 편집하고 싶을 것이다. Listing 7에서는 두 개의 IP로 제한하여 ClickMeter 애플리케이션이 빨리 위치할 수 있도록 한다:

Listing 7. 디스커버리 범위 제한

  <include-range retries="2" timeout="3000">    <begin>192.168.23.75</begin>    <end>192.168.23.76</end>  </include-range>

OpenNMS용 ClickMeterMonitor
다음에는, poller 데몬(pollerd) 모니터 플러그인, org.opennms.netmgt.poller.ClickMeterMonitor 클래스를 만든다: 이 클래스는 org.opennms.netmgt.poller.ServiceMonitor 인터페이스를 구현해야 한다. OpenNMS는 이 인터페이스의 모든 메소드용 디폴트 구현을 갖고있는 기본 클래스(org.opennms.netmgt.poller.IPv4Monitor)를 제공한다. 특별한 초기화나 릴리스가 필요없기 때문에 poll() 메소드를 오버라이딩 하면된다.(Listing 8):

Listing 8. poll() 메소드

public int poll(NetworkInterface iface, Map parameters) {       if(iface.getType() != NetworkInterface.TYPE_IPV4)              throw new NetworkInterfaceNotSupportedException("Unsupported                     interface type, only TYPE_IPV4 currently supported");    Category log = ThreadCategory.getInstance(getClass());    int retry   =       getKeyedInteger(parameters, "retry", DEFAULT_RETRY);    int port    =       getKeyedInteger(parameters, "port", DEFAULT_PORT);    int timeout =       getKeyedInteger(parameters, "timeout", DEFAULT_TIMEOUT);    InetAddress ipv4Addr = (InetAddress)iface.getAddress();            String hostIP = ipv4Addr.getHostAddress();    if (log.isDebugEnabled())               log.debug("ClickMeterMonitor.poll: Polling interface: "                      + hostIP + " timeout: " + timeout + " retry: " + retry);        int serviceStatus = ServiceMonitor.SERVICE_UNAVAILABLE;    for (int attempts=0; attempts <= retry && serviceStatus !=               ServiceMonitor.SERVICE_AVAILABLE; attempts++) {                  URL jmxLink = null;                  InputStream iStream = null;        try {                          jmxLink = new URL("http", hostIP, port,                                     CLICK_METER_BEAN_LOCATOR_URL);                        if (scanURL(jmxLink, CLICK_METER_ID, log)) {                            serviceStatus = ServiceMonitor.SERVICE_AVAILABLE;                            break;                             }                     }          catch(IOException e) {        e.fillInStackTrace();        log.debug("ClickMeterMonitor.poll:                IOException while polling address: "                + ipv4Addr, e);      }    }  // of for       return serviceStatus;  }

scanURL() 헬퍼 메소드(Listing 8)를 사용하여 ClickMeter MBean에 액세스한다는 것에 주목하라.

<OpenNMS installation directory>/etc/poller-configuration.xml 파일에서, 서비스 정의 엔트리를 추가해야 한다. Listing 9에서 폴링 간격을 10,000 밀리초로 정했다:

Listing 9. poller 데몬 서비스 정의

  <service name="CLICKMETER" interval="10000"     user-defined="false" status="on">      <parameter key="timeout" value="3000"/>      <parameter key="port" value="8082"/>  </service>

같은 poller-configuration.xml 파일에 모니터 정의를 삽입해야 한다. (Listing 10):

Listing 10. poller 데몬 모니터 정의

<monitor service="CLICKMETER"    class-name="org.opennms.netmgt.poller.ClickMeterMonitor" />

ClickMeter 서비스로 OpenNMS 테스트하기
소스에서 두 개의 플러그인을 구현하려면 compile.bat 파일을 사용한다. 몇몇 OpenNMS 라이브러리 파일을 <article code distribution>/lib 디렉토리에 복사해야 할 것이다. compile.bat 파일은 dwClickMeterJMX.jar를 만든다. 이 결과 JAR 파일을 <OpenNMS installation directory>/lib 디렉토리에 놓으면 OpenNMS는 시작할 때 자동적으로 새로운 플러그인을 로딩하게 된다.

반드시 OpenNMS의 웹 기반 GUI에 액세스하여 시스템을 시작하라. 일반적인 URL은 다음과 같다:

http://<host of OpenNMS>:8080/opennms/

프롬프트되면 userid에는 admin을 패스워드에는 admin을 사용한다. (그림 5):

그림 5. OpenNMS 시작 스크린
Figure 5. OpenNMS startup screen

OpenNMS가 시작하는 동안 두번이나 빠르게 증가하는 ClickMeter 카운트를 보게된다. ClickMeter는 pollerd가 작동할 때 보통 10초 간격으로 증가한다.

일단 capsd가 ClickMeter 애플리케이션을 탐지하면 이를 관리 서비스로서 RDBMS에 추가 할 것이다. 제품의 경우 OpenNMS의 자산 관리 기능들은 이 서비스를 보다 자세히 범주화 해 놓을 수 있다. 우리 실험에서는 OpenNMS GUI를 사용하여 ClickMeter 서비스를 연구하고 이에 개입된 자세한 이벤트를 볼 수 있다.(그림 6):

그림 6. ClickMeter 서비스를 관리하는 OpenNMS
Figure 6. OpenNMS managing ClickMeter service

참고자료

'JAVA > JSE' 카테고리의 다른 글

[펌] JAVA application 성능향상..  (0) 2004.10.07
[펌 http://raytrust.pe.kr/] signed applet  (0) 2004.09.06
[펌] javadoc 유틸리티  (0) 2004.09.01
[nio] 기초 정리 잘된곳  (0) 2004.08.09
[펌] 나만의 자바 컴포넌트를 만들자  (0) 2004.04.01
Posted by tornado
|

출처 : http://www-903.ibm.com/developerworks/kr/java/library/j-gss-sso.html

GSS-API와 Kerberos 티켓을 사용하여 SSO를 구현하는 클라이언트/서버 자바 애플리케이션 디자인

Level: Intermediate

Faheem Khan
컨설턴트
2003년 9월 9일

점점 더 많은 패스워드 보안 애플리케이션을 조직의 컴퓨터 환경에 추가한다면 이는 인증의 복잡함도 함께 가중시키는 것이고 결국 개발자와 사용자에게 부담을 주게 된다. 대부분의 엔터프라이즈 애플리케이션 통합 프로젝트들은 싱글사인온(SSO) 기능을 포함하고 있다. 이는 사용자가 다양한 애플리케이션을 사용하기 위해 단 한번만 로그인 하도록 하는 기능이다. 이 글에서 자바 플랫폼에 SSO를 구현하는 방법을 설명한다.

당신의 기업은 많은 자바 애플리케이션들을 구동하고 있으며 그 엔터프라이즈 리소스에 접근하기 위해서는 인증이 필요합니까? 그렇다면 싱글사인온(SSP) 보안 기능을 구현하여 사용자 침입을 최소화 할 필요가 있다. 이 글에서 Kerberos와 Java Generic Security Services API (GSS-API)를 사용하여 SSO를 구현하는 방법을 배우게 될 것이다. 우선 SSO의 정의를 설명하고 강력한 애플리케이션을 설명할 것이다. 그런 다음 Kerberos 기반의 SSO를 구현할 때 발생하는 메시지 교환 시퀀스도 연구한다. 자바 GSS-API와 GSS를 사용하여 SSO를 달성하는 전형적인 자바 애플리케이션 아키텍쳐를 간단히 소개한다. 마지막으로 이 모두를 종합하여 코드 예제를 선보인다. 자바 개발자가 GSS Kerberos 티켓으로 SSO를 어떻게 구현하는지 보게 될 것이다.

'싱글사인온'이란 무엇인가?
근본적으로 싱글사인온 인증이 의미하는 것은 인증 데이터의 공유이다. 예를 들어 웨어하우징 회사의 많은 사원들은 엔터프라이즈 리소스(데이터베이스 테이블 등)에 접근하여 그들의 업무에 필요한 것을 수행해야 한다. 동시에 다른 사원들도 작업 내용에 따라 다양한 리소스가 필요하다. 회계 담당자는 회계 관련 데이터베이스 테이블에 접근해야 하는 반면 판매 담당자는 판매 관련 데이터베이스 테이블에 접근해야 한다. CEO라면 기업의 데이터베이스 어디에나 접근이 필요할 것이다.

분명한 것은, 이 기업은 무엇보다도 제대로 된 인증 메커니즘이 필요하다. 어떤 사원이 특정 리소스에 접근을 시도했는지 결정할 수 있도록 말이다. 일단 기업 인증 모듈이 사원의 존재를 알면 엔터프라이즈 구현 안의 인증 모듈은 인증이 있는 사용자가 적절한 권한을 갖고 리소스에 접근했는지의 여부를 검사할 수 있다.

코드
j-gss-sso.zip 파일에는 이 글에 사용된 모든 코드가 포함되어 있다. 이 글의 각 리스팅은 파일 이름이 포함되어 있는 주석으로 시작한다. 이 이름들을 사용하여 각 리스팅을 j-gss-sso.zip 아카이브에 있는 상응하는 파일에 매치한다.

이 아카이브에는 Setup.txt 파일도 있다. 코드 실행 전에 이 파일을 읽기 바란다.

사원들이 인증용 유저네임과 패스워드를 사용한다고 가정해보자. 이 기업의 인증 모듈은 당연히 유저네임과 패스워드의 데이터베이스를 갖고 있을 것이다. 들어오는 인증 요청은 유저네임-패스워드 쌍으로 동반될 것이다. 이 인증 모듈은 내부 데이터베이스의 쌍과 비교를 하게 된다.

이제 우리의 웨어하우징 회사는 이 범위안에서 실행되는 여러 애플리케이션들을 갖고있다. 다양한 애플리케이션들이 같은 엔터프라이즈의 다른 모듈을 형성한다. 각각의 애플리케이션은 그 자체로 완벽하다. 이것 나름대로 사용자 기반의 여러 다른 티어(백엔드 데이터베이스, 비지니스 로직, 사용자용 GUI)를 갖고있다는 것을 의미한다. 엔터프라이즈 애플리케이션 통합(EAI)는 이와 같은 독립된 애플리케이션들을 하나의 엔터프라이즈에 통합하는 프로젝트를 일반적으로 가르킨다.

EAI 프로젝트에서 인증 프로세스에 두드러지는 하나의 일반적인 요소가 있다. 특정 애플리케이션의 사용자는 그 엔터프라이즈 내의 또 다른 애플리케이션에 액세스 해야한다. 예를 들어 판매 데이터베이스를 사용하고 있는 판매 담당자가 특정 부품을 검색하기 위해 재고 데이터베이스에 액세스 해야 할 경우도 있다. 이러한 유형의 크로스-애플리케이션 인증을 실현시킬 수 있을까?

두 가지 선택이 있다:

  • 두 애플리케이션에서 유저네임과 패스워드 데이터베이스를 중복시킬 수 있다. 따라서 이러한 두 개의 애플리케이션들이 웨어하우징 회사의 모든 사원들을 위해 인증 요청을 효율적으로 처리할 수 있도록 하는 것이다. 사용자는 두 개의 애플리케이션에서 개별적으로 인증을 처리하게 된다. 다시 말해서 자신의 유저네임과 패스워드로 들어가면서 두 애플리케이션 중 하나에 액세스하고 그 애플리케이션은 모든 인증 단계를 수행하게 된다. 유저네임과 패스워드 데이터베이스를 중복할 뿐만 아니라 인증 프로세스 오버헤드까지 중복하는 것이다. 이 솔루션에서 중복의 양은 명확해진다.

  • 두 번째 선택은 판매 애플리케이션과 재고 애플리케이션 간 싱글사인온을 통한 인증 데이터 공유를 가능하게 하는 것이다. 만일 사용자가 한 애플리케이션 인증을 갖고 있다면 그의 인증 정보는 다른 것에 전달 될 수 있다. 이 두 번째 애플리케이션은 그 모든 인증 단계를 거치지 않고도 인증 정보를 수락한다. 이 솔루션에는 중복이 없다. 두 개의 애플리케이션이 서로 신뢰하여 각 애플리케이션이 서로의 인증 데이터를 수락할 수 있도록 하는 것이 유일한 필요조건이다.

일반적으로 SSO는 개별 인증 모듈로서 구현된다. 사용자 인증이 필요한 모든 애플리케이션들은 SSO 기반의 인증 모듈에 의지하여 사용자를 확인한다. 이 인증 정보에 따라 다양한 애플리케이션들은 각자의 인증 정책을 발효한다.

이 웨어하우징 기업 예제는 사용자 관점에서 SSO가 무엇인지를 설명한 것이다. 다음 질문은 당연히 "SSO를 어떻게 구현하는가?"이다. 여러 방법이 있다. SSO를 포함하여 다양한 보안 서비스를 제공하는 Kerberos에 대해 알아보자.

Kerberos 사용하기
Kerberos는 Internet Engineering Task Force (IETF) 표준으로서 전형적인 키 교환 메커니즘을 정의한다. 애플리케이션들은 Kerberos 서비스를 사용하여 사용자에게 인증을 주고 암호 키를 교환한다. Kerberos는 티켓 발상에 기인한다. 하나의 티켓은 암호 키와 몇몇 정보 조각을 래핑하는 데이터 구조일 뿐이다. 키 발급 센터(KDC)는 Kerberos 티켓을 인증 사용자에게 발급한다. KDC는 두 가지 유형의 티켓을 발행한다:

  • 마스터 티켓(티켓 발행 티켓 (TGT))
  • 서비스 티켓

KDC는 우선 TGT를 클라이언트에 발행한다. 이 클라이언트는 TGT에 여러 서비스 티켓을 요청할 수 있다. TGT와 서비스 티켓의 작동 방법을 설명해주는 다음의 키 교환 시나리오를 보자:

  1. 클라이언트가 TGT의 발행을 요청하며 KDC에 메시지를 보낸다. 이 요청은 평이한 텍스트 형식(암호화 되어있지 않음)이고 클라이언트의 유저네임이 포함되어있다. 패스워드는 포함되지 않는다.

  2. KDC는 TGT를 클라이언트에 발급한다. TGT는 암호화된 형식안에 세션 키를 포함하고 있다. 이 세션 키를 암호화하려면 KDC는 클라이언트의 패스워드에서 나온 키를 사용한다. 클라이언트만이 TGT의 암호를 해독할 수 있고 세션 키를 가져올 수 있다는 것을 의미한다. 따라서 클라이언트 애플리케이션이 TGT를 요청하기 위해 패스워드를 알 필요는 없어도 TGT를 처리하고 사용할 패스워드는 필요하다.

  3. 클라이언트가 TGT의 암호를 해독하고 여기에서 세션 키를 추출한다. 클라이언트는 서비스 티켓을 위한 요청을 작성한다. 서비스 티켓은 양방간 커뮤니케이션용으로만 가능하다. 즉 클라이언트와 그 클라이언트가 통신하기 원하는 엔터티. 어떤 누구도 이 서비스 티켓을 사용할 수 없다. 따라서 서비스 티켓을 요청하는 동안 클라이언트는 서비스 티켓으로 사용 할 서버 이름을 지정한다. 이 서버는 KDC에 이미 존재하고 있어야 한다.

  4. KDC는 서버용 서비스 티켓을 작성한다. 이 티켓은 클라이언트의 인증 데이터와 새로운 암호 키(하위 세션 키)를 포함하고 있다. KDC는 서버의 비밀 키로 서비스 티켓을 암호화한다. 오직 서버만이 서비스 티켓 암호를 해독할 수 있다.

  5. KDC는 메시지를 작성하고 이 안에 서비스 티켓을 래핑한다. KDC는 이 메시지 내부에 하위 세션 키를 복사한다. 하위 세션 키는 메시지에 두 번 포함되었다는 것을 기억하라.

  6. KDC는 2, 3 단계에서 나온 세션 키로 전체 메시지를 암호화한다. 따라서 오직 클라이언트만 메시지를 해독하고 하위 세션 키와 서비스 티켓을 추출할 수 있다. 클라이언트가 서비스 티켓 암호를 해독하지 못하면 오직 서버만이 할 수 있다. 따라서 어떤 누구도 다른 목적으로 서비스 티켓을 사용할 수 없다. 그런 다음 KDC는 메시지를 클라이언트에 보낸다.

  7. 클라이언트는 KDC에서 받은 메시지를 해독하고 메시지 내부에 있는 하위 세션 키와 서비스 티켓을 가져온다. 서비스 티켓은 서버에 보낸다.

  8. 서버는 서비스 티켓을 받고 이를 해독하여 요청 클라이언트의 인증 데이터와 하위 세션 키를 가져온다. 서버는 클라이언트의 요청을 확인하고 새로운 보안 세션이 클라이언트와 서버 사이에 만들어진다. 클라이언트와 서버 모두 같은 하위 세션 키를 마련하고 있고 서로 보안 통신을 위해 이를 사용할 수 있다.

클라이언트는 3 단계에서 8 단계 까지 또 다른 서버 애플리케이션에 반복할 수 있다. Kerberos 서비스가 인증 데이터를 공유하는 데 사용될 수 있고 같은 클라이언트가 다른 애플리케이션에도 인증을 받을 수 있다는 것을 의미한다. 이는 SSO를 효과적으로 만든다 .

Java Generic Security Services API
IETF는 Generic Security Services API (GSS-API)를 고급 보안 API로 정의했다. 이것은 Credential성, 메시지 무결성, 보안 등의 기능을 제공한다. GSS-API는 클라이언트-서버 환경에서 쉽게 작동한다. IETF는 GSS-API를 언어 독립 방식으로 정의했다.

자바 GSS-API는 자바 스팩 형식의 GSS-API이다. Sun은 Java Community Process를 통해 자바 GSS-API를 개발했고 레퍼런스 구현도 제공했다. 이는 JDK 1.4 버전에 번들 될 것이다. (참고자료).

보다 간단히 하기 위해 GSS-API를 GSS로 통칭하겠다.

GSS는 다른 저급 보안 서비스의 상단에 고급 추상 레이어를 제공하는 것이 목적이다. Kerberos는 GSS 추상화에서 사용할 수 있는 기술 중 하나이다. GSS 추상 레이어에서 프로그래머는 보안 메커니즘을 개발 할 수 있다. 보다 낮은 레벨에서 어떤 메커니즘이 작동하고 있는지 걱정하지 않아도 된다. 이 글에서 GSS 하에서 작동하는 저급 보안 메커니즘으로서 Kerberos의 사용법에 초점을 맞추겠다.

GSS는 GSSName, GSSCredential, GSSContext의 개념에서 작동한다. GSSName은 이메일을 확인하기 위해 입력하는 유저네임 같이 당신을 개별적 존재로 구분한다. GSSCredential은 당신의 존재를 증명하기 위해 내보이는 어떤 것이다. 이메일 확인 시 입력하는 패스워드와 같다. Kerberos 서비스 티켓은 GSSCredential의 또 다른 예제이다. GSSContext는 보안 관련 정보(암호 키)를 캡슐화하는 보안 세션 같은 것이다. GSS 기반의 애플리케이션은 GSSContext를 사용하여 안전하게 통신한다.

KDC 설정하기
GSS에서의 Kerberos를 구현할 때 특별한 KDC 구현이 필요하지 않다. 따라서 GSS 기반의 모든 코드는 GSS 호완의 KDC로 작동이 될 것이다. (참고자료).

이 글의 코드를 테스트 하기위해 Microsoft의 KDC 구현을 사용했다. 이것은 Windows 2000 Server 버전 및 이후 버전에서 제공한다. 이 글에 쓰인 모든 코드가 포함되어 있는 j-gss-sso.zip 파일에는 Setup.txt 파일도 포함되어 있어 Microsoft KDC 설정에 필요한 단계를 설명하고 있다.

Microsoft KDC는 모든 Windows 사용자들을 이 서비스의 사용자로 받아들인다. KDC가 Windows 네트워크에 등록된 모든 사용자를 위해 TGT를 발급할 수 있다는 것을 의미한다.

GSS 클라이언트/서버 애플리케이션
이 섹션에서는 GSS 기반 보안 자바 애플리케이션의 실제 구현을 설명하겠다. 재사용 가능한 JavaBeans 컴포넌트 두 개를 개발 할 것이다. 하나의 빈은 요청 클라이언트로서 작동하여 새로운 GSS 세션의 초기화를 요청한다. 다른 빈은 서버로서 작동하여 요청을 리스닝하고 클라이언트에서 오는 요청을 수락한다. 그런 다음 보안 컨텍스트를 만들고 클라이언트와 통신한다.

이 글에서 SSO를 설명해야하기 때문에 제 삼자에 의해 호스팅되는 애플리케이션으로 KDC를 사용하여 우리 애플리케이션을 단순하게 할 것이다. 클라이언트와 서버 모두 이 KDC를 믿고 있으며 여기에서 오는 인증 데이터를 받아들인다. 따라서 이 글에 쓰인 샘플 코드를 사용하기 위해서는 Kerberos 실행이 필요하다. 하지만 GSS 호완의 KDC를 사용할 수 있다. ("KDC 설정하기" 참조)

JAAS 인증 클라이언트
일단 KDC 서비스를 실행하면 계속 진행하여 TGT를 발급하도록 KDC에게 요청할 수 있다. 요청하는 GSS 클라이언트는 KDC에게 TGT를 발급하도록 요청 할 것이다.

작은 문제가 있다. GSS는 사용자에게서 온 유저네임-패스워드 쌍을 가져올 메소드가 없다. 따라서 GSS 애플리케이션은 다른 비 GSS 메커니즘에 의존하여 로그인 정보를 얻어야 한다. 우리는 여기서 Java Authentication and Authorization Service (JAAS)를 사용하여 요청 클라이언트가 유저네임과 패스워드를 제공하고 TGT를 얻도록 하겠다.

Listing 1에서는 GSSClient 라는 클래스를 보게 될 것이다. 이 클래스는 원격 GSS 서버와 보안 세션의 설치를 원하는 GSS 클라이언트의 기능을 나타낸다. GSSClient 구조체는 많은 매개변수를 취한다:

  1. 클라이언트 피어 이름

  2. 클라이언트 피어의 패스워드

  3. 원격 서빙(serving) 피어의 이름

  4. 원격 서빙(serving) 피어의 주소

  5. 서버의 포트

  6. Kerberos 영역 또는 도메인

  7. KDC 주소

  8. 로그인 설정 파일의 이름과 위치 경로

  9. 클라이언트 설정 이름

main() 메소드는 간단한 애플리케이션을 시뮬레이팅하고 있다. Listing 1에 이 메소드를 추가했다. 명령행에서 이를 실행하여 이 클래스의 작동을 설명하기 위해서이다. main() 메소드는 명령행에서 매개변수 값을 읽고 GSSClient() 구조체를 호출하면서 매개변수 값을 이 구조체에 보낸다.

GSSClient() 구조체는 다른 필드에 있는 명령행에서 오는 매개변수를 저장하고 세 개의 시스템 프로퍼티를 지정한다. java.security.krb5.realm 시스템 프로퍼티는 KDC 범위를 지정한다. java.security.krb5.kdc 프로퍼티는 KDC 서버의 주소를 지정한다. java.security.auth.login.config 프로퍼티는 로그인 설정 파일의 이름과 위치 경로를 지정한다. GSS 프레임웍은 이 속성들을 내부에서 사용 할 것이다.

GSSClient 객체를 인스턴스화 한 후에, main() 메소드는 GSSClient.login() 메소드를 호출한다. 이 메소드는 LoginContext 객체를 인스턴스화 하는데 JAAS 프레임웍의 일부이다. LoginContext 구조체는 두 개의 매개변수를 취한다. 두 번째는 BeanCallBackHandler 클래스이다. 이 둘을 자세히 살펴보자.

confName과 설정파일
confName은 JAAS 설정의 이름을 수반한다. 아홉 번째 명령행 매개변수는 클라이언트가 인증용으로 사용할 JAAS를 설정한다. 여덟 번째 매개변수로 지정된 JAAS 설정 파일에는 한 개 이상의 클라이언트 설정이 포함되어 있다. 클라이언트는 이 중 하나를 사용할 수 있다.

JAAS 설정은 인증에 사용될 메커니즘을 정의한다. 설정 파일은 자바 애플리케이션이 인증 로직과 독립된 인증 메커니즘을 선택할 수 있도록 한다.

JAAS 설정은 .conf 파일로 저장된다. Listing 2 의 JAAS 설정 파일 샘플에는 두 가지 설정이 있다. GSSClient 설정은 다음과 같다:

 GSSClient { com.sun.security.auth.module.Krb5LoginModule required; }; 

이 JAAS 설정은 com.sun.security.auth.module.Krb5LoginModule 라는 자바 클래스 이름을 지정한다. 이 클래스는 JAAS의 Kerberos 로그인 모듈이고 GSSClient 설정은 로그인에 이것을 사용한다. 따라서 이 설정을 지정한다는 것은 인증 메커니즘으로서 Kerberos를 사용한다는 것을 의미한다.

Listing 2. GSSClient와 GSSServer 용 JAAS 로그인 설정
 /**** Login.conf ****/ GSSClient{ com.sun.security.auth.module.Krb5LoginModule required; }; GSSServer{ com.sun.security.auth.module.Krb5LoginModule required storeKey=true; }; 

BeanCallBackHandler
Listing 1의 클라이언트를 다시 살펴보자. LoginContext 구조체 메소드 호출과 함께 전달된 두 번째 매개변수는 인증 과정 중에 콜백을 핸들할 객체를 지정한다. Callback은 자바 애플리케이션이 인증 프로세스 중에 JAAS 구현과 인터랙팅 할 수 있도록 한다. 정상적으로 콜백 기능을 사용하여 유저네임과 패스워드를 Kerberos 인증 모듈에 전달 할 수 있다. BeanCallBackHandler 구조체 call에 있는 usernamepassword를 전달했다는 것에 주목하라.

Listing 3은 BeanCallBackHandler 클래스이다. 이 클래스는 CallBackHandler 라는 인터페이스를 구현한다. 이 인터페이스는 JAAS 프레임웍의 일부이고 단 하나의 메소드인 handle()을 포함하고 있다.

Listing 3. JAAS 프레임웍에서 콜백 핸들링하기
 /**** BeanCallbackHandler.java ****/ import java.io.*; import java.security.*; import javax.security.auth.*; import javax.security.auth.callback.*; public class BeanCallbackHandler implements CallbackHandler { // Store username and password. String name = null; String password = null; public BeanCallbackHandler(String name, String password) { this.name = name; this.password = password; }//BeanCallbackHandler public void handle (Callback[] callbacks) throws UnsupportedCallbackException, IOException { for(int i=0; i<callbacks.length; i++) { Callback callBack = callbacks[i]; // Handles username callback. if (callBack instanceof NameCallback) { NameCallback nameCallback = (NameCallback)callBack; nameCallback.setName(name); // Handles password callback. } else if (callBack instanceof PasswordCallback) { PasswordCallback passwordCallback = (PasswordCallback)callBack; passwordCallback.setPassword(password.toCharArray()); } else { throw new UnsupportedCallbackException(callBack, "Call back not supported"); }//else }//for }//handle }//BeanCallbackHandler 

CallBackHandler 객체의 handle() 메소드는 인증이 이루어지는 동안 제어를 자동으로 받는다.

JAAS 프레임웍은 CallBack 객체의 어레이를 CallBackHandler 인스턴스의 handle() 메소드로 전달한다. CallBack 객체의 어레이에는 다른 유형의 콜백 객체들이 포함되어 있다. 하지만 여기에서는 두 가지 유형(NameCallBackPasswordCallBack)을 중심으로 설명하겠다.

NameCallBack 객체는 JAAS 프레임웍에 username을 제공하는데 사용된다. PasswordCallBack은 인증 동안 password를 수반한다. 핸들 메소드(Listing 3)내부에서 NameCallBack 객체의 setName() 메소드를, PasswordCallBacksetPassword() 메소드를 호출했다.

LoginContext 구조체 호출에 따르는 두 개의 매개변수에 대한 위 논의를 요약해보면 사용자 인증에 필요한 LoginContext 객체에 모든 정보를 제공해 줄 수 있다. 따라서 Listing 1의 다음 단계는 LoginContext.login() 메소드를 호출한다. 이는 실제 로그인 프로세스를 수행할 것이다.

인증 프로세스 중에 무엇인가 잘못된다면-예를 들어, password가 정확하지 않다거나-javax.security.auth.login.LoginException이 던져진다. 로그인 메소드 호출의 결과로 어떤 예외도 없다면 인증 프로세스가 성공했다는 것으로 간주할 수 있다.

Kerberos 로그인을 사용하고 있기 때문에 JAAS 프레임웍은 내부적으로 KDC와의 모든 통신을 관리하고 Kerberos TGT를 보내면서 고급의 사용이 편리한 JAAS 인터페이스 뒤에 모든 기술적 상세들을 숨긴다.

성공적인 인증은 LoginContext 객체 안에 Kerberos TGT를 로딩하게 된다. LoginContext 클래스의 getSubject() 메소드를 호출할 수 있다. 이것은 Subject라는 클래스 인스턴스를 리턴한다. Subject 인스턴스는 TGT를 래핑한다.

Subject 클래스를 사용하여 우리가 로그인 했던 액션을 수행할 것이다. 보안 GSS 콘텍스트를 설치하는 것이다. 인증이 성공한 후 필요한 액션을 호출하는 방법을 보자.

Subject 클래스에는 doAs()라는 정적 메소드가 포함되어 있다. 이것은 두 개의 매개변수를 갖는다. Listing 1의 Subject.doAs() 메소드 호출을 보자. 첫 번째 매개변수 값은 Subject 인스턴스로서 성공적인 인증 후에 획득한 것이다. doAs() 메소드는 인증된 Subject를 사용하여 권한 결정을 내린다. 이 Subject가 주어진 작동을 호출할 권한을 받았는지를 판단하는데 사용하는 것이다.

지금까지 Subject 클래스는 TGT를 보내는데에만 사용했다. 따라서 GSSClient에 대한 권한 정책을 지정하지 않았다. 어떤 사용자라도 이 클라이언트를 실행할 수 있다. GSSClient는 주어진 작동을 실행할 때 보안 권한을 요구하지 않는다. 하지만 애플릿 같은 웹 클라이언트는 엄격한 보안 콘텍스트 하에서 실행된다. Setup.txt에는 애플릿 기반의 GSS 클라이언트를 위한 권한 정책을 작성하는 지침이 포함되어 있다.

doAs() 메소드 호출의 두 번째 매개변수 값인 thisGSSClient 객체(Listing 1)를 지정한다. 이 매개변수는 PriviledgedAction 인터페이스를 노출하는 객체를 기대한다. GSSClient는 그 PriviledgedAction 인터페이스를 구현한다. 하나의 클래스에 모든 클라이언트측 코드를 조합했지만 PriviledgedAction 인터페이스를 구현하는 개별 클래스를 가질 수 있다. 원한다면 말이다. 만일 그렇게 한다면 그 객체를 인스턴스화 하고 doAs() 메소드 호출과 함께 두 번째 매개변수 값으로서 이를 전달한다.

PriviledgedAction 인터페이스에는 단 하나의 메소드 run()이 포함되어 있다. 권한 정책이 Subject로 하여금 GSSClient 클래스의 run() 메소드에 액세스 할 수 있도록 한다면 그 메소드는 개별 쓰레드 실행에서 제어를 받는다. run() 메소드가 제어를 받으면 보안 콘텍스트(TGT)가 따라온다. GSS 로직은 자동으로 그 보안 콘텍스트를 사용하고 TGT를 보낸다.

GSS 클라이언트 디자인
Listing 1의 run() 메소드에서 다음 단계를 수행하여 GSS 세션을 만들어야 한다:

  1. GSSManager를 인스턴스화 한다. GSSManager 클래스의 getInstance() 정적 메소드를 호출했던 Listing 1을 주목하라. 이것은 GSSManager 객체를 리턴한다. 이 GSSManager 객체는 적어도 Kerberos 메커니즘을 지원하고 그 밖에 다른 메커니즘도 지원할 것이다. GSSManager 클래스에는 GSS 애플리케이션이 다른 보안 메커니즘을 정의하기 위해 호출할 수 있는 메소드들이 포함되어 있다. 지금은 Kerberos 메커니즘을 중심으로 설명하는 것이니 만큼 getInstance() 메소드를 호출하여 Kerberos 메커니즘을 사용한다. Listing 1의 GSSManager 객체를 인스턴스화 한 후에 kerberos라는 Oid (Object ID) 객체를 인스턴스화 했다. 이것은 Kerberos 메커니즘을 확인한다. ."

  2. GSSName 객체를 만든다. 이것은 GSS 엔터티를 나타낸다. 투웨이 통신을 하는 동안 GSS 엔터티를 통신 피어로 생각할 수 있다. 따라서 두 개의 GSSName 객체를 만드는 것이다. 하나는 요청 클라이언트 피어(clientPeerName)용이고 또 다른 하나는 원격 피어(remotePeerName)용이다.

  3. Credential 세트를 만든다. GSS는 일반적인 보안 메커니즘이다. 따라서 기저의 기술에 의존하여 이러한 Credential을 만든다. Kerberos를 사용하고 있으므로 Kerberos 티켓은 실제 Credential이다. GSSCredential 객체를 얻으려면 GSSManager 클래스의 createCredential() 메소드를 사용한다. createCredential() 메소드는 GSSCredential 인터페이스를 노출하는 객체를 리턴한다.

  4. 보안 GSS 콘텍스트를 만든다. 이것은 두 개의 통신 피어 간 보안 통신을 확립하는데 사용된다. GSSManagercreateContext() 메소드는 GSSContext 인터페이스의 인스턴스를 만들어 리턴한다. GSSContext 객체는 실제 보안 콘텍스트를 래핑한다. Listing 1에서 GSSNameGSSCredential 객체를 전달했다는 것에 주목하라.

보안 콘텍스트를 래핑하는 GSSContext 객체를 가졌지만 콘텍스트 그 자체는 아직 설치되지 않았다. GSSContext 객체를 얻은 후 요청 피어가 이것의 requestConf() 메소드를 호출 할 것이다. 이 메소드는 애플리케이션이 기밀성과 데이터 무결성을 요청하게 하여 원격으로 보내는 모든 데이터가 암호화된 형식이 되도록 한다.

Listing 1에서 requestConf() 메소드를 호출 한 후에 byteToken이라는 바이트의 어레이를 선언했고 이를 제로 사이즈 바이트로 인스턴스화 했다. byteToken 어레이는 GSS 클라이언트가 서버에서 송수신하는 데이터 바이트를 갖고있게된다.

GSSContext 인터페이스의 initSecContext() 메소드를 while 루프에서 반복적으로 호출한다. 이 메소드는 실제 바이트 교환을 수행하여 요청 피어와 서빙 피어 간 보안 콘텍스트를 확립한다.

while(!peerContext.isEstablished()) 블록(Listing 1)은 실제로 GSS 클라이언트와 서버 간 투웨이 통신을 수행한다. 이 블록은 보안 콘텍스트가 설치되었을 때에만 종료된다.

while 루프가 첫번째로 실행될 때 byteToken 어레이에는 데이터가 없다. peerContext.isEstablished() 메소드는 false를 리턴할 것이다. 보안 콘텍스트가 아직 설치되지 않았기 때문이다.

while 루프 내부에서 우리가 수행하는 첫 번째 일은 byteToken 어레이상에서 initSecContext() 메소드를 전달하는 것이다. 이 메소드는 두 가지의 일을 한다. 클라이언트가 서버에 보내는 바이트를 만들어내고 서버에서 오는 바이트를 받아들인다. 바이트 교환은 GSS 콘텍스트가 확립될 때 까지 계속된다.

처음에 initSecContext() 메소드를 호출 할 때 전달할 바이트가 없다. 따라서 비어있는 byteToken 어레이에서 첫 번째 호출용 메소드에 전달한다. initSecContext() 메소드는 몇몇 바이트를 리턴하는데 이것은 같은 byteToken 어레이에 저장되었다. 그 다음 byteToken을 아웃풋 스트림에 작성하고 스트림을 플러시 하여 byteToken 어레이가 원격 서버로 보내지도록 한다.

원격 서버에 보내진 바이트에 대한 응답으로 원격 서버는 어떤 것을 보낼것으로 기대할 수 있다. 따라서 우리는 인풋 스트림에서 바이트를 읽고 같은 byteToken 어레이에 이 바이트를 저장하고 byteToken 어레이를 다시 initSecContext() 메소드에 보낸다. 이 메소드는 다시 바이트 어레이를 리턴한다. 이러한 바이트 교환은 while 루프 내부에서 보안 콘텍스트가 확립되고 peerContext.isEstablished() 메소드가 true를 리턴할 때 까지 지속된다.

보안 콘텍스트의 설치가 의미하는 것은 적절한 Kerberos 키들이 클라이언트와 서버 양측에서 사용가능한 상태가 된다는 것을 의미한다. 양측에서 이 키들을 사용하여 보안 통신에 쓴다. 보안 콘텍스트가 성립되면 GSSContext 객체를 리턴한다.

GSS 콘텍스트 사용하기
GSSClient 클래스의 main() 메소드에서 GSSContext 객체를 사용하는 방법을 살펴보겠다. login() 메소드를 호출한 후 login() 메소드가 리턴한 GSSContext 이 null인지를 확인한다. null이 아니라면 보안 콘텍스트가 원격 서버와의 보안 통신에 사용될 수 있다.

main() 메소드는 원격 서버가 기밀성과 메시지 무결성이라는 클라이언트 요청에 합당한지를 점검한다. 서버가 요청에 합당하면 GSSContext 클래스의 getConfState() 메소드는 true를 리턴한다.

main() 메소드는 원격 메소드로 보내는 데이터가 무엇이든 보낼 수 있다. sendMessage() 헬퍼 메소드를 작성하여 데이터를 서버에 보냈다. GSSContext 클래스의 wrap() 메소드는 데이터의 바이트 어레이를 받아들이고 바이트 어레이를 리턴한다. sendMessage() 메소드는 플레인 텍스트 데이터를 wrap() 메소드에 보내고 응답으로 암호화된 바이트 어레이를 받는다. 아웃풋 스트림 상에서 바이트 어레이를 작성하여 암호화된 바이트 어레이를 원격 서버로 보낼 수 있다.

sendMessage() 메소드는 서버에서 오는 인커밍 데이터를 리스닝한다. 서버에서 데이터를 받으면 인커밍 데이터를 GSSContext 클래스의 unwrap() 메소드에 보낼 수 있다. 이것은 평이한 텍스트 형식의 데이터를 리턴한다.

GSS 서버 애플리케이션
지금까지 GSS 클라이언트 애플리케이션이 작동 방법을 보았다. 이제 이것과 인터랙팅 할 서버를 구현해본다.

Listing 4GSSServer 클래스용 코드이다. GSSServerstartServer() 메소드는 GSSClient 클래스의 login() 메소드를 설명하면서 거론되었던 것과 같은 기능을 수행한다.

GSSServer 클래스의 run() 메소드 내부에서 GSSManagerGSSName 객체를 만들었다. 지금까지 클라이언트측 코드와 서버측 코드 사이에 차이점이 거의 없었다. 하지만 좀더 자세히 보면 서버는 단 하나의 GSSName 객체를 만든다는 것을 알 수 있다. 이것은 서버를 나타낸다. GSSName을 만든 후에 이 서버는 createCredential() 메소드를 호출하여 GSSCredential 객체에 credentials를 로딩한다.

다음 단계는 GSS 콘텍스트를 만들기 위해 createContext() 객체를 호출하는 것이다. createContext() 메소드 호출은 GSS 클라이언트 애플리케이션에서 이루어진 createContext() 호출과는 다르다. 지금의 createContext() 메소드는 단 하나의 매개변수의 서버의 credential을 갖는다. 이 서버측 콘텍스트가 양측 사이에 있지 않다는 것을 의미한다.

다음에는 통신용 인풋과 아웃풋 스트림을 만든다. 그런 다음 보안 콘텍스트가 설치될 때까지 계속 루핑 할 while 루프로 들어간다. 이 루프 안에서 요청 클라이언트를 기다려 연결 구축 요청을 보낸다. while 루프 내부의 인풋 스트림 상에서 데이터를 받으면 데이터를 바이트 어레이로 읽고 바이트 어레이 토큰을 GSSContext 클래스의 acceptSecContext() 메소드에 제공한다. acceptSecContext() 메소드는 데이터 바이트를 리턴한다. 이것은 아웃풋 스트림으로 되돌아간다.

GSSContextinitSecContext()acceptSecContext() 메소드는 결합하여 작동한다. GSS 클라이언트 애플리케이션을 설명하면서 initSecContext() 메소드의 사용법을 설명했다. initSecContext() 메소드는 GSS 클라이언트가 GSS 서버 애플리케이션으로 보내는 초기 바이트를 만들어낸다. acceptSecContext() 메소드는 이러한 인커밍 바이트를 받아들이고 이것의 바이트 어레이를 만들어낸다. 이것을 클라이언트로 보낸다. 이러한 바이트 교환은 보안 GSS 콘텍스트가 구축될 때 까지 지속된다.

GSS는 모든 통신을 바이트 어레이 토큰으로서 핸들한다. 클라이언트에서 서버로 바이트 어레이를 나르기위해 어떤 유형의 전송 서비스를 사용할 수 있다. GSS는 데이터 전송에 사용되는 것에는 관심이 없다.

보안 세션의 구축 과정은 요청 클라이언트의 인증으로 끝을 맺는다. GSSContext 클래스의 getSrcName() 메소드를 호출하여 인증된 클라이언트의 GSSName을 보낼 수 있다. 반면 GSSContext 클래스의 getTargName() 메소드는 원격 클라이언트의 요청을 수락한 서버의 GSSName을 리턴한다.

while (!context.isEstablished()) 루프가 리턴한 후에(Listing 4) run() 메소드는 클라이언트로 부터 통신을 기다린다. 인커밍 데이터를 계속해서 리스닝하고 인커밍 바이트 스트링을 받게되면 GSSContext 클래스의 unwrap() 메소드를 통해 스트링을 보낸다. unwrap() 메소드는 클라이언트에서 온 평이한 텍스트 형식의 메시지를 리턴한다.

브라우저 속의 GSS
Listing 5는 애플릿이 Listing 1의 GSS 클라이언트를 사용하여 Listing 4의 GSS 서버와의 보안 통신을 구축하는 방법이 나와있다.

이 애플릿은 HTML 페이지에서 실행될 것이다. (Listing 6):

Listing 6. GSS 애플릿을 사용하는 HTML 페이지
 <!-- E-Commerce Login.html --> <HTML> <HEAD> <TITLE>E-Commerce Login... </TITLE> </HEAD> <BODY> <p align="center"> <table bgcolor="Gray"> <tr> <td align="center"> <b>E-Commerce Site Login Page </b> </td> </tr> <tr> <td> <Applet CODE="GSSClientApplet.class" archive="GSSClientApplet.jar" name="GSSClientApplet" width="500" height="280"> </Applet> </td> </tr> </table> </p> </BODY> </HTML> 

이 애플릿이 e-커머스 웹 사이트의 메인 페이지에서 실행된다고 생각해보자. 그림 1은 실행 모습을 나타낸 것이다. 두 개의 텍스트 엔트리 필드, 버튼 세 개, 하나의 텍스트 영역이 있다. 세 개의 버튼 각각은 e-커머스 웹 사이트의 파트너의 서버측 구현에 상응한다.

그림 1. GSS 애플릿 사용
An HTML page showing GSS applet usage

e-커머스 웹 사이트 고객은 어떤 사이트 파트너라도 인증받을 수 있다. 애플릿의 텍스트 필드에 유저네임과 패스워드를 입력하고 인증을 원하는 파트너 사이트에 해당하는 Login 버튼을 누른다. 이 버튼의 이벤트 핸들러는 GSSClient 구조체에 필요한 매개변수를 제공한다. 나머지 작업은 GSS 클라이언트가 할 일이고 이것은 이미 설명했다.

참고자료

Posted by tornado
|

출처 : http://www-903.ibm.com/developerworks/kr/java/library/os-ecref.html

Eclipse의 자동화 리팩토링 기능

Level: Intermediate

David Gallardo
소프트웨어 컨설턴트
2003년 9월 9일

Eclipse는 강력한 자동 리팩토링을 제공한다. 이것을 통해 자바 엘리먼트를 재명명하고, 클래스와 패키지를 옮기며 실제 클래스에서 인터페이스를 만들고 중첩 클래스를 상위레벨 클래스로 변환하고 구 메소드의 코드 섹션에서 새로운 메소드를 가져올 수 있다. Eclipse의 리팩토링 툴에 익숙해진다면 생산성을 향상할 수 있는 좋은 방법이다.

리팩토링
리팩토링(Refactoring)은 프로그램의 기능은 변화시키지 않고 프로그램의 구조를 변화시키고 있다. 리팩토링은 강력한 기술이지만 조심스럽게 다뤄져야 한다. 가장 위험한 것은 에러들이 갑자기 발생할 수 있다. 특히 리팩토링을 수동으로 수행할 때 그렇다. 바로 이것이 리팩토링의 주요 비판 요소이다.

코드를 리팩토링하는 여러가지 이유가 있다. 첫 번째는 전설 때문이다. 훌륭한 제품을 위한 진부한 오래된 코드 기반은 전승되거나 그렇지 않다면 신비스럽게 나타난다. 그리고 원래 개발팀은 사라졌다. 새로운 기능들을 갖춘 새로운 버전이 만들어져야 하지만 그 코드는 더 이상 이해할 수 없는 것이 되고 만다. 새로운 개발팀은 밤낮으로 일하며 이것을 판독하고 매핑하며 많은 계획과 디자인 후에 코드를 산산조각으로 만든다. 마침내 그들은 새로운 버전에 따라 이 모두를 다시 제자리에 갖다 놓는다. 이것은 영웅적 스케일의 리팩토링이고 일부만이 살아남아 이 이야기를 할 수 있다.

보다 현실적인 시나리오는 디자인 변화를 필요로하는 프로젝트에 새로운 요구사항이 만들어졌다는 것이다. 이러한 요구사항이 원래 계획의 부주의한 간과 때문인지 또는 반복적인 접근방식이 개발 프로세스 전반에 걸쳐 요구사항들을 정교하게 만들어내는데 사용되었기 때문이지는 불분명하다. 이것은 훨씬 작은 스케일의 리팩토링이다. 그리고 여기에는 일반적으로 클래스 계측을 변경하는것이 포함되어있는데 아마도 인터페이스 또는 추상 클래스 도입, 클래스 나누기, 클래스 재조정등을 통해 수행된다.

마지막으로, 자동화된 리팩토링 툴이 사용가능하다면, 첫 번째 단계에서 코드 생성을 위한 지름길로서의 리팩토링을 하는 이유가 되겠다. 리팩토링을 이렇게 사용한다면 툴에 익숙하다면 효과적으로 시간을 절약할 수 있다.

코드가 깨지는 위험을 감소시킬 수 있는 두 가지 중요한 방법이 있다. 하나는 코드용 단위 테스트 세트를 갖추는 것이다. 이 코드는 리팩토링 전후에 걸쳐 테스트를 통과해야한다. 두 번째 방법은 Eclipse의 리팩토링 기능 같은 자동화된 툴을 사용하여 리팩토링을 수행하는 것이다.

전체 테스팅과 자동화된 리팩토링의 결합은 특별히 강력하며 언제나 유용한 툴로 변모된다. 기능을 추가하거나 관리능력을 향상시키기 위해 기능을 변화시키지 않고 코드의 구조를 변경하는 능력은 코드를 설계하고 개발하는 방식에 큰 영향을 미칠 수 있다.

Eclipse의 리팩토링 유형
Eclipse의 리팩토링 툴은 크게 세 가지 범주로 나눌 수 있다.(이것은 Refactoring 메뉴에 나타나는 순서이다):

  1. 코드의 이름과 물리적 조직을 변경하고, 재명명 필드, 변수, 클래스, 인터페이스를 추가하고, 패키지와 클래스 옮기기
  2. 클래스 레벨에서 코드의 논리적 조직을 변경하고, 익명의 클래스들을 중첩 클래스로 옮기며, 중첩 클래스들을 상위 레벨 클래스로 전환하고 구체 클래스에서 인터페이스를 만들고 클래스에서 하위 클래스 또는 상위 클래스로 메소드 또는 필드 옮기기
  3. 클래스 내에 있는 코드 변경하고, 로컬 변수를 클래스 필드로 변환하고 메소드내의 선택된 코드를 개별 메소드로 전환하고 getter와 setter 메소드 생성하기

많은 리팩토링이 이러한 세 가지 범주에 딱 맞아떨어지는 것은 아니다. 특히 세 번째 카테고리에 있는 Change Method Signature가 그렇다. 이 예외를 제외하고 나머지 섹션들은 이 순서대로 Eclipse의 리팩토링 툴을 설명할 것이다.

물리적 재구성(reorganization)과 재명명(renaming)
특별한 툴 없이 파일 시스템에 있는 파일들을 재명명하거나 옮길 수 있다. 하지만 자바 소스 파일로 이일을 수행하려면 import 또는 package 문을 업데이트 하기 위해 많은 파일들을 편집해야 한다. 텍스트 에디터의 검색을 사용하여 클래스, 메소드, 변수를 쉽게 재명명 할 수 있고 기능을 대체시킬 수 있다. 하지만 다양한 클래스들이 같은 이름을 가진 메소드 또는 변수를 갖기 때문에 조심해야 한다. 모든 인스턴스가 정확히 구분되고 변경되도록 확인해야 한다.

Eclipse의 Rename과 Move는 사용자가 개입하지 않고 이러한 변경들을 지능적으로 수행할 수 있다. Eclipse는 코드 문법을 이해하고 특정 메소드, 변수, 클래스 이름에 대한 레퍼런스를 구분할 수 있기 때문이다.

코드가 원래 계획과는 다르게 작동하도록 변경되었기 때문에 부적절한 이름이나 오도된 이름을 가진 코드를 찾는 것은 매우 기본적인 일이다. 예를 들어 파일에서 특정 단어를 찾는 프로그램은 InputStream을 얻기 위해 URL 클래스를 사용함으로서 웹 페이지와 함께 작동하도록 확장될 수 있다. 이 인풋 스트림이 원래 파일로 호출되었다면 좀더 일반적인 성질을 반영하도록 변경되어야 한다. 개발자들은 이 같은 변경 작업을 실패하곤 한다.

자바 엘리먼트를 재명명하려면 Package Explorer 뷰에 있는 rename을 클릭하거나 자바 소스 파일에서 이를 선택한 다음 Refactor > Rename을 선택한다. 다이얼로그 박스에서 새로운 이름을 선택하고 Eclipse가 이 이름에 대한 레퍼런스도 변경하는지의 여부를 선택한다. 디스플레이되는 정확한 필드는 선택한 엘리먼트 유형에 의존한다. 예를 들어 getter와 setter 메소드를 가진 필드를 선택하면 이 새로운 필드를 반역하기 위해 이 메소드들의 이름을 업데이트 할 수 있다. (그림 1).

그림 1. 로컬 변수의 재명명
Renaming a local variable

모든 Eclipse 리팩토링과 마찬가지로 리팩토링을 수행하는데 필요한 모든 것을 설정한 후에 Preview를 눌러 변경사항을 볼 수 있다. 비교 다이얼로그로 되어있는데 거부 또는 모든 개별 파일의 변경사항 마다 거부 또는 승인을 할 수 있다. Eclipse가 변경작업을 정확하게 수행했다는 확신이 있다면 OK만 눌러도 된다. 분명히 리팩토링이 무엇을 수행하는지 확실하지 않다면 프리뷰를 우선 눌러야 한다. 하지만 이것은 Rename과 Move 같은 간단한 리팩토링에는 해당하지 않는다.

Move는 Rename과 비슷하다: 자바 엘리먼트(대게, 클래스)를 선택하고 이것의 새로운 위치를 지정하고 레퍼런스의 업데이트 여부를 지정한다. 그런다음 Preview를 선택해 변경 사항을 검토하거나 OK를 눌러 리팩토링을 즉시 수행한다. (그림 2).

그림 2. 하나의 패키지에서 다른 패키지로 클래스 옮기기
Moving a class

몇몇 플랫폼 상에서는 (주로 Windows), 하나의 패키지 또는 폴더에서 또 다른 패키지나 폴더로 클래스를 이동할 수 있다. Package Explorer 뷰에서 이를 드래그 & 드랍 방식을 이용한다. 모든 레퍼런스들은 자동으로 업데이트 될 것이다.

클래스 관계 재정의
Eclipse의 리팩토링 세트를 이용하면 클래스 관계를 자동으로 변경할 수 있다. 이러한 리팩토링은 Eclipse가 제공하는 다른 유형의 리팩토링 만큼 일반적인 것은 아니지만 매우 복잡한 태스크를 수행한다는 점에서 가치가 있다.

익명의 클래스와 중첩 클래스 사용하기
두 개의 리팩토링(Convert Anonymous Class to Nested & Convert Nested Type to Top Level)은 현재 범위에서 enclosing 범위까지 클래스를 옮긴다는 점에서 비슷하다.

익명의 클래스는 클래스 이름을 주지않고 필요한 곳에 추상 클래스나 인터페이스를 구현하는 클래스를 인스턴스화할 수 있는 지름길이다. 사용자 인터페이스에 리스너를 만들 때 일반적으로 사용된다. Bag이 두 개의 메소드(get() & set())를 선언하는 곳에서 정의된 인터페이스라고 가정해보자. (Listing 1).

Listing 1. Bag 클래스
 public class BagExample { void processMessage(String msg) { Bag bag = new Bag() { Object o; public Object get() { return o; } public void set(Object o) { this.o = o; } }; bag.set(msg); MessagePipe pipe = new MessagePipe(); pipe.send(bag); } } 

익명의 클래스가 너무 커져서 코드 판독이 불가능하게 되면 익명의 클래스를 정식 클래스로 만드는 것을 고려해봐야 한다. 캡슐을 유지하려면 상위 레벨 클래스 보다는 중첩 클래스로 만들어야 한다. 익명 클래스 안에서 클릭하여 Refactor > Convert Anonymous Class to Nested를 선택한다. 클래스 이름을 입력하고 Preview 또는 OK를 선택한다. (Listing 2).

Listing 2. 리팩토링이 수행된 Bag 클래스
 public class BagExample { private final class BagImpl implements Bag { Object o; public Object get() { return o; } public void set(Object o) { this.o = o; } } void processMessage(String msg) { Bag bag = new BagImpl(); bag.set(msg); MessagePipe pipe = new MessagePipe(); pipe.send(bag); } }

Convert Nested Type to Top Level은 다른 클래스에도 사용 가능한 중첩 클래스를 만들 때 유용하다. 소스 파일에서 클래스 이름을 강조하고(또는 아웃라인 뷰의 클래스 이름을 클릭하여) Refactor > Convert Nested Type to Top Level을 선택한다.

이 리팩토링은 enclosing 인스턴스용 이름을 요청할 것이다. OK를 누른 후 enclosing BagExample 클래스용 코드는 변경된다. (Listing 3).

Listing 3. 리팩토링이 수행된 Bag 클래스
 public class BagExample { void processMessage(String msg) { Bag bag = new BagImpl(this); bag.set(msg); MessagePipe pipe = new MessagePipe(); pipe.send(bag); } } 

클래스가 중첩되면 외부 클래스의 멤버에 대한 액세스를 가진다는 것에 주목하라. 이 기능을 유지하기 위해 리팩토링은 enclosing 클래스 BagExample의 인스턴스를 이전에 중첩된 클래스에 추가 할 것이다. 이것은 이전에 이름을 제공하도록 요청받았던 인스턴스 변수이다. 이것은 또한 인스턴스 변수를 설정하는 컨스트럭터를 만든다. 다음은 리팩토링이 만든 이 새로운 BagImpl 클래스이다. (Listing 4).

Listing 4. BagImpl 클래스
 final class BagImpl implements Bag { private final BagExample example; /** * @paramBagExample */ BagImpl(BagExample example) { this.example = example; // TODO Auto-generated constructor stub } Object o; public Object get() { return o; } public void set(Object o) { this.o = o; } } 

BagExample 클래스에 대한 액세스를 보일필요가 없다면 인스턴스 변수와 컨스트럭터를 제거하고 BagExample 클래스를 no-arg 컨스트럭터로 변경한다.

클래스 계층 내에서 멤버 옮기기
두 개의 다른 리팩토링(Push Down & Pull Up)은 클래스 메소드 또는 필드를 하나의 클래스에서 이것의 하위클래스 또는 상위 클래스로 각각 이동시킨다. 추상 클래스 Vehicle이 있다고 가정해보자. (Listing 5).

Listing 5. Vehicle 추상 클래스
 public abstract class Vehicle { protected int passengers; protected String motor; public int getPassengers() { return passengers; } public void setPassengers(int i) { passengers = i; } public String getMotor() { return motor; } public void setMotor(String string) { motor = string; } } 

Automobile이라는 Vehicle의 하위클래스도 있다. (Listing 6).

Listing 6. Automobile 클래스
 public class Automobile extends Vehicle { private String make; private String model; public String getMake() { return make; } public String getModel() { return model; } public void setMake(String string) { make = string; } public void setModel(String string) { model = string; } } 

Vehicle의 애트리뷰트 중 하나가 motor라는 것에 주목하자. 모터로 구동되는 탈것 만을 다룰 것이라면 괜찮지만 보트 같은 것도 허용하려면 motor 애트리뷰트를 Vehicle 클래스에서 Automobile 클래스로 밀어내려야 한다. 이를 위해서는 Outline 뷰에서 motor를 선택하고 Refactor > Push Down을 선택한다.

Eclipse는 여러분이 필드를 언제나 옮길 수는 없다는 것을 인식할 정도로 똑똑하기 때문에 Add Required 라는 버튼을 제공한다. 하지만 Eclipse 2.1에서는 언제나 정확하게 작동하는 것은 아니다. 이 필드에 근거하는 모든 메소드가 "push down" 되었는지를 확인해야 한다. 이 경우 motor 필드를 수반하는 getter와 setter 메소드가 있다. (그림 3).

그림 3. Add Required
Adding required members

OK를 누른 후, motor 필드와 getMotor() & setMotor() 메소드는 Automobile 클래스로 이동한다. Listing 7은 Automobile 클래스의 리팩토링 후의 모습이다.

Listing 7. 리팩토링 후의 Automobile 클래스
 public class Automobile extends Vehicle { private String make; private String model; protected String motor; public String getMake() { return make; } public String getModel() { return model; } public void setMake(String string) { make = string; } public void setModel(String string) { model = string; } public String getMotor() { return motor; } public void setMotor(String string) { motor = string; } } 

Pull Up 리팩토링은 Push down과 거의 동일하다. 클래스 멤버를 클래스에서 상위클래스로 옮긴다는 것을 제외하면...

Automobile 클래스에 motor가 있다는 것은 Bus 같이 Vehicle의 다른 하위클래스를 만든다면 모터를 Bus 클래스에 추가해야한다는 것을 의미한다. 이와 같은 관계를 나타내는 한 가지 방법은 Motorized라는 인스턴스를 만드는 것이다. AutomobileBus는 구현하지만 RowBoat는 그렇지 않다.

Motorized 인터페이스를 만드는 가장 쉬운 방법은 Automobile에 Extract 인터페이스 리팩토링을 사용하는 것이다. Outline 뷰에서 Automobile 클래스를 선택하고 메뉴에서 Refactor > Extract Interface를 선택한다. (그림 4).

그림 4. Motorized 인터페이스
Motorized interface

OK를 선택한 후, 인터페이스가 만들어진다. (Listing 8).

Listing 8. Motorized 인터페이스
 public interface Motorized { public abstract String getMotor(); public abstract void setMotor(String string); } 

Automobile용 클래스 선언은 다음과 같다:

 public class Automobile extends Vehicle implements Motorized 

supertype 사용하기
이 범주에 포함된 마지막 리팩토링은 Use Supertype Where Possible이다. 자동차의 재고를 관리하는 애플리케이션을 생각해보자. 이것은 Automobile 유형의 객체를 사용한다. 모든 유형의 탈것을 핸들하려면 이 리팩토링을 사용하여 Automobile에 대한 레퍼런스를 변경할 수 있다. (그림 5). instanceof 오퍼레이터를 사용하여 코드에서 모든 유형 체킹을 수행하면 특정 유형을 사용하는 것이 적절한지 또는 supertype을 선택하는 것이 적절한지 결정해야하고 첫 번째 옵션을 체크하게 될 것이다. 대게 'instanceof' 익스프레션에서 선택된 supertype을 사용한다.

그림 5. Automobile을 supertype인 Vehicle로 바꾸기
Supertype

supertype의 필요성은 자바 언어를 사용할 때 종종 발생한다. 특히 Factory Method 패턴이 사용될 때 그렇다. 일반적으로 이것은 추상 클래스를 구현하는 구체적 객체를 리턴하는 정적 create() 메소드를 갖고 있는 추상 클래스를 갖추면서 구현된다. 구현되어야 할 구체적 객체의 유형이 클라이언트 클래스와 관계가 없는 구현 상세에 의존한다면 유용하게 쓰인다.

클래스 내부에서 코드 변경하기
가장 광범위한 리팩토링은 클래스 내에서 코드를 인식하는 리팩토링이다. 무엇보다도 이들은 중간 변수를 가져오고 오래된 메소드의 한 부분에서 새 메소드를 만들고 필드용 setter 및 getter를 만든다.

Extracting & inlining
Extract: Extract Method, Extract Local Variable, Extract Constants로 시작하는 많은 리팩토링이 있다. Extract Method는 여러분이 선택한 코드에서 새로운 메소드를 만든다. 클래스의 main() 메소드 예를 들어보자(Listing 8). 이것은 명령행 옵션을 계산하고 -D로 시작하는 무엇인가를 발견하면 Properties 객체에 이름 값 쌍으로 그들을 저장한다.

Listing 8. main()
 import java.util.Properties; import java.util.StringTokenizer; public class StartApp { public static void main(String[] args) { Properties props = new Properties(); for (int i= 0; i < args.length; i++) { if(args[i].startsWith("-D")) { String s = args[i].substring(2); StringTokenizer st = new StringTokenizer(s, "="); if(st.countTokens() == 2) { props.setProperty(st.nextToken(), st.nextToken()); } } } //continue... } } 

메소드에서 몇 개의 코드를 가져다가 이를 또 다른 메소드에 넣는 곳에 두 가지 경우가 있다. 첫 번째 경우는 메소드가 너무 길고 두 개 이상의 구별된 논리적 작동을 수행한다. 두 번째 경우는 다른 메소드에 의해 재사용 될 수 있는 코드의 구별된 섹션이 있다면 두 번째 케이스이다.

이름 값 쌍을 파싱하고 그들을 Properties 객체에 추가해야하는 또 다른 장소가 있고 StringTokenizer 선언과 뒤따르는 if 절을 포함하는 코드 색션을 추출할 수 있다고 가정해보자. 이를 수행하기 위해서는 이 코드를 강조하고 그런다음 Refactor > Extract Method를 메뉴에서 선택한다. 메소드 이름이 프롬프트되면 addProperty를 입력하고 이 메소드가 두 개의 매개변수(Properties prop & Strings)를 갖고 있음을 확인한다. (Listing 9).

Listing 9. 추출된 addProp()
 import java.util.Properties; import java.util.StringTokenizer; public class Extract { public static void main(String[] args) { Properties props = new Properties(); for (int i = 0; i < args.length; i++) { if (args[i].startsWith("-D")) { String s = args[i].substring(2); addProp(props, s); } } } private static void addProp(Properties props, String s) { StringTokenizer st = new StringTokenizer(s, "="); if (st.countTokens() == 2) { props.setProperty(st.nextToken(), st.nextToken()); } } } 

Extract Local Variable 리팩토링은 직접 사용되고 로컬 변수에 먼저 할당되는 익스프레션을 취한다. 이 변수는 익스프레션이 있었던 곳에서 사용된다. 변수를 제공하라는 프롬프트가 뜨면 key를 입력한다. 선택된 모든 익스프레션을 새로운 변수에 대한 레퍼런스로 대체하는 옵션이 있다. 이것은 종종 적절하지만 nextToken() 메소드의 경우에는 그렇지 않다. 호출될 때마다 매번 다른 값을 리턴한다.이 옵션에는 선택되지 않았다. (그림 6).

그림 6. 선택된 모든 익스프레션을 대체하지 않는다.
Extract variable

그런 다음 st.nextToken()에 대한 두 번째 호출에 대해 이 리팩토링을 반복한다. 이번에는 새로운 로컬 변수 value를 호출한다. Listing 10은 두 개의 리팩토링 후의 코드 모습이다 .

Listing 10. 리팩토링 후의 코드
 private static void addProp(Properties props, String s) { StringTokenizer st = new StringTokenizer(s, "="); if(st.countTokens() == 2) { String key = st.nextToken(); String value = st.nextToken(); props.setProperty(key, value); } } 

이러한 방식으로 변수를 도입할 때 여러 이점이 있다. 우선 익스프레션에 의미가 풍부한 이름을 제공함으로서 코드가 무엇을 수행하는지가 분명해진다. 둘째로, 익스프레션이 리턴하는 값을 쉽게 검사할 수 있기 때문에 코드 디버깅이 더욱 쉽다. 마지막으로 익스프레션의 많은 인스턴스들이 하나의 변수로 대체될 수 있는 경우 더욱 효율적이다.

Extract Constant는 Extract Local Variable와 비슷하지만 정적 상수 익스프레션을 선택해야한다. 이것은 하드 코딩된 숫자와 스트링을 코드에서 제거하는데 유용하다. 예를 들어, 위 코드에서 이름 값 쌍을 정의하면서 명령행 옵션을 위해 -D" 를 사용했다. 코드에서 -D" 를 하이라이트 하고 Refactor > Extract Constant를 선택한 다음 상수 이름으로 DEFINE을 입력한다. 이 리팩토링은 코드를 변경할 것이다. (Listing 11).

Listing 11. 리팩토링된 코드
 public class Extract { private static final String DEFINE = "-D"; public static void main(String[] args) { Properties props = new Properties(); for (int i = 0; i < args.length; i++) { if (args[i].startsWith(DEFINE)) { String s = args[i].substring(2); addProp(props, s); } } } // ... 

각 Extract... 리팩토링의 경우 상응하는 인라인 ... refactoring이 있어 반대 작동을 수행한다. 예를 들어 위 코드의 변수 s를 강조하고 Refactor > Inline...를 선택하고 OK를 누르면 Eclipse는 addProp()에 대한 호출에 직접 args[i].substring(2) 익스프레션을 사용한다:

 if(args[i].startsWith(DEFINE)) { addProp(props,args[i].substring(2)); }

필드의 캡슐화
객체의 내부 구조를 노출하는 것은 좋은 관례로 여겨지지 않았다. Vehicle 클래스와 이것의 하위클래스가 프라이빗 필드 또는 보호 필드를 갖고 있고 퍼블릭 setter와 getter 메소드가 액세스를 제공하는 이유도 그것이다. 이러한 메소드들은 두 가지 다른 방법으로 만들어진다.

이러한 메소드들을 만드는 한 가지 방법은 Source > Generate Getter and Setter를 사용하는 것이다. 각각의 필드가 갖고 있지 않은 제안된 getter와 setter 메소드가 있는 다이얼로그 박스를 디스플레이 할 것이다. 이것은 리팩토링은 아니다. 새로운 메소드를 사용하기 위해 필드에 대한 레퍼런스를 업데이트 하지 않기 때문이다. 이 옵션은 시간 절약에는 효과가 있지만 클래스를 처음 만들 때 가장 적합하다. 또는 새로운 필드를 클래스에 추가할 때도 적절하다.

getter와 setter 메소드를 만드는 두 번째 방법은 필드를 선택하고 메뉴에서 Refactor > Encapsulate Field를 선택하는 것이다. 이 메소드는 한번에 한 필드에 대한 getter와 setter를 만들지만 Source > Generate Getter and Setter와는 달리 필드에 대한 레퍼런스를 새로운 메소드에 대한 호출로 변경한다.

새로운 Automobile 클래스로 시작해보자 (Listing 12).

Listing 12. Automobile 클래스
 public class Automobile extends Vehicle { public String make; public String model; } 

Automobile을 인스턴스화하는 클래스를 만들고 make 필드에 직접 액세스한다. (Listing 13).

Listing 13. Automobile의 인스턴스화
 public class AutomobileTest { public void race() { Automobilecar1 = new Automobile(); car1.make= "Austin Healy"; car1.model= "Sprite"; // ... } }

필드 이름을 강조하고 Refactor > Encapsulate Field를 선택하여 make 필드를 캡슐화한다. 다이얼로그에서 getter와 setter 메소드용 이름을 입력한다. 예상했겠지만 이는 기본적으로 getMake()setMake() 이다. 필드와 같은 클래스에 있는 메소드가 필드에 직접 액세스 할 것인지 또는 이 레퍼런스가 변경되어 모든 다른 클래스 처럼 액세스 메소드를 사용할 것인지를 선택한다. (그림 7).

그림 7. 필드의 캡슐화
Encapsulating a field

OK를 누른 후, Automobile 클래스에 있는 make 필드는 프라이빗이 되고 getMake()setMake() 메소드를 갖게된다. (Listing 14).

Listing 14. 리팩토링 후의 Automobile 클래스
 public class Automobile extends Vehicle { private String make; public String model; public void setMake(String make) { this.make = make; } public String getMake() { return make; } } 

AutomobileTest 클래스는 업데이트 되어 새로운 액세스 메소드를 사용한다. (Listing 15).

Listing 15. AutomobileTest 클래스
 public class AutomobileTest { public void race() { Automobilecar1 = new Automobile(); car1.setMake("Austin Healy"); car1.model= "Sprite"; // ... } }

Change Method Signature
마지막 리팩토링(Change Method Signature)은 사용하기 가장 어렵다. 이것이 무엇을 수행하는지는 매우 명확하다. 매개변수, 가시성, 메소드의 리턴 타입을 변경한다. 분명하지 않은 것은 이러한 변경이 메소드 또는 그 메소드를 호출하는 코드에 대한 영향이다. 여기에 마술은 존재하지 않는다. 리팩토링되는 메소드에서 변경으로 인해 문제가 생긴다면 리팩토링 작동은 이를 거부한다. 어쨌든 리팩토링을 수락하여 나중에 문제를 정정하거나 리팩토링을 취소해야 한다. 리팩토링이 다른 메소드에서 문제를 일으키면 이것은 무시되고 리팩토링 후에 스스로 문제를 해결해야 한다.

Listing 16. MethodSigExample 클래스
 public class MethodSigExample { public int test(String s, int i) { int x = i + s.length(); return x; } } 

test() 메소드는 다른 클래스의 메소드에 의해 호출된다. (Listing 17).

Listing 17. callTest 메소드
 public void callTest() { MethodSigExample eg = new MethodSigExample(); int r = eg.test("hello", 10); }

첫 번째 클래스에서 test를 하이라이팅하고 Refactor > Change Method Signature를 선택한다. 다이얼로그 박스가 나타난다. (그림 8).

그림 8. Change Method Signature 옵션
Change Method Signature options

이 첫 번째 옵션은 메소드의 가시성을 변경하는 것이다. 이 예제에서 이를 protected 또는 private으로 변경하여 두 번째 클래스의 callTest() 메소드가 접근할 수 없도록 한다. Eclipse는 리팩토링을 실행하는 동안 이 에러를 정지하지 않는다. 적절한 값을 선택하는 것은 여러분의 몫이다.

다음 옵션은 리턴 유형을 변경하는 것이다. 예를 들어 리턴 유형을 float로 변경한다고 해서 에러를 정지할 수 없다. test() 메소드의 리턴 문의 int가 자동으로 float을 프롬프트하기 때문이다. 그럼에도 이것은 두 번째 클래스의 callTest()에서 문제를 일으킨다. floatint로 변환될 수 없기 때문이다. test()에 의해 리턴된 리턴 값을 int에 캐스팅하거나 callTest()r 타입을 float으로 변경한다.

String에서 int으로 첫 번째 매개변수 유형을 변경한다면 비슷한 것이 고려될 수 있다. 이것은 리팩토링 실행 중 정지된다. 리팩토링 되고있는 메소드에 문제를 일으키기 때문이다: intlength() 메소드를 갖고 있지 않다. 이를 StringBuffer로 바꾸면 문제로 인해 정지되지 않는다. length() 메소드가 없기 때문이다. 물론 이것 역시 callTest() 메소드에 문제를 일으킨다. test() 를 호출할 때 String을 전달하기 때문이다.

앞서 언급했지만 리팩토링이 에러라는 결과를 내는 경우, 정지 되든 안되든 경우에 따라 에러를 정정하여 지속할 수 있다. 또다른 방법으로는 에러를 사전점유하는 것이다. i 매개변수를 제거하려면 리팩토링 되고있는 메소드에서 이에 대한 레퍼런스를 제거할 수 있다. 이 매개변수를 제거하면 더욱 부드러워 진다.

Default Value 옵션을 마지막으로 설명하겠다. 이것은 매개변수가 메소드 신호에 추가될 때에만 사용된다. 매개변수가 콜러에 추가될 때 값을 제공하는데 사용된다. 예를 들어 n이라는 이름을 가진 String 타입의 매개변수와 world의 디폴트 값을 callTest() 메소드의 test()에 대한 호출에 추가하면 다음과 같이 된다 :

 public void callTest() { MethodSigExample eg = new MethodSigExample(); int r = eg.test("hello", 10, "world"); } 

요약
Eclipse의 툴을 사용하면 리팩토링이 쉽다. 이 툴에 익숙해지면 생산성도 향상된다. 프로그램 기능을 반복적으로 추가하는 개발 방식에서는 프로그램 디자인의 변경 및 확장 기술로서 리팩토링에 의존한다. 하지만 리팩토링에서 요구하는 공식적 방법을 사용하지 않더라도 Eclipse의 리팩토링 툴은 일반적인 코드 변경을 가능하게 하는 방식을 제공한다.

참고자료

  • Refactoring: Improving the Design of Existing Code by Martin Fowler, Kent Beck, John Brant, William Opdyke, and Don Roberts (Addison-Wesley, 1999).

  • Eclipse In Action: A Guide for Java Developers, by David Gallardo, Ed Burnette, and Robert McGovern (Manning, 2003).

  • Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (Addison-Wesley, 1995).

  • Design Patterns use C++; for a book that translates patterns to the Java language, see Patterns in Java, Volume One: A Catalog of Reusable Design Patterns Illustrated with UML, Mark Grand (Wiley, 1998).

  • Extreme Programming Explained: Embrace Change, by Kent Beck (Addison-Wesley, 1999).

웹 사이트

developerWorks 튜토리얼

Posted by tornado
|

출처 : http://network.hanbitbook.co.kr/view_news.htm?serial=709

 

†역자 주: 본 기사는 원문의 절반도 안되는 내용이나 우선은 역자 자신이 딱 이거다 싶은 내용과 직접 사용해본 내용만을 번역해 놓은 것이다. 스트러츠를 계속 사용하면서 이해가 되는 부분은 차츰 하나씩 번역해나갈 생각이다.

스타일시트나 다른 HTML 자원을 참조할 시에는 <html:link rewrite>를 사용하라

html:link rewrite 태그는 컨텍스트 상대적인 URL을 사용할 수 있다. 또한, 필요하다면 URL을 인코딩하는 것도 가능하기 때문에 컨테이너가 보안을 관리하는 자원의 경우 이용할 수 있다.
예제)<LINK REL=’stylesheet’ HREF=”’<html:rewrite page=’/path/to/CSS”/>’ TYPE=’text/css’>
정적인 페이지 접근을 위한 액션(Action)은 어찌 할 것인가?

모델 2 환경에서 페이지는 깔끔하지만, 처리한 내용만 보여주기 때문에 단순하다고 할 수 있다. 모델 2 환경에서는 액션을 먼저 거친 후, 페이지로 이동을 해야 한다(바로 이 부분이 모델 1에서 웹 프로그래밍 하던 사람들이 고생하는 것으로 역자 또한 그러하다). 그리고 액션은 페이지가 request, session context에 담아서 보낸 정보를 조합하여 나타낼 뷰를 결정해야 한다. 그러나 액션은 뷰의 주소를 모른다(ActionMapping이 알고 있음). 모든 페이지는 하나의 액션 핸들러를 가지고 있어야 한다. 하나의 액션은 다수의 다른 뷰 페이지를 핸들링 할 수 있으며 하나의 페이지는 여러 다른 액션에 의해서 핸들링 될 수 있다. 그러나 하나의 페이지는 적어도 하나의 액션 핸들러를 반드시 가지고 있어야 한다. 여기서 한가지 질문! 그렇다면 static한 페이지에 접근하는 경우, 모두 액션을 가지고 있어야 하는가? 100개의 페이지가 있다면 그것들 모두 액션을 가지고 있어야 하는가? 이런 경우를 대비해서 스트럿츠에는 ForwardAction이라는 액션을 제공한다. 액션 맵핑에 아래와 같이 입력해주면 된다.
예제)<action    name="boardForm"            parameter="/boardcreate.jsp"            path="/boardcreateform"            type="org.apache.struts.actions.ForwardAction"            validate="false"/>
다른 건 다 무시하고, parameter와 type만 살펴보자. Boardcreate.jsp은 새로운 글을 입력하는 페이지로, 실제로 액션이 있어도 아무런 역할을 하지 못한다. 이 경우 org.apache.struts.actions.ForwardAction를 액션 타입으로 지정하고, parameter란에 이동하고자 하는 페이지 주소를 적는다. 이 경우는 /boardcreateform.do를 (*.do으로 서블릿 매핑이 되었다고 가정) 주소창에 치면 우선은 ForwardAction을 통과해서 boardcreate.jsp로 포워드 된다. 이렇게 함으로써 모델 2에서 Action을 거쳐서 페이지로 이동해야 한다는 조건을 만족하게 된다.

액션이 하는 일이 매우 작다면 하나의 액션을 가지고 여러 일을 처리하는 방법을 간구하라

간단히 게시판을 만드는 중이었다. 게시물 삭제, 수정, 수정하는 폼, 등록, 답글 달기, 주석 달기 등 이렇게 하나하나 액션을 만들다 보니 관리해야 할 액션 클래스만 몇 십 개가 되어버렸다. 이렇게 하나의 단위로 묶을 만한 액션들 다수가 존재한다면, DispatchAction의 사용을 고려해봐야 한다. 곧바로 예제를 보겠다.
예제)<action            input="/board/bullcreateform.jsp"            name="bullForm"            parameter="method"            path="/bull"            scope="request"            type="webfx.board.struts.action.BullAction"            validate="false"/>
다른 액션 맵칭하는 방법과 거의 다르지 않다. 여기서 특이하게 볼 내용은 parameter이다. 그리고 BullAction의 코드는 아래와 같다.
…public BullAction extends org.apache.struts.actions.DispatchAction {	public ActionForward create(		ActionMapping mapping,		ActionForm form,		HttpServletRequest request,		HttpServletResponse response)		throws Exception {	…};	public ActionForward remove(		ActionMapping mapping,		ActionForm form,		HttpServletRequest request,		HttpServletResponse response)		throws Exception {	….	}	등등…}
이 소스에는 execute라는 메소드가 없다. 단지 Method Signature가 이름을 제외하고 execute와 다른 create, remove 함수가 있다. 그리고 이 액션은 org.apache.struts.actions.DispatchAction을 상속했다. 우리가 parameter로 지정한 값은 request로 넘어올 파라미터 이름이다. 그리고 이 파라미터에 우리가 실행시키고자 하는 메소드 명을 지정하면 그 메소드가 실행된다. 예를 들어 /bull.do?method=create라고 실행시켰다면 BullAction에서 create 함수를 실행시키게 된다. 이처럼 관리하기 힘들 정도로 액션이 늘어날 경우에는 DispatchAction을 사용하는 것도 고려해봐야 한다.

데이터와 Value Objects을 교환하기 위해서 populate 유틸리티를 사용하라

Jakarta.apache.org 사이트에 있는 Commons에 속한 BeanUtils를 말하는 겁니다. BeanUtils.populate()와 describe() 메소드는 beans와 임의 타입 간의 데이터 전송 사용을 위해서 사용될 수 있다. 요구되는 것은 대응되는 public 형의 프로퍼티 이름들과 네이티브 타입이다. Wrapping Object 즉, Integer 같은 것들은 지원이 되지만 그 외의 타입은 지원되지 않는다(예, Date).

describe 메소드는 소스 빈을 스트링 값을 가지고 있는 맵으로 만든다. populate 메소드는 그러한 맵을 읽어서 타겟이 되는 빈에 프로퍼티를 적용하는 역할을 한다. 만약 타겟 타입이 스티링이 아니라면 필요한 타입으로 변경이 된다. 만약 변환이 실패하면 예외가 발생한다. 다음이 두 빈(Bean) 간의 데이터 이동하는 소스이다.
BeanUtils.populate(target, BeanUtils.describe(source));
몇몇 경우에 필요한 만큼만 조절한 ActionBean에서 또한 조절된 값을 가지고 있는 Map을 만들기 위해서 커스터마이징한 describe 메소드가 필요할 때가 있다. 다른 값들 (즉, 지원되지 않는 타입)은 직접 세팅해 줄 수 있다.
BeanUtils.populate(target, source.describe());tartget.setDate(source.getDateBinary());
BeanUtils을 직접 이용하는 것보다 유틸클래스를 하나 만들어, 그곳에서 BeanUtils 클래스를 이용하도록 만드는 게 더 좋을 것이다. 예를 들면 아래와 같다.
valueUtils.populate(ValueObject target, ActionForm source);valueUtils.populate(ActionForm target, ValuteObject source);
공통적으로 사용되는 속성들에 대한 객체를 만들어 사용하라

웹 애플리케이션 안에서 애플리케이션에 전체적으로 걸쳐서 사용되어야 하는 속성이 있다. 사용자 정보, 북마크, 메뉴 세팅, 다른 프로퍼티 등등이 이것에 속할 수 있다. 이때에는 이런 것들을 각각 따로 관리하는 것보다 이런 것들을 한꺼번에 래핑하는 객체를 만들어서 사용하자.

애플리케이션을 위한 Base Action을 사용하라

애플리케이션 액션들은 몇몇 기본적인 작업을 처리한다. 이러한 태스크를 일관적으로 구현하기 위해, 애플리케이션의 다른 액션들이 서브클래싱하여 사용할 수 있는 Base 클래스를 이용하자! 만약 개개 액션의 execute 메소드 안에서 중요하게 처리되어야 할 일이 있다면 서브 클래스들이 execute() 메소드를 통해서 실행시킬 추상 메소드를 사용하는 것이 좋은 접근 방법이다. Base 클래스는 기본적으로 자신이 해야 할 일을 하고 그것이 모두 잘 처리 되었을 때, 새로운 메소드를 실행시키고 그 결과를 리턴한다. 이 부분 때문에 새로운 메소드의 서명을 변경할 수 있다.
Public ActionForward execute(ActionMapping mapping,				ActionForm form,				HttpServletRequest request,				HttpServletResponse response)			throws IOException, ServletException {		// Application specific bevaviour		// if evevrything is kosher call subclass		return (executeAction(mapping, form, request, response, myParameter))}
executeAction은 Base 클래스의 추상 클래스이다.

이중으로 submit이 일어나는 것을 막기 위해서 Action Token을 사용하라

스트럿츠의 액션에 one-use tokens을 만들어 담는 메소드들이 있다. 폼이 작성될 때 세션에 토큰이 위치하게 되고, 이 토큰은 HTML 폼에 hidden 프로퍼티로 포함된다. 그 폼이 리턴 될 때 토큰은 validation이 된다. 만약 validation에 실패하면 벌써 그 폼은 submit이 된 것이므로 사용자는 그에 대한 알림 메시지를 받을 수 있다.
  • saveToken(request)
  • on the return trip
    • isTokenValid(request)
    • resetToken(request)
스트럿츠를 이용한 웹 애플리케이션 개발 사이클
  1. 화면 설계서를 개발한다. (화면 요구사항 설계)
  2. 화면 요구사항을 만족하기 위한 Helper 클래스를 개발한다. (value object)
  3. 워크플로우 요구사항을 개발한다. (화면 이동 등의 스토리보드 작성)
  4. 워크플로우 요구사항을 만족시키기 위한 ActionMapping을 개발한다.
  5. ActionMapping을 사용하여 워크 플로우를 검증하기 위한 Stub JSP 페이지를 작성한다.
  6. ActionMapping에 의해 호출 되는 액션을 개발한다.
  7. Stub Jsp를 작성한다.
Posted by tornado
|
 

 

 

 

동적 서블릿 이미지 메이킹을 마음대로!
Level: Introductory
 

Dan Becker
소프트웨어 개발자, IBM Software Group
2002년 11월

웹 사이트를 코딩하고 지원하거나 인터넷에 페이지를 갖고 있다면 독자들의 필요를 충족시키는 이미지를 제공하는 것이 얼마나 어려운 일인지 알 것이다. JavaServer Pages (JSP) 태그를 이용하여 이미지 관리를 시도해보자.
이 글은 IBM developerWorks 저널에 2002년 11월 이슈 부분에 기재되었음을 밝혀둔다.

세상을 지배하라! 그렇지 않다면, 적어도 이미지라도 지배하라!
사이트 개발자 또는 페이지 작성자로서 다양한 선호를 만족시키는 것은 어려운 일이라는 것을 안다. 이를 수작업으로 수행하기 위해서는 사이트의 이미지 하나하나를 웹 사이트가 지원하는 이미지 사이즈로 변환해야한다. 그런다음 사이트의 각 페이지에 이미지 태그를 조정하여 각각의 태그가 이미지 사이즈를 적절하게 반영할 수 있도록 해야한다. 이미지를 변경하지 않고서는 HTML img 태그의 넓이와 높이를 간단히 변경시킬 수 없다. 이는 저대역 사용자들은 큰 이미지를 다운로드하여 클라이언트 측에서 리사이징을 해야한다는 것을 뜻한다. 이러한 종류의 이미지 관리는 성가실 뿐 아니라 에러를 양산해낸다. 그리고 대부분의 웹 사이트가 다양한 이미지 사이즈를 제공하지 않는 이유를 쉽게 알 수 있다.

문제는 기술의 문제가 아니다. 자바 프로그래밍으로 이미지를 다양한 사이즈나 포맷으로 변환하기는 쉽다. 서비스만의 문제 또한 아니다. 웹 서비스를 사용하여 각자의 필요에 맞춰 페이지를 커스터마이징하는 것이 일반적이기 때문이다. 문제는 전개와 관리가 쉽도록 기술과 서비스를 조합하는 일이다.

이 글은 JavaServer Pages (JSP) 태그를 사용하는 솔루션으로 이미지를 관리하는 방법을 다룬다. 예를들어 HTML에서 이미지 태그를 코딩하고 다양한 버전의 이미지 사이즈를 갖추는 대신 다음과 같이 하는것이다:


<img src="images/LazyDog.jpg" width="800" height="600" >

 

사용자의 선호에 맞춰 이미지를 자동으로 사이징하는 태그를 갖추는 것이 합리적이다:


<util:imagesizer src="images/LazyDog.jpg"/>

 

사용자가 다양한 사이즈 중에서 선택하고 선호도에 따라 사이트상의 모든 이미지가 바뀌도록 하는 것이 가능하다. 그림 1의 샘플 이미지를 보자. 또한 넓이와 높이 속성을 삽입하고 태그를 수동으로 편집하는 일 따위는 하지 않아도 된다.

그림 1. 이미지 선택이 있는 JSP 페이지

 


한번도 JSP 커스텀 태그를 본 적이 없다면 예제에서 신택스를 연구해보자. JSP 커스텀 태그를 HTML 태그와 비교해보자:

태그 개발자가 만든 imagesizer가 있다.


이 태그는 프리픽스 util이 있는데 태그 세트들을 자바 프로그래밍의 패키지 이름과 흡사한 라이브러리들로 그룹핑한다. 새로운 프리픽스를 만들거나 라이브러리에 있는 디폴트 이름을 사용할 수 있다.


이 태그는 새로운 XML 방식의 끝내는(ending) 태그를 가지고 있다: "/>".
HTML 태그처럼, JSP 태그도 많은 속성이 있다. 여기에서는 src가 제시되었다. 또한 바디가 있으며 바디 안에는 다른 태그들도 가질 수 있다. HTML img 태그를 모방하는 것이기 때문에 JSP 이미지 사이징 태그용 바디는 없다.

JSP 페이지가 커스텀 image-sizer 태그를 사용할 때, 태그의 자바 구현은 이미지 파일을 찾아 적절한 사이즈로 변환하고 이 이미지를 독자에게 제공한다. 태그는 퍼블리싱하기 전에 이미지 변환 부터 사이트 매니저를 저장한다. 또한 웹 페이지 작성 작업을 단순화한다. 단지 하나의 페이지가 많은 이미지 사이즈 선택을 핸들하는데 필요하기 때문이다. 무엇보다도, 중요한 것은 각자의 사이트 이미지에 이러한 유연성을 제공한다면 인기있는 사이트가 될 것이다.

웹 서버
이제는, 클라이언트(웹 브라우저를 이용하는 독자)가 JSP 페이지를 제공하는 사이트를 방문할 때 그 배후에 어떤일이 벌어지는지 알아보자. 세 개의 인터랙션이 이루어진다. (그림 2):

그림 2. 웹 클라이언트와 서버 간 인터랙션

 


첫 번째 경우, 브라우저가 HTML 파일이나 이미지 파일 같은 정적 문서를 요청한다고 가정해보자. 서버는 파일 공간에 리소스를 위치시키고 브라우저에 파일을 제공한다. 문서를 요청하고 요청에 응답하는 것은 HTTP에 정의되어 있는데 이것은 인터넷 상에서 클라이언트-서버 인터랙션의 기초가 된다. 웹 서버는 요청을 완벽하게 핸들하고 웹 애플리케이션 서버나 서블릿 콘테이너와 인터랙션 할 필요가 없다.

두 번째 경우 브라우저가 자바 서블릿을 포함하는 웹 리소스를 요청한다고 가정해보자. 자바 서블릿은 웹 서버가 자바 프로그래밍 언어를 사용하여 서버상에서 태스크를 수행하도록 한다. 서블릿들은 효율적이어서 CGI와 서버측 JavaScript 같은 오래된 기술보다 메모리와 프로세싱 파워를 덜 사용한다. 서블릿은 IBM WebSphere Application Server과 Apache Tomcat 같은 웹 서버에서 다른 기술들에 비해 이동성이 강하고, 다양한 플랫폼 상에서 같은 서블릿을 구동할 수 있는 서블릿 콘데이너를 지원한다. 마지막으로, 자바언어 고유의 안정성으로 인해 잘못된 서블릿이 웹 서버에 영향을 주는 경우도 드물다. 웹 서버는 적절한 서블릿을 찾아 필요할 경우 서블릿 소스 코드를 컴파일하고 서블릿 프로세싱 결과를 요청자에게 리턴한다. 자주 요청되는 서블릿은 서버 메모리에 캐싱된다. (그림 2).

세 번째 경우, 브라우저가 JSP 페이지를 포함하는 웹 페이지를 요청한다는 것을 가정해보자. JSP 페이지는 정보를 디스플레이 하는 작업을 쉽게 하고 동적 콘텐트를 정적 페이지와 분리하는 것을 돕는다. 웹 페이지 디자이너는 HTML 라이브러리에 있는 다른 태그인 것 처럼 하여 JSP 태그를 사용한다. JSP 프로그래머는 JSP 프로그래밍 스팩에 따라 태그를 구현한다.

이번에는 이미지-사이징 JSP 태그를 구현하고 JSP 코드를 작성하는 방법을 알아보자. 웹 콘테이너의 관점에서 볼 때 JSP 페이지는 자바 서블릿과 밀접하게 관련되어 있다. 텍스트 기반 JSP 페이지는 웹 콘테이너에 의해 자바 구현으로 변환된다. 웹 콘테이너는 자바 구현을 찾아 자바 서블릿 같은 구현을 처리하고 코드를 구동하여 프로세싱 결과를 클라이언트에 리턴한다. 많은 레이어와 리다이렉트가 있는 듯이 보이지만 디스패칭은 빠르고 사용자에게도 투명하다. 서블릿과 마찬가지로 자주 요청되는 JSP 페이지는 서버 메모리에 캐싱된다.

JSP 커스텀 태그 작성하기
웹 서비스가 JSP 페이지 요청을 어떻게 처리하는지 보았다. 이제는 JSP 커스텀 태그를 어떻게 구현하는지 보자. JSP 태그는 Java Standard Template Library(JSTL) 같은 표준 라이브러리와 일명 커스텀 태그라고 하는 스스로 작성한 라이브러리에도 해당된다는 것을 명심하라. 일반적으로 커스텀 태그는 특정 문제 도메인을 다룬다. 이 글에서는 이미지를 관리하는 방법을 다루겠다. 현재 Java 2 Extended Edition (J2EE) Versions 1.2와 1.3은 JSP Version 1.2를 사용한다. 현재 Sun은 JSP 스팩 2.0 버전을 발표했다. 이 새로운 스팩은 커스텀 태그를 구현하는 방식에 있어서 큰 변화가 없다.

표준과 커스텀 태그 라이브러리를 taglib 디렉티브를 통해 JSP 페이지로 반입할 수 있다:


<%@ taglib uri='imagesizer.tld' prefix='util' %>

 

이 디렉티브는 태그 라이브러리 디스크립터 파일의 위치를 명명한다. 여기서는 imagesizer.tld로 명명되었다. 그리고 사용된 프리픽스는 util로 명명되었다. 태그 예제에서 보았듯이 프리픽스와 이름으로 태그를 사용할 수 있다:


<util:imagesizer src="images/LazyDog.jpg"/>

 

태그 라이브러리 디스크립터는 웹 콘데이너에게 사용가능한 태그가 무엇인지 그들이 어떻게 작용하는지를 설명한다. Listing 1은 그 예제이다. 이 파일은 XML 포맷으로 되어있고 읽기 쉽다. 하지만 IBM WebSphere Studio Application Developer 같은 애플리케이션 개발 플랫폼은 필드를 채우고 파일을 검사하는 것을 돕는다. 가장 중요한 정보는 태그 엘리먼트이다. 이것은 JSP 커스텀 태그의 이름과 태그를 구현하는 자바 클래스를 정의한다. 또한 태그가 받아들이는 모든 속성과 바디 콘텐트를 보여준다.

Listing 1. Tag Library Descriptor (TLD) 발췌
<taglib >
  <tlibversion> 1.0 </tlibversion>
  <jspversion> 1.1 </jspversion>
  <tag>
    <name>imagesizer</name>
    <tagclass>tags.ImageSizerTag</tagclass>
    <bodycontent>empty</bodycontent>
    <attribute>
      <name>src</name>
      <required>required</required>
    </attribute>
    <attribute>
      <name>alt</name>
    </attribute>
    <attribute>
      <name>quality</name>
    </attribute>
  </tag>
</taglib>

 

이 예제에서 태그는 src 속성이 요구하는 세 개의 속성을 갖고 있다. alt 속성은 HTML img alt 속성을 모방한 것이다. 이 JSP 태그를 확장하여 다른 이미지 속성을 포함시켜도 된다.

JSP 커스텀 태그를 작성하는 다음 단계는 태그 용 자바 코드를 구현하는 것이다. 이 글에서 imagesizer 태그는 tags.ImageSizerTag 자바 클래스에서 구현된다. J2EE 커스텀 태그 지원 대부분은 javax.servlet.jsp.tagext 패키지에 위치해있다. imagesizer 클래스는 표준 TagSupport를 확장한다. 이것은 바디 없이 태그를 구현한다. 이것의 자손 클래스는 BodyTagSupport인데 이것은 바디가 있는 태그를 구현한다. 두 클래스 모두 Tag 인터페이스를 구현한다. doStartTag과 doEndTag 메소드는 태그가 처음 읽혀지고 태그가 웹 컨테이너에 의해 완전히 읽혀진 후에 호출된다. ImageSizer 태그는 doEndTag 만 구현한다.

TagSupport 클래스에서 PageContext 클래스는 JSP 페이지와 관련된 중요한 정보에 액세스 하도록 한다. 예를들어, PageContext는 HttpRequest와 HttpResponse 객체에 액세스를 가능하게 한다. 이러한 객체들은 값을 읽고 응답을 작성하는데 필수적이다. 요청은, 사용자 선택을 트래킹하고 페이지에서 페이지로 값을 수행할 경우 HttpSession로 액세스를 허용한다. PageContext는 ServletContext로 액세스를 허용하는데 이것은 서블릿 경로, 이름, 기타정보를 위치시키는데 도움이 된다. ImageSizer 코드(Listing 2)에서, PageContext 객체에 대한 레퍼런스와 정보가 있다. 그림 3은 이들 클래스들의 관계이다. 다른 표준 클래스 다이어그램 처럼 실선으로 된 박스는 클래스를 나타내고 점선 박스는 인터페이스를 나타낸다. 상속은 파생된 클래스나 인터페이스부터 부모까지 선으로 이어져있다.

Listing 2. ImageSizerTag doEndTag 구현
// Implement the tag once the complete tag has been read.
public int doEndTag() throws JspException {

  // Move request data to session.
  int outputSize = 0;
  String sizeVal = request.getParameter( REQUESTSIZEKEY );
  if ( sizeVal != null ) {
    session.setAttribute( REQUESTSIZEKEY, sizeVal );
    sizeVal = (String) session.getAttribute( REQUESTSIZEKEY );
    if ( sizeVal != null ) {
      outputSize = Integer.parseInt( sizeVal );
    }
  }
 
  // Get specified image locally.
  String contextPath = getContextPath( request );
  Image image = Toolkit.getDefaultToolkit().getImage(contextPath + src );
  ImageSizer.waitForImage( image );
  int imageWidth = image.getWidth( null );
  int imageHeight = image.getHeight( null );

  if (( imageWidth > 0 ) && ( imageHeight > 0 )) {
    if (( outputSize > 0 ) && ( outputSize != imageWidth )) {
      // Convert image to new size.
      Image outputImage = ImageSizer.setSize( image, outputSize, -1 );
      ImageSizer.waitForImage( outputImage );
      int outputWidth = outputImage.getWidth( null );
      int outputHeight = outputImage.getHeight( null );
 
      if ( outputWidth > 0 && outputHeight > 0 ) {
        // Change image file name to xxxx.size.jpg
        String originalSrc = src;
        int lastDot = src.lastIndexOf( '.' );
        if ( lastDot > -1 ) {
          src = src.substring( 0, lastDot + 1 );
        }
        setSrc( src + outputSize + ".jpg" );

        // Write new size image to JPEG file.
        File file = new File( contextPath + src );
        if ( !file.exists() ) {
          out.println( "" );
          FileOutputStream fos = new FileOutputStream( contextPath + src );
          ImageSizer.encodeJPEG( fos, outputImage, quality );
          fos.close( ) ;
        }
       
        imageWidth = outputWidth;
        imageHeight = outputHeight;
      }
    } // if outputSize
  } // if image found

  // Produce output tag.
  out.print( "<img src=\"" + src + "\"" );
  // Add alt text, if any
  if ((alt != null ) && ( alt.length() > 0 )) {
    out.print( " alt=\"" + alt + "\"" );
  }
 
  // Add proper width, height.
  out.print( " width=\"" + imageWidth + "\" height=\"" +
    imageHeight + "\"" );
  out.println( ">" );

  return EVAL_PAGE;
} // doEndTag

 

그림 3. javax.servlet.jsp.tagext 클래스

 


ImageSizerTag 클래스의 doEndTag 메소드는 Listing 2에 나와있다. 이것은 JSP 커스텀 태그를 구현하는데 필요한 거의 모든 자바 코드라고 볼 수 있다. 큰 블록이지만 메소드를 완벽하게 보는데 도움이 된다. 우선 모든 HTTP 요청 매개변수는 HTTP 세션에 저장된다. 이것은 이미지 사이즈에 대한 사용자 선택 같은 속성을 가져다가 세션에 저장하여 페이지에 따라 사용자가 이를 따르도록 한다. 이 태그의 기능을 확장하기 위해서 이것을 확장하여 쿠키에 선택을 저장하여 사용자가 다음에 이 사이트를 방문할 때 선택을 사용하도록 할 수 있다.

다음 단계는 디폴트 이미지를 로딩하는 것이다. java.awt.Toolkit은 이미지를 요청하는데 이것은 ImageSizer.waitForImage와 함께 로드되고 적절한 사이징이 검토된다. 로딩을 위해 일시정지가 필요하다. 왜냐하면 자바 이미지는 비동기식으로 로딩되고 요청할 때마다 항상 사용할 수 있는 것이 아니기 때문이다. ImageSizer 보조 클래스는 이 예제에서 전체 이미지 프로세싱을 수행하고 다음 섹션에서는 더 자세하게 다루겠다. 넓이와 높이가 맞으면 이미지는 리사이징이 필요없다. HTML 이미지 태그는 이미지 이름과 현재 사이즈를 사용하여 작성된다. 이것은 HTML 이미지 태그를 모방하기위해 필요한 JSP 구현의 모든것이다.

ImageSizer 보조 클래스는 사용자가 새로운 이미지 사이즈를 요청하면 이미지를 리사이징한다. 이미지 파일은 파일 크기에 따라 새로운 이름이 된다. 이 파일은 JPEG 인코딩이고 파일 시스템에 작성된다. 결과적으로 새롭게 리사이징된 파일은 HTML 이미지 태그 아웃풋에 사용된다. 이 태그의 대안 구현은 파일을 GIF나 PNG 포맷으로 저장하거나 디스크 공간을 저장하기위해 메모리에서 이미지를 제공하는 것이다. Listing 2는 리사이징된 파일을 앞으로 사용할 경우에 대비하여 디스크에 캐싱한다. 따라서 처음의 리사이징은 약간의 서버 프로세싱 시간이 필요하지만 연속적인 이미지 사이즈 요청은 프로세싱이 전혀 필요하지 않다. 사용가능한 디스크 공간을 체크하여 제한된 파일 공간과 클라이언트에게 제공하는 정보의 긴급성 간에 균형을 맞추도록 할 수 있다.

이미지 사이징
지금까지 JSP 태그를 작성하는 단계를 검토했다. ImageSizerTag 클래스는 이미지를 자동으로 리사이징하여 사용자 선택에 맞춘다. 이번에는 ImageSizer 클래스를 사용하여 JPEG 파일로서 이미지를 리사이징하고 저장하는 구체적인 방법을 설명하겠다. 자바 코드로 이미지를 리사이징 할 때 java.awt.Image 클래스의 getScaledInstance 메소드를 사용할 때 쉬워진다. 이 메소드를 새로운 넓이와 높이로 하거나 형태 비율을 보존하기위해 -1에서 1까지의 매개변수 값을 제공한다. 그리고 나서 새롭게 리사이징된 이미지를 얻는다. 하지만 모든 자바 이미지는 즉시 사용할 수 있는 것은 아니다. 따라서 java.awt.MediaTracker 를 사용하여 이미지가 완전히 로딩될 때까지 기다려야한다. ImageSizer의 waitForImage 메소드는 이 코드를 캡슐화한다.

이 예제에서 가장 어려운 디자인 포인트는 이미지 저장 방법을 결정하는 일이다. 자바 프로그래밍에는 이미지 인코딩과 저장에 많은 선택권이 있어 비교를 해야한다.

com.sun.image.codec. 이 패키지는 Java 2 SDK 1.2와 1.3 구현에 사용할 수 있지만 개인 패키지어서 Java 2 버전까지 기다려야한다. 이 패키지는 JPEG 인코딩에 제한이 있다.


Java Image I/O API. 이 패키지는 Java 2 SDK 1.4의 표준이다. 하지만 작성 당시 어떤 J2EE 버전도 SDK 1.4를 사용하지 않는다. 이 패키지는 뛰어난 이미지 조작 기능과 인코딩 옵션을 갖추고 있다.


Java Advanced Imaging API. 이 API는 표준 확장이지만 이것을 사용하려면 웹 마스터가 지원하지도 않는 패키지를 설치해야한다.


ACME GIF Encoder. 이 패키지는 유용하며 예제 코드와 결합할 수 있다. 하지만 비용과 유지 문제가 남아있다. 다른 옵션들과는 다르게 이 소프트웨어는 유료이며 GIF 표준을 완전히 지원하지 않는다.
Listing 3의 경우 com.sun.image.codec 패키지를 사용했는데 IBM WebSphere와 Apache Tomcat 같은 J2EE 1.2 와 1.3 웹 서버 콘테이너에서 사용할 수 있기 때문이다. 인코더는 단순하며 100% 순수한 자바 코드이지만 com.sun 패키지에 있다. 장기적인 시각으로 볼 때 Java Image I/O 패키지는 보완해야 할 사항이 많이있다. 이미지 변형 기능과 많은 파일 포맷을 저장하는 기능이 훌륭하다. Java Image I/O 패키지는 Java 2 version 1.4 이전까지는 표준이 아니다.

어떤 이미징 패키지를 사용할 것인가에 대해 결정이 끝났다면 JPEG 파일을 저장하는 코드는 간단하다. ImageSizer의 encodeJPEG 메소드는 다음 절차를 캡슐화한다:


java.awt.image.BufferedImage 객체-자바 Image 자손-는 리사이징된 아웃풋 이미지에서 만들어진다. 주석은 로고, watermark, 타임스탬프, 이미지 저작권 정보를 추가할 때 코드에 주석이 달린다.


Image에서 BufferedImage로 변환한 후에, 아웃풋 스트림에 JPEGImageEncoder 객체를 만들어라. 아웃풋 인코딩 평가는 0.0 (최악)부터 1.0 (최고) 까지 이다. 0.75는 디폴트이지만 0.95는 좀더 자세한 이미지와 함께 큰 파일 사이즈로 만들어진다.


이미지는 아웃풋 스트림으로 인코딩되고 스트림은 모든 정보가 이미지 파일에 나타나도록 플러쉬된다.

Listing 3. ImageSizer encodeJPEG 구현
// Encodes the given image at the given
// quality to the output stream.
public static void encodeJPEG
  ( OutputStream outputStream,
  Image outputImage, float outputQuality )
  throws java.io.IOException {

  // Get a buffered image from the image.
  BufferedImage bi = new BufferedImage
    ( outputWidth, outputHeight,
      BufferedImage.TYPE_INT_RGB );
  Graphics2D biContext =
    bi.createGraphics( );
  biContext.drawImage
    ( outputImage, 0, 0, null );
  // Additional drawing code, such as
  // watermarks or logos can be placed here.

  // com.sun.image.codec.jpeg package
  // is included in Sun and IBM sdk 1.3.
  JPEGImageEncoder encoder =
    JPEGCodec.createJPEGEncoder
    ( outputStream );
  // The default quality is 0.75.
  JPEGEncodeParam jep =
    JPEGCodec.getDefaultJPEGEncodeParam
    ( bi );
  jep.setQuality( outputQuality, true );
  encoder.encode( bi, jep );
  outputStream.flush();
} // encodeImage

 

여기까지는 이미지를 리사이징하고 저장하는데 필요한 모든 단계이다.

WebSphere또는 Tomcat에서 패키징 및 전개
이번에는 Application Server version 4.0이나 Apache Tomcat version 4.0에서 ImageSizer JSP 태그의 패키지 및 전개 방법을 설명하겠다. 그림 4는 Application Developer 이다. 왼쪽 칼럼의 상단에 있는 Navigator는 웹 애플리케이션의 디렉토리 구조를 나타내고 JSP 태그가 J2EE 스팩에 따라 어떻게 패키지되어야 하는지를 보여준다. J2EE 스팩에 필요하기 때문이 이 디렉토리 구조는 모든 웹 애플리케이션에 일반적이다. 일단 구조가 저장되면 Web Archive (WAR) 파일이 되고 WebSphere, Tomcat, 기타 웹 콘테이너에 쉽게 전송된다. Application Developer 같은 좋은 개발 환경은 다음의 스팩을 따르고 훌륭한 애플리케이션을 만들어낸다.

그림 4. WebSphere Studio Application Developer에서 ImageSizer 패키징

 


ImageSizer 프로젝트에는 소스 코드용 디렉토리가 있다. 최종 WAR 파일에 이 디렉토리를 추가할 것인지의 여부는 개발자가 선택하기 나름이다. webApplication 디렉토리에는 실제 프로그램 코드가 포함되어 있다. 예제 프로젝트에는 PickASize.jsp이라고 하는 테스트 JSP 페이지와 LazyDog.jpg 이라고 하는 테스트 이미지가 포함되어 있다. 일반적으로 이들은 ImageSizer 커스텀 태그의 라이브러리 버전에 포함되지 않는다. 이 태그의 구현은 WEB-INF 디렉토리에 있다. 자바 클래스는 WEB-INF/classes에 있고 Tag Library Descriptor 파일은 WEB-INF/tlds에 있다. 이들은 모든 웹 애플리케이션에 적용되는 표준 디렉토리 위치이다. 이 트리에 있는 다른 파일들은 서버 옵션 설정에 도움을 주지만 그렇지 않더라도 WAR 파일에 필수적인 것은 아니다. Application Developer나 Java SDK를 사용하여 이 애플리케이션 용 WAR 파일을 만들어보라.

To m c a t 에 웹 애플리케이션을 설치하려면 ROOT/webapps 디렉토리에 파일을 놓고 서버가 WAR 파일을 디렉토리 구조로 확장하도록 해야한다. Application Server의 경우, Administrators Console의 웹 애플리케이션 마법사(Web Application wizard)를 사용하여 애플리케이션을 설치할 수 있다. 전개한 후에, http://yourhostname:port/ImageSizer/PickASize.jsp를 방문하여 JSP 페이지를 구동해보라.

결론
지금까지 이미지 사이징을 자동으로 관리하는 JSP 커스텀 태그를 만들었다. 커스텀 태그는 이미지 리사이징 작업에 도움이 되고 웹 사이트를 방문하는 사용자들이 선택을 할 수 있도록 한다. 예제 태그를 확장하여 모든 종류의 이미지 조작을 할 수 있다. 저작권 텍스트, 타임스탬프, 로고, 워터 마크 등이 그것이다. Application Server나 Apache Tomcat에 이를 전개하고 이미지 기반 JSP 페이지를 작성하거나 예제를 사용하여 코드를 실험할 수 있다. 이 글이 JSP 태그에 대해 실질적인 팁을 주었기를 바라며 각자의 필요에 맞춰 기능을 확장해보기를 바란다.


 

참고자료


 
 
필자소개
Dan Becker는 IBM(Austin, Texas)의 소프트웨어 그룹에서 일하고 있다. 현재는 Interactive Financial Services 프로젝트에서 온라인 웹 뱅킹 시스템 작업을 수행하고 있다.

 

 

 

참고문서 : http://www-903.ibm.com/developerworks/kr/java/library/j-jspdwj.html

'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] 리팩토링(Refactoring)  (0) 2004.03.29
[펌] 스트럿츠 이디엄  (0) 2004.03.29
[펌] 고급DAO 사용하기  (0) 2004.03.29
[펌] Torque로 DB에 Query문 출력하기  (0) 2004.03.26
[펌] Torque프로젝트 사용기  (0) 2004.03.26
Posted by tornado
|
DAO 구현 기술

Level: Advanced

Sean C. Sullivan
소프트웨어 엔지니어
2003년 10월 7일

J2EE 개발자들은 Data Access Object (DAO) 디자인 패턴을 사용하여 저수준의 데이터 액세스 로직과 고급 비즈니스 로직을 분리한다. DAO 패턴을 구현하는 것은 단순히 데이터 액세스 코드를 작성하는 것 이상이다.

지난 18개월 동안, 재능 있는 소프트웨어 엔지니어들과 함께 웹 기반의 공급체인 관리 애플리케이션을 구현했다. 우리가 만든 애플리케이션은 선적 상황, 공급 체인 메트릭스, 창고 재고, 운송장, 프로젝트 관리 데이터, 사용자 프로파일 등의 광범위한 데이터에 접근했다. JDBC API를 사용하여 회사의 다양한 데이터베이스 플랫폼에 연결했고 이 애플리케이션에 DAO 디자인 패턴을 적용했다.

그림 1은 애플리케이션과 데이터 소스의 관계이다:

그림 1. 애플리케이션과 데이터 소스
Web applications

DAO의 기초
DAO 패턴은 표준 J2EE 디자인 패턴들 중 하나이다. 이 패턴을 사용하여 저수준 데이터 액세스와 고급 비지니스 로직을 분리한다. 전형적인 DAO 구현에는 다음과 같은 요소들이 있다:

  • DAO 팩토리 클래스
  • DAO 인터페이스
  • DAO 인터페이스를 구현하는 구체적 클래스
  • 데이터 전송 객체들(밸류(value) 객체)

구체적인 DAO 클래스에는 특정 데이터 소스로 부터 데이터에 액세스하는데 쓰이는 로직이 포함되어 있다.

트랜잭션 경계설정(demarcation)
DAO는 트랜잭션 객체들이라는 것을 반드시 기억해야 한다. DAO에 의해 수행되는 각각의 작동(데이터 구현, 업데이트, 삭제)은 트랜잭션과 관련있다. 따라서 트랜잭션 경계설정(demarcation) 개념은 매우 중요하다.

트랜잭션 경계설정은 트랜잭션 영역들이 정의 되는 방식이다. J2EE 스팩은 두 가지 모델의 트랜잭션 경계설정을 설명하고 있다. (표 1):

표 1. 트랜잭션 경계설정

선언적(Declarative) 트랜잭션 경계설정프로그램에 입각한(Programmatic) 트랜잭션 경계설정
프로그래머는 EJB 전개 디스크립터를 사용하여 트랜잭션 애트리뷰트를 선언한다.프로그래머는 트랜잭션 로직을 코딩해야한다.
런타임 환경(EJB 컨테이너)은 트랜잭션을 자동으로 관리하기 위해 이 애트리뷰트를 사용한다.이 애플리케이션은 API를 통해 트랜잭션을 제어한다.

프로그램에 입각한(Programmatic) 트랜잭션 경계설정을 중점적으로 설명하겠다.

디자인 고려사항
앞서 언급했지만, DAO는 트랜잭션 객체이다. 전형적인 DAO는 구현, 업데이트, 삭제 같은 트랜잭션 작동을 수행한다. DAO를 설계할 때 다음 사항들을 점검한다:

  • 트랜잭션은 어떻게 시작하는가?
  • 트랜잭션은 어떻게 끝나는가?
  • 트랜잭션 시작을 담당하는 객체는 무엇인가?
  • 트랜잭션 종료를 담당하는 객체는 무엇인가?
  • DAO가 트랜잭션의 시작과 종료를 담당해야 하는가?
  • 이 애플리케이션이 다중의 DAO를 통해 데이터에 액세스해야 하는가?
  • 트랜잭션에 포함될 DAO의 수는?
  • 하나의 DAO가 또 DAO에 대한 메소드를 호출할 수 있는가?

이러한 질문들에 대한 답을 알고있다면 자신의 DAO에 가장 잘 맞는 트랜잭션 경계설정 전략을 선택하는데 도움이 된다. DAO에는 두 가지 주요한 트랜잭션 경계설정 전략이 있다. 하나는 DAO가 트랜잭션의 경계를 설정하도록 하는 것이다. 또 다른 방법은 트랜잭션 경계설정을 DAO의 메소드를 호출하는 객체에 맡기는 것이다. 전자를 선택한다면 DAO 클래스 안에 트랜잭션 코드를 임베딩해야 한다. 후자의 방법을 선택한다면 트랜잭션 경계설정 코드는 DAO 클래스의 외부에 있게 될 것이다. 코드 예제를 보고 이 두 가지 방법을 자세히 보도록 하자.

Listing 1- DAO와 두 개의 데이터 작동;구현 및 업데이트:

Listing 1. DAO 메소드
       public void createWarehouseProfile(WHProfile profile);       public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);

Listing 2은 간단한 트랜잭션이다. 트랜잭션 경계설정 코드는 DAO 클래스 외부에 있다. 이 예제에서 콜러(caller)가 다중의 DAO 작동들을 이 트랜잭션 내에서 결합하는 방법을 주목해보자.

Listing 2. 콜러(Caller)에 의해 관리되는 트랜잭션
      tx.begin();    // start the transaction      dao.createWarehouseProfile(profile);      dao.updateWarehouseStatus(id1, status1);      dao.updateWarehouseStatus(id2, status2);      tx.commit();   // end the transaction

이 트랜잭션 경계설정 전략은 단일 트랜잭션에서 다중의 DAO에 액세스 해야하는 애플리케이션에 특별히 어울린다.

JDBC API 또는 Java Transaction API (JTA)를 사용하여 트랜잭션 경계설정을 구현할 수 있다. JDBC 트랜잭션 경계설정은 JTA 보다 단순하다. 하지만 JTA는 보다 유연하다.

JDBC를 이용한 트랜잭션 경계설정
JDBC 트랜잭션은 Connection 객체를 사용하여 제어된다. JDBC Connection 인터페이스(java.sql.Connection)는 두 개의 트랜잭션 모드를 제공한다. (auto-commit과 manual commit). java.sql.Connection은 트랜잭션 제어에 다음의 메소드를 제공한다:

  • public void setAutoCommit(boolean)
  • public boolean getAutoCommit()
  • public void commit()
  • public void rollback()

Listing 3은 JDBC API를 사용하여 트랜잭션의 경계설정을 하는 방법이다:

Listing 3. JDBC API를 이용한 트랜잭션 경계설정
      import java.sql.*;      import javax.sql.*;      // ...      DataSource ds = obtainDataSource();      Connection conn = ds.getConnection();      conn.setAutoCommit(false);      // ...      pstmt = conn.prepareStatement("UPDATE MOVIES ...");      pstmt.setString(1, "The Great Escape");      pstmt.executeUpdate();      // ...      conn.commit();      // ...

JDBC 트랜잭션 경계설정을 이용하면 여러 개의 SQL 문장을 하나의 트랜잭션으로 결합할 수 있다. JDBC 트랜잭션의 단점 중 하나는 트랜잭션 범위가 하나의 데이터베이스 연결로 제한되어 있다는 점이다. JDBC 트랜잭션은 다중 데이터베이스로 확장할 수 없다. 다음에는 JTA를 사용한 트랜잭션 경계설정이다.

JTA 개요
Java Transaction API (JTA)와 Java Transaction Service (JTS)는 J2EE 플랫폼에 분산 트랜잭션 서비스를 제공한다. 분산 트랜잭션에는 트랜잭션 매니저와 한 개 이상의 리소스 매니저가 포함된다. 리소스 매니저는 일종의 영속 데이터스토어이다. 트랜잭션 매니저는 모든 트랜잭션 참여자들 간 통신을 조정하는 역할을 담당한다. 트랜잭션 매니저와 리소스 매니저 관계는 다음과 같다. (그림 2):

그림 2. 트랜잭션 매니저와 리소스 매니저
A transaction manager and resource managers

JTA 트랜잭션은 JDBC 트랜잭션 보다 강력하다. JDBC 트랜잭션이 하나의 데이터베이스 연결로 제한되어 있다면 JTA 트랜잭션은 다중의 참여자들을 가질 수 있다. 다음의 자바 플랫폼 컴포넌트 중 어떤 것이든지 JTA 트랜잭션에 참여할 수 있다:

  • JDBC 커넥션
  • JDO PersistenceManager 객체
  • JMS 큐
  • JMS 토픽
  • Enterprise JavaBeans
  • J2EE Connector Architecture 스팩에 호환하는 리소스 어댑터

JTA를 이용한 트랜잭션 경계설정
JTA로 트랜잭션 경계를 설정하기 위해 애플리케이션은 javax.transaction.UserTransaction 인터페이스에 대한 메소드를 호출한다. Listing 4는 UserTransaction 객체의 전형적인 JNDI이다:

Listing 4. UserTransaction 객체의 JNDI
      import javax.transaction.*;      import javax.naming.*;      // ...      InitialContext ctx = new InitialContext();      Object txObj = ctx.lookup("java:comp/UserTransaction");      UserTransaction utx = (UserTransaction) txObj;

애플리케이션 UserTransaction 객체에 대한 레퍼런스를 가진 후에 트랜잭션을 시작한다. (Listing 5):

Listing 5. 트랜잭션 시작
      utx.begin();      // ...      DataSource ds = obtainXADataSource();      Connection conn = ds.getConnection();      pstmt = conn.prepareStatement("UPDATE MOVIES ...");      pstmt.setString(1, "Spinal Tap");      pstmt.executeUpdate();      // ...      utx.commit();      // ...

애플리케이션이 commit()을 호출하면, 트랜잭션 매니저는 2 단계 커밋(two-phase commit) 프로토콜을 사용하여 트랜잭션을 종료한다.

트랜잭션 제어용 JTA 메소드
javax.transaction.UserTransaction 인터페이스는 다음의 트랜잭션 제어 메소드를 제공한다:

  • public void begin()
  • public void commit()
  • public void rollback()
  • public int getStatus()
  • public void setRollbackOnly()
  • public void setTransactionTimeout(int)

JTA 트랜잭션을 시작할 때 이 애플리케이션은 begin()을 호출한다. 트랜잭션을 끝내려면 commit() 또는 rollback()을 호출한다. (참고자료).

JTA와 JDBC 사용하기
개발자들은 DAO 클래스에서 저수준 데이터 작동에 JDBC를 사용하곤 한다. JTA를 이용하여 트랜잭션의 경계를 설정하려면 javax.sql.XADataSource, javax.sql.XAConnection, javax.sql.XAResource 인터페이스를 구현하는 JDBC 드라이버가 필요하다. 이러한 인터페이스를 구현하는 드라이버는 JTA 트랜잭션에 참여할 수 있게된다. XADataSource 객체는 XAConnection 객체용 팩토리이다. XAConnection는 JTA 트랜잭션에 참여하는 JDBC 커넥션이다.

애플리케이션 서버의 관리 툴을 사용하여 XADataSource를 설정해야 한다. 애플리케이션 서버 문서와 JDBC 드라이버 문서를 참조하라.

J2EE 애플리케이션은 JNDI를 사용하여 데이터 소스를 검색한다. 일단 애플리케이션이 데이터 소스 객체에 대한 레퍼런스를 갖게 되면 이것은 javax.sql.DataSource.getConnection()을 호출하여 데이터베이스로의 커넥션을 획득하게 된다.

XA 커넥션은 비 XA 커넥션과는 다르다. XA 커넥션은 JTA 트랜잭션에 참여하고 있다는 것을 언제나 기억하라. XA 커넥션은 JDBC의 자동 커밋 기능을 지원하지 않는다. 또한 이 애플리케이션은 XA 커넥션 상에서 java.sql.Connection.commit() 또는 java.sql.Connection.rollback()을 호출하지 않는다. 대신 UserTransaction.begin(), UserTransaction.commit(), UserTransaction.rollback()을 사용한다.

최상의 접근방법 선택하기
JDBC와 JTA를 이용한 트랜잭션 경계설정에 대해 이야기했다. 각 접근방식 대로 장점이 있기 때문에 자신의 애플리케이션에 가장 알맞는 것을 선택해야 한다.

최근 많은 프로젝트에서 우리팀은 JDBC API를 사용하여 DAO 클래스를 구현했다. 이 DAO 클래스는 다음과 같이 요약된다:

  • 트랜잭션 경계설정 코드는 DAO 클래스 안으로 임베딩된다.
  • DAO 클래스는 트랜잭션 경계설정에 JDBC API를 사용한다.
  • 콜러가 트랜잭션 경계를 설정할 방법은 없다.
  • 트랜잭션 범위는 하나의 JDBC Connection으로 제한된다.

JDBC 트랜잭션이 복잡한 엔터프라이즈 애플리케이션에 언제나 적합한 것은 아니다. 트랜잭션이 다중 DAO 또는 다중 데이터페이스로 확장한다면 다음과 같은 구현 전략이 보다 적합하다:

  • 트랜잭션은 JTA로 경계 설정된다.
  • 트랜잭션 경계설정 코드는 DAO와 분리되어 있다.
  • 콜러가 트랜잭션 경계설정을 담당하고 있다.
  • DAO는 글로벌 트랜잭션에 참여한다.

JDBC 접근방법은 간단함이 매력이다. JTA 접근방법은 유연성이 무기이다. 애플리케이션에 따라 구현 방법을 선택해야 한다.

로깅과 DAO
잘 구현된 DAO 클래스는 런타임 작동에 대한 세부사항을 파악하기 위해 로깅(logging)을 사용한다. 예외, 설정 정보, 커넥션 상태, JDBC 드라이버 메타데이터, 쿼리 매개변수 중 어떤 것이든 선택하여 기록해야 한다. 기록은 모든 개발 단계에 유용하다.

로깅 라이브러리 선택하기
많은 개발자들은 단순한 형식의 로깅을 사용한다: System.out.printlnSystem.err.println. Println 문장은 빠르고 편리하지만 완벽한 로깅 시스템은 제공하지 않는다. 표 2는 자바 플랫폼을 위한 로깅 라이브러리이다:

표 2.자바 플랫폼을 위한 로깅 라이브러리

로깅 라이브러리오픈소스여부URL
java.util.loggingNohttp://java.sun.com/j2se/
Jakarta Log4jYeshttp://jakarta.apache.org/log4j/
Jakarta Commons LoggingYeshttp://jakarta.apache.org/commons/logging.html

java.util.logging은 J2SE 1.4 플랫폼을 위한 표준 API이다. Jakarta Log4j가 더 많은 기능성과 유연성을 제공한다는 것에는 많은 개발자들이 동의하고 있다. java.util.logging의 이점 중 하나는 J2SE 1.3과 J2SE 1.4 플랫폼을 지원한다는 것이다.

Jakarta Commons Logging은 java.util.logging 과의 연결 또는 Jakarta Log4j에 사용될 수 있다. Commons Logging은 로깅 추상 레이어로서 애플리케이션을 기저의 로깅 구현에서 고립시킬 수 있다. Commons Logging을 사용하여 설정 파일을 변경하여 기저의 로깅 구현을 바꿀 수 있다. Commons Logging은 Jakarta Struts 1.1과 Jakarta HttpClient 2.0에 사용된다.

로깅 예제
Listing 7은 DAO 클래스에서 Jakarta Commons Logging을 사용하는 방법이다:

Listing 7. Jakarta Commons Logging
import org.apache.commons.logging.*;class DocumentDAOImpl implements DocumentDAO{      static private final Log log = LogFactory.getLog(DocumentDAOImpl.class);      public void deleteDocument(String id)      {          // ...          log.debug("deleting document: " + id);          // ...          try          {              // ... data operations ...          }          catch (SomeException ex)          {              log.error("Unable to delete document", ex);              // ... handle the exception ...  }      }}

로깅은 미션 수행에 중요한 애플리케이션의 중요한 일부이다. DAO에서 오류가 생기면 무엇이 잘못되었는지를 이해할 수 있게끔 최고의 정보를 로그가 제공한다. 로깅과 DAO를 결합하면 디버깅과 문제해결은 확실하다.

DAO에서의 예외 핸들링

DAO 패턴을 구현할 때 다음 사항을 자문해보라:

  • DAO의 퍼블릭 인터페이스의 메소드들이 검사된 예외를 던지는가?
  • 그렇다면 어떤 예외들인가?
  • DAO 구현 클래스에서 예외는 어떻게 처리되는가?

DAO 패턴을 작업하는 과정에서 우리 팀은 예외 핸들링에 대한 가이드라인을 개발했다:

  • DAO 메소드는 의미있는 예외를 던져야한다.

  • DAO 메소드는 java.lang.Exception을 던져서는 안된다. java.lang.Exception은 너무 일반적이다. 근본 문제에 대한 정보를 제공하지 않을 것이다.

  • DAO 메소드는 java.sql.SQLException 메소드를 던져서는 안된다. SQLException은 저급 JDBC 예외이다. DAO는 JDBC를 나머지 애플리케이션에 노출하는 것이 아니라 JDBC를 캡슐화 해야한다.

  • DAO 인터페이스의 메소드들은 콜러가 예외를 처리할 수 있다고 합리적으로 판단될 때 에만 검사된예외를 던져야 한다. 콜러가 예외를 핸들할 수 없다면 검사되지 않은(런타임) 예외를 던질 것을 고려하라.

  • 데이터 액세스 코드가 예외를 잡으면 이를 무시하지 말아라. 잡힌 예외를 무시하는 DAO는 문제해결에 애를 먹는다.

  • 연쇄 예외를 사용하여 저급 예외를 고급 예외로 트랜슬레이팅 한다.

  • 표준 DAO 예외 클래스를 정의하라. Spring Framework (참고자료)은 사전 정의된 DAO 예외 클래스를 제공한다.

구현 예제: MovieDAO
MovieDAO는 이 글에 논의된 모든 기술을 보여주는 DAO이다: 트랜잭션 경계설정, 로깅, 예외 핸들링. 코드는 세 개의 패키지로 나뉜다. (참고자료):

  • daoexamples.exception
  • daoexamples.movie
  • daoexamples.moviedemo

DAO 패턴 구현은 클래스와 인터페이스로 구성된다:

  • daoexamples.movie.MovieDAOFactory
  • daoexamples.movie.MovieDAO
  • daoexamples.movie.MovieDAOImpl
  • daoexamples.movie.MovieDAOImplJTA
  • daoexamples.movie.Movie
  • daoexamples.movie.MovieImpl
  • daoexamples.movie.MovieNotFoundException
  • daoexamples.movie.MovieUtil

MovieDAO 인터페이스는 DAO의 데이터 작동을 정의한다. 이 인터페이스는 다섯 개의 메소드를 갖고 있다:

  • public Movie findMovieById(String id)
  • public java.util.Collection findMoviesByYear(String year)
  • public void deleteMovie(String id)
  • public Movie createMovie(String rating, String year, String, title)
  • public void updateMovie(String id, String rating, String year, String title)

daoexamples.movie 패키지에는 두 개의 MovieDAO 인터페이스 구현이 포함되어 있다. 각 구현은 트랜잭션 경계설정에 다른 접근방식을 사용한다. (표 3):

표 3. MovieDAO 구현

MovieDAOImplMovieDAOImplJTA
MovieDAO 인퍼페이스 구현YesYes
JNDI를 통한 DataSourc 획득YesYes
DataSource에서 java.sql.Connection 객체 획득YesYes
DAO가 트랜잭션을 내부적으로 경계설정하는가?YesNo
JDBC 트랜잭션 사용YesNo
XA DataSource 사용NoYes
JTA 트랜잭션 참여NoYes

MovieDAO 데모 애플리케이션
이 데모 애플리케이션 이름은 daoexamples.moviedemo.DemoServlet 이다. DemoServlet은 Movie DAO를 사용하여 무비 데이터를 쿼리 및 업데이트 한다.

Listing 8은 싱글 트랜잭션 에서의 JTA-aware MovieDAO와 Java Message Service의 결합 방법이다.

Listing 8. MovieDAO와 JMS 코드 결합
  UserTransaction utx = MovieUtil.getUserTransaction();  utx.begin();  batman = dao.createMovie("R",      "2008",      "Batman Reloaded");  publisher = new MessagePublisher();  publisher.publishTextMessage("I'll be back");  dao.updateMovie(topgun.getId(),      "PG-13",      topgun.getReleaseYear(),      topgun.getTitle());  dao.deleteMovie(legallyblonde.getId());  utx.commit();

데모를 실행하려면 XA 데이터소스와 비 XA 데이터소스를 애플리케이션 서버에 설정해야 한다. 그런다음 daoexamples.ear 파일을 전개한다.

참고자료

Posted by tornado
|
막심 므라비차 - 왕벌의 비행



막심 므라비차 (Maksim Mrvica)- "피아노계의 바네사메이"

크로아티아의 피아니스트 막심 므라비차(1975년생).
유고내전 당시 포탄이 우박처럼 떨어지고, 총알이 빗발치던 거리에서
피아노를 배우며 삶의 치열함을 경험한 그는 유고내전이 끝난 1993년
18세의 나이에 이미 크로아티아 최고의 피아니스트가 됐다.

속도감있는 일렉트릭 사운드 위에 탄탄한 피아노 연주가 더해진, 미래의 피아노 음악,
바네사-메이와 본드를 넘어 일렉트릭-클래식의 새 지평을 연 사상초유의
크로스오버 피아니스트 막심의 충격적인 메이저 진출 앨범 「The Piano Player」!!!!!
바네사 메이의 뒤를 이을 음악인을 찾던 EMI의 눈에 띈 야심작~

 

Posted by tornado
|
















































































































































가면 축제 사진이예요..

실제로 보면 더 멋있을듯 해요..

가면들이 하나같이 다 멋있네요..

저런 축제속에 섞여서 놀아보고 싶은 맘이 굴뚝 같아요...

 

 

 

Posted by tornado
|
O형 천천히 식사할 것… 1주일에 최소 3회 이상 운동하라

A형 소량으로 자주 식사를… 하루 8시간 이상 충분히 자라

B형 요가·스트레칭·명상으로 마음 다스리며 창의적 활동을

AB형 약물남용·흡연 금물… 야외활동 충분히 하는
것이 좋아

 

엄밀히 검증된 것은 아니지만 한방에서는 건강은 체질에 의해 크게 좌우된다고 믿는 경향이 있다. 사람은 대개 태음인, 태양인, 소음인, 소양인 등 사상체질로 분류하며

이들 체질은 나름대로의 건강상 특징을 가진다는 것이다.

 

반면 한의학이 없었던 서양에서는 오래 전부터 체질 대신 혈액형으로 건강을 분류하려는 시도가 이어지고 있다. A, B, AB, O형의 혈액형이 건강생활의 변수가 된다고 보는 것이다.

대표적인 사람은 미국 코네티컷주 스탬포드에서 활동 중인 자연요법 의사 피터 다다모. 그는 1996년 베스트셀러 ‘혈액형에 따른 올바른 식사법(Eat right 4 Your Type)’을 발간, 언론으로부터 크게 주목받았다. 혈액형은 수혈시 필요한 자료라는 생각에서 벗어나 면역계와 소화기계 등에 영향을 미친다고 설파했다.

지금까지의 연구 결과에 따르면 사람의 적혈구에는 500개 이상의 항원이 존재하며 이들 항원에 의해 ABO혈액형이 결정된다. 혈액형의 빈도는 인종에 따라 차이를 보이는데 한국인의 경우 A형 34%, O형 28%, B형 27%, AB형 11% 정도이다. 혈액형을 결정하는 항원들은 적혈구 이외의 많은 조직에서도 발견된다. 백혈구 중 하나인 림프구나 혈소판에서도 비록 수는 적으나 AB항원이 있으며, 혈장이나 침 속에도 혈액형 물질이 존재한다고 한다.

의학적 연구에 따르면 A형인 사람들은 O형보다 위암이나 난소암, 침샘의 종양에 걸릴 확률이 높다. 반면 위궤양 빈도는 O형에서 다른 형보다 20% 정도 높은 것으로 집계되어 있다. 국내에서도 A형에서 위암의 빈도가 높은 것으로 나타나고 있으며 십이지장궤양에 관해서는 보고자에 따라 B형 혹은 O형이 발생빈도가 높다는 주장이 있다. A형은 다른 혈액형에 비해 혈전증이나 색전증의 위험성이 높은 것으로 알려져 있다.

이에 대해 의사들은 “혈액형을 결정짓는 항원들은 생리학적으로 인체에 많은 영향을 미친다”고 말한다. 항원은 위험한 박테리아 같은 이물질이 침투하는 것에 대응하며 태아의 성장에도 중요한 역할을 하고 질환에 대한 저항력과 회복력에도 많은 영향을 미친다는 것이다. 또 질병 때문에 혈액형이 변하는 경우도 있다.

 

서양의 연구결과·관련 서적들을 바탕으로 혈액형이 건강에 미치는 영향을 살펴본다. 이는 대체적인 경향성과 개연성을 제시한 것으로 이해하는 것이 타당하리라고 본다.

O형 이해득실 잘 따지는 현실주의자

걸리기 쉬운 질환으로는 심장병, 파킨슨씨병, 약물남용, 조울증, 정신분열증, 각종 궤양, 뇌졸중, 관절염, 장 염증질환, 호흡기 알레르기, 피부암, 결핵, 콜레라 등이 거론된다.

이들에게는 건강 유지를 위해 식탁에 앉아서


천천히 식사할 것, 체중감량시에는 고단백 식이요법을 시행할 것, 화 나거나 술 마시고 담배 피우고 싶거나 단 것을 먹고 싶으면 운동을 할 것, 1주일에 최소 3회 이상 에어로빅 웨이트트레이닝 달리기 킥복싱 자전거타기 등의 운동을 할 것, 충동적으로 일하지 않도록 주의하며 지루함에 빠지지 않도록 계획을 잘 세워 시간을 보낼 것, 화를 건설적으로 처리하는 방법을 익혀둘 것, 일이 잘 안풀릴 때는 산책이나 가벼운 스트레칭을 할 것 등이 권장된다.

붉은 살코기를 소량으로 자주 섭취하며 생선, 과일과 야채를 많이 먹는 것이 좋다. 반면 오렌지주스나 딸기 등 산성 과일, 밀이나 밀 제품, 유제품, 차, 커피, 토마토, 팝콘, 땅콩, 베이컨, 멜론, 키위 등은 멀리 하는 것이 바람직하다.

특히 젊은층은 달리기, 수영, 등산, 자전거타기 등으로 매일 최소 한 시간 이상 에너지를 소모할 필요가 있다.

어린이는 다른 어린이들보다 더 많은 관심과 칭찬을 필요로 하며 이들에게는 화를 잘 처리하도록 교육시킬 필요가 있다. 다른 어린이들과 어울리기를 좋아하고 리더 역할을 하는 경향이 있으나 주의력결핍, 과잉행동장애를 보이기 쉽다.

장년층은 관절염 예방을 위해 충분한 단백질을 섭취해야 하며 소화기궤양에 취약하므로 이부프로펜 등 비스테로이드성 소염제를 피하는 것이 좋다.

 

A형 감정이 깊고 신중한 타입

신장질환, 위나 장 계통의 암, 인슐린 비의존성 당뇨병, 강박신경증, 스트레스, 골다공증 등을 주의해야 한다.

바람직한 생활습관으로는 밤 11시 이전에 잠자리에 들고 하루 8시간 이상 수면을 취할 것, 아침 식사에서 단백질을 충분히 섭취하되 고단백질 음식은 피할 것, 식사는 소량으로 자주(하루 6끼까지) 할 것, 음식을 충분히 씹을 것, 1주일에 3번 정도 요가나 태극권 등 명상 운동을 할 것, 매일 두 차례 20분 이상 명상 시간을 가질 것, 화를 참지 말고 말로써 표출할 것, 능력 이상의 일을 떠맡지 말 것 등이 권장된다.

식품으로는 고등어, 연어, 정어리, 대구, 잉어, 계란, 콩류, 견과류, 곡류, 우유, 요구르트, 치즈, 채소와 과일 등의 섭취가 권장된다. 반면 육류, 토마토, 감자, 양배추, 고구마 등은 피하는 것이 좋다.

젊은층은 폭력 장면을 보면 스트레스를 받기 때문에 TV와 비디오 시청을 삼가며 대신 음악감상이나 독서, 예술활동을 하는 것이 좋다. 심호흡을 하고 스트레칭 운동을 자주 하도록 하며 취침시간을 지키는 규칙적 수면이 필요하다. 쉽게 의기소침해지는 경향이 있으므로 자존심을 갖도록 격려할 필요가 있다.

어린이의 경우 자폐아의 상당수가 A형이라는 보고가 있다. A형 어린이는 귀 감염질환 발병 확률이 50% 정도 더 높으며 최소한 4개월 이상 모유 수유를 하는 것이 권장된다.

장년층은 낮은 위산 분비를 더 낮추는 것을 방지하기 위해 탄산음료를 피할 것, 불면증의 예방ㆍ치료를 위해 비타민 B12를 복용할 것, 골다공증ㆍ치매 예방을 위해 정신적 긴장을 이완하고 스트레칭 운동을

할 것 등이 필요하다.
B형 얽매이기 싫어하는 자기방식형

조심해야 할 질병으로는 우울증, 인슐린 비의존성 당뇨병, 바이러스 감염, 만성피로증후군, 다발성경화증, 비만, 갑상선 기능저하증 등이 있다. 하지만 바람직한 생활을 하는 건강한 B형은 다른 혈액형에 비해 질환에 걸릴 위험이 적다.

생활습관으로는 집중력을 필요로 하는 창의적인 활동에 하루 20분 이상 투자할 것, 골프나 테니스 격투기 자전거타기 걷기 등 자신이 좋아하는 운동을 할 것, 질병을 예방하기 위해 정신적 긴장 이완에 힘쓸 것 등이 권장된다.

 

 

식품으로는 적색 살코기류, 생선과 해산물, 계란, 우유 요구르트 치즈 등 유제품, 콩류, 채소 과일류 등이 권장된다. 반면 닭고기, 옥수수류, 밀류, 보리, 땅콩류, 깨소금, 토마토, 콩류, 해바라기씨 등은 삼가는 것이 좋다.

젊은층의 경우 자신의 일을 스스로 알아서 처리하도록 배려하며 수면·식사 시간도 자신이 알아서 하도록 융통성을 주고 단음식을 피하게 할 필요가 있다.

어린이는 주의력결핍장애의 위험이 있으므로 조용한 활동을 하게 하고 경쟁적인 운동은 피하게 하는 것이 좋다. 특히 닭고기, 옥수수, 땅콩 등을 피하게 하고 밀류는 다른 곡류로 대체하는 것이 좋다.

장년층은 크로스워드 퍼즐이나 기타 정신활동으로 마음을 다스리는 것이 좋고 매일 요가, 스트레칭, 명상을 하는 것이 권장된다.

 

AB형 합리적이나 우유부단한 타입

암, 우울증, 정신분열증, 담석증, 황달, 심장질환, 신장질환 등을 조심해야 한다.

권장되는 생활방식으로는 경쟁적인 것보다는 상호 협조적인 직장생활, 자신만의 시간을 가질 것, 스스로 통제할 수 없는 일에 대한 강박감에서 벗어날 것, 매일 명상이나 요가를 하고 1주일에 두 번 이상 에어로빅이나 웨이트트레이닝을 할 것, 차 커피 알코올류를 피할 것, 조급함과 과도한 스트레스를 피하고 금연할 것 등이 거론된다.

몸에 좋은 식품으로는 생선류, 해조류, 송아지 간이나 양고기, 된장류, 생우유가 아닌 유가공품, 채소와 과일, 계란, 땅콩과 땅콩버터 등이 있으며 닭고기, 옥수수, 베이컨, 햄, 돼지고기, 소고기, 송어, 랍스터, 새우, 해바라기씨, 바나나 등은 피하는 것이 좋다.

젊은층의 경우 질병에 강해지기 위해 맑은 공기와 태양이 필요하므로 야외활동을 충분히 하도록 하며 춤 같은 활동을 즐기는 것이 좋다. 중독의 성향이 있으므로 특히 약물남용이나 흡연을 하지 않도록 주의해야 한다.

 

삼성서울병원 진단검사의학과 박규은 교수는 “혈액형과 질병의 상관관계에 대해 여러나라에서 많은 통계학적 연구들이 있었다. 부분적으로 상관성이 보고되고 있지만 명백히 입증된 것은 아닌 만큼 참고자료 정도로 활용하는 것이 좋겠다’고 말했다.

 

Posted by tornado
|
crit.clear();
crit.add(MiddlePeer.BIG_ID,MformData.getBigId());
System.out.println(MiddlePeer.createQueryString(crit));<--SQL문 출력하기
MiddlePeer.doDelete(crit);
Posted by tornado
|
 

■Torque사용을 위한 기초작업들

http://jakarta.apache.org/builds/jakarta-turbine/torque/release/3.1/에서

torque-3.1.zip  <==실행

Torque.properties의 편집

클래스 패스는 lib안의 필요한 것(어떤 것? )에 통한다

클라이언트의 코딩과 실행

torque-gen-3.1.zip  <==생성

build.properties의 편집

JDBC 드라이버는 lib에 카피

Ant 태스크를 실행


torque-3.1.zip에 있는 lib폴더 내부의 jar파일들을 사용하는 웹폴더(즉,C:\Tomcat 5.0\webapps-test)의 C:\Tomcat 5.0\webapps-test\ROOT\WEB-INF\lib 에 저장한다. lib폴더엔 스트럿의 jar파일과 중복되는 부분이 있어 torque의 lib폴더의 jar파일의 일부만 가져와서 덮어쓴다. 그 내용은 다음과같다

avalon-framework-4.1.4.jar

commons-configuration-1.0-dev-3.20030607.194155.jar

commons-dbcp-20030825.184428.jar

commons-pool-20030825.183949.jar

jcs-20030822.182132.jar

jdbc-2.0.jar

jndi-1.2.1.jar

junit-3.8.1.jar

log4j-1.2.8.jar

logkit-1.0.1.jar

stratum-1.0-b3.jar

torque-3.1.jar

village-2.0-dev-20030825.jar

xercesImpl-2.0.2.jar

xmlParserAPIs-2.0.2.jar


C:\javatool\torque-3.1에 있는 설정파일들을 사용하는 웹폴더(C:\Tomcat 5.0\webapps-test\ROOT\WEB-INF)의 WEB-INF에 복사한다.


web.xml 에 Torque를 설정해준다.

<servlet>

      <servlet-name>initTorque</servlet-name>

<!--torque를 사용할 때 매번 초기화 하는부분을 이 설정파일(web.xml)에서 설정해준다.-->

      <servlet-class>initialize.TorqueInit</servlet-class>

      <init-param>

      <param-name>config</param-name>

<!--torque를 사용할 때 참조할 properties파일 위치 설정-->

      <param-value>/WEB-INF/classes/properties/Torque.properties</param-value>

      </init-param>

      <init-param>

         <param-name>debug</param-name>

         <param-value>0</param-value>

      </init-param>

      <load-on-startup>1</load-on-startup>

    </servlet>


 ========================================================

<!--torque를 사용할 때 매번 초기화 하는부분을 이 설정파일(web.xml)에서 설정해준다.-->

      <servlet-class>initialize.TorqueInit</servlet-class>  파일이름 : TorqueInit.java(임의로 작성)

package initialize;


/**

* @author  : Kwoen BongJe(2002-12-21)

*/


import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;


import org.apache.torque.Torque;


public class TorqueInit extends HttpServlet{

    private static boolean hasInitialized = false;


        public void init() throws ServletException{

                

                synchronized(TorqueInit.class){

                        

                        if(!hasInitialized){

                                try{

                                        String propFileName = getServletConfig().getInitParameter("config");

                                if(propFileName==null) {

                                   propFileName="/WEB-INF/classes/properties/Torque.properties";

                                }

                                String propFile = getServletContext().getRealPath(propFileName);

                                        Torque.init(propFile);

                                        hasInitialized = true;

                                }catch(Exception e){

                                        throw new ServletException("\nComstar.framework : Can't initialize Torque!\n"+e);

                                }

                        }

                }

                

        }

}

 

===================================================

<!--torque를 사용할 때 참조할 properties파일 위치 설정-->

      <param-value>/WEB-INF/classes/properties/Torque.properties</param-value> 프로퍼티 파일을 사용환경에 맞게 변경해주어야 한다. 아래는 예제파일


 

torque.applicationRoot = /usr/local/tomcat

log4j.category.org.apache.torque = ALL, org.apache.torque

log4j.appender.org.apache.torque = org.apache.log4j.FileAppender

log4j.appender.org.apache.torque.file = ${torque.applicationRoot}/logs/testdb/torque.log

log4j.appender.org.apache.torque.layout = org.apache.log4j.PatternLayout

log4j.appender.org.apache.torque.layout.conversionPattern = %d [%t] %-5p %c - %m%n

log4j.appender.org.apache.torque.append = false


torque.defaults.pool.logInterval = 0

torque.defaults.pool.connectionWaitTimeout = 10

torque.defaults.pool.defaultMaxConnections = 80

torque.defaults.pool.maxExpiryTime = 3600


torque.defaults.connection.driver = org.gjt.mm.mysql.Driver

torque.defaults.connection.url = jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=euc-kr

torque.defaults.connection.user = test

torque.defaults.connection.password = test


torque.database.default=testdb

torque.database.testdb.adapter=mysql


torque.dsfactory.testdb.factory=org.apache.torque.dsfactory.SharedPoolDataSourceFactory

torque.dsfactory.testdb.pool.defaultMaxActive=10

torque.dsfactory.testdb.pool.testOnBorrow=true

torque.dsfactory.testdb.pool.validationQuery=SELECT 1

torque.dsfactory.testdb.connection.driver = org.gjt.mm.mysql.Driver

torque.dsfactory.testdb.connection.url = jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=euc-kr

torque.dsfactory.testdb.connection.user = test

torque.dsfactory.testdb.connection.password = test


torque.idbroker.cleverquantity=true

torque.manager.useCache = true


======

 

설치한 torque-gen폴더 아래에 C:\javatool\torque-gen-3.1 torque가 사용될 폴더를 만들어준다 폴더이름은 임으로 만들지만 프로젝트 명으로 하는 것이 좋겠다.


C:\javatool\torque-gen-3.1\webapps-address 아래에 schema 폴더를 만든다.


schema 폴더에 사용할 DB에서의 사용할 태이블 구조를 xml형식으로 만든다

이때 파일 이름의 형식은 “*-schema.xml”의 형식을 따라야 한다. 이는 C:\javatool\torque-gen-3.1에 있는build-torque.xml파일을 torque가 참조한다. build-torque.xml파일에는 DB를 생성과 참조 등등이 정의 되어있다.

참고자료 : http://db.apache.org/torque-31/generator/schema-reference.html

<예>

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

<!DOCTYPE database SYSTEM "http://db.apache.org/torque/dtd/database_3_1.dtd">


<!-- ==================================================================== -->

<!--                                                                      -->

<!-- I D  B R O K E R  S C H E M A                                        -->

<!--                                                                      -->

<!-- ==================================================================== -->

<!-- This is the XML schema use by Torque to generate the SQL for         -->

<!-- ID_TABLE table used by the id broker mechanism in Torque.            -->

<!-- ==================================================================== -->

<!-- @author: <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>       -->

<!-- @version $Id: id-table-schema.xml,v 1.2 2003/07/24 12:40:41 mpoeschl Exp $ -->

<!-- ==================================================================== -->


<database name="struts-address">

Posted by tornado
|

Posted by tornado
|