달력

42024  이전 다음

  • 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

출처 : 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/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
|
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
|