Binary Coder's Weblog: Dev. Note Archives

Dev. Note|Early Adopter|Life Style|Monolog|News|Software|Blog Home Search this site:

Binary Coder's Weblog

It's just sometimes, it's always me. How dark can these hallways be?

May 31, 2005

세련된 자바 웹 프로그래머 되기 | 기본기 갈고닦기

View: ZDNet Korea | [세련된 자바 웹 프로그래머 되기] 기본기 갈고닦기

프로그래밍 초보자가 능히 한 사람 몫을 할 정도, 혹은 혼자 코딩하도록 내버려둬도 다른 사람들이 불안에 떨지 않을 만큼 성장하는 가장 빠른 방법은 무엇일까요? 디자인 패턴을 공부하고 최신 기술을 익히고 실전 프로그래밍을 많이 해보는 것?

물론 중요합니다. 그러나 이보다 훨씬 더 중요한 것은 기초를 다지는 것입니다. 슬램덩크에서 강백호는 농구부 입단 후 2주일 간 드리블 연 습만 했고 이것이 그가 빠른 시간 안에 한 사람 몫을 해내는 데 밑거름이 됐지요. 복잡한 이론, 어려운 신기술은 잠시 접어두고 프로그래머 로서의 기본을 재점검해보겠습니다.

4년 전 학교에서 어느 벤처 경영인의 강연을 들은 적이 있습니다. 미국에서 벤처를 시작해 어느 정도 성공을 거둔 기업가였는데, 그는 강 연 내내 기본을 강조했습니다. 미국과 한국의 기업 문화의 차이를 비교하면서 미국의 벤처들은 대체로 경영인으로서의 기본적으로 지켜야 할 것들을 잘 지키는 반면 한국의 벤처는 기본적인 것들을 제대로 지키지 못하고 그로 인해 실패하는 경우가 많다고 하더군요.

그는 모든 것을 기본이란 말 하나로 설명했습니다. 기본이 물론 성공의 충분조건은 아닙니다. 그러나 기본을 지키지 않고는 성공할 수 없습 니다. 어떤 분야든 이것은 예외가 없을 것입니다.

그렇다면 프로그래머, 그 중에서도 자바 웹 프로그래머의 기본은 무엇일까요? 당연히 자바 언어에 대해 잘 아는 것입니다. 웹 프로그래밍이 라는 것도 결국 사용하는 API가 다른 것 뿐, 좋은 자바 웹 프로그래머가 되려면 먼저 좋은 자바 프로그래머가 되어야 합니다.

너무도 당연한 말 같지만 현실은 그렇지 않습니다. 여러 자바 커뮤니티에 가보면 자바에 대한 기본적인 질문이 수없이 올라오며, 현업 프로 그래머 중에도 기초가 부족한 사람이 너무나도 많습니다. 자바 프로그래머라면 자바에 관한 기본서 하나 정도는 마스터하고 시작하도록 합 시다.

자바 기본서들은 대체로 내용이 충실하므로 아무거나 사도 나쁜 선택은 아닐 것입니다. 그래도 추천이 필요하다면 『Thinking in Java』 를 추천합니다. 프로그래밍에 처음 입문한다면 예제들을 직접 따라해 보는 것도 좋을 것입니다.

자바에 익숙해졌다면 다음 단계는 웹 기술입니다. 웹 프로그래밍의 기본은 웹과 관련된 스펙(specification)에 대한 지식, 구체적으로 서블 릿/JSP 스펙, HTTP 스펙(RFC 2068), HTML W3C 스펙 등입니다. 이 스펙들에 대해 상세히 다 알 필요는 없지만 웹 프로그래밍에서 사용 하는 API들이 어떤 스펙에 기반을 두고 있는지, 자세히 알고 싶으면 무엇을 찾아야 하는지는 알아야 합니다.

공대생이 공학 수학의 내용을 전부 알고 있을 필요는 없지만 미분방정식을 풀고 싶으면 어느 페이지를 찾아봐야 하는지는 알고 있어야 하 는 것처럼 어떤 요구사항이 발생했을 때 그 요구사항을 구현하려면 어떤 스펙을 찾아봐야 하는지 정도는 알고 있어야 하는 것이죠. 그리고 의외로 많은 웹 프로그래머들이 HTML, CSS에 익숙지 않은데, 이 때문에 웹사이트의 브라우저 호환성이 떨어질 뿐만 아니라 지저분한 코 드를 양산하게 됩니다.

HTML 코드 역시 유지보수 대상이 되는 코드이며 자바 코드 못지않게 깔끔하게 유지할 수 있어야 함을 기억해야 합니다. 이를 위해서는 HTML과 CSS에 대해 상세히 알아둘 필요가 있습니다. XML은 이제 프로그래머의 기본이니 언급할 필요도 없을 것입니다. XML 파일을 이 용하는 것이 편하게 느껴질 정도가 되면 코드의 유연성을 높일 좋은 방법을 많이 생각해낼 수 있을 것입니다.

스펙을 실제로 활용하는 것은 API를 통해서입니다. 서블릿/JSP API는 스펙과는 달리 실제로 API를 통해 무엇을 할 수 있는지를 상세하게 알고 있어야 합니다. 이것은 비단 서블릿/JSP API뿐 아니라 자바 기본 API, 각종 라이브러리의 API도 마찬가지입니다. 필자가 이제껏 자 바에 관해 받아본 질문 중 대부분은 API 문서만 잘 들여다 보면 해결되는 것이었습니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (1 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

API 문서를 자주 찾아보는 습관을 들입시다. 리눅서들은 매뉴얼을 읽지 않고 질문하는 사람에게 RTFM(Read The Fucking Manual)이라 는 대답을 해줍니다. 자바 역시 RTFM이 필요합니다. J2EE 기본서를 하나 사서 보는 것도 좋을 것입니다. J2EE 기본서에는 웹 관련 스펙 중 중요한 부분들, 서블릿/JSP 스펙 및 API들이 잘 정리되어 있습니다. 『Java Server Programming, J2EE Edition』 정도면 훌륭한 참 고서가 될 것입니다.

이제부터 이런 기본적인 지식 중에 중요하지만 간과하기 쉬운 것들, 간단하지만 알면 도움이 되는 정보, 자주 부딪히게 되는 고민 등 몇 가 지 작은 문제들을 짚어볼 것입니다. 모두 기본 학습 과정을 잘 거쳤다면 자연스럽게 알 수 있는 내용입니다. 이런 하나하나의 지식을 통해 자신에게 부족한 점을 되짚어볼 수 있는 계기를 마련할 수 있기를 바랍니다.

web.xml 배치 서술자(deployment descriptor)라고 부르는 web.xml은 웹 프로젝트를 구성하는 데 있어 필수적이면서 웹 애플리케이션의 동작을 여러 가지로 조정하는 기능을 합니다. 스트러츠를 사용하는 경우도 스트러츠를 사용하기 위한 설정은 web.xml에 하게 되는데 그 설정들 이 무슨 의미를 가지고 있는지 정도는 상식으로 알아두는 것이 좋을 것입니다. <리스트 1>의 실제 스트러츠 설정 예제를 봅시다.

<리스트 1> web.xml 서블릿 맵핑

action org.apache.struts.action.ActionServlet config /WEB-INF/struts-config.xml 1

action *.do

PHP, ASP 등의 다른 서버 사이드 스크립트나 JSP 페이지는 페이지를 호출하는 경로에 실제 스크립트 파일이 존재해야 하 지만 서블릿은 이와 달리 web.xml의 설정을 이용해 URL을 특정 서블릿으로 맵핑할 수 있습니다. <리스트 1>의 설정은 호출된 URL을 스트러츠의 Action으로 맵핑하기 위한 설정입니다.

servlet 설정에서 action이라는 이름의 서블릿을 org.apache.struts.action.ActionServlet 클래스로 등록하고 다음 servlet-mapping 설정에서 *.do라는 URL로 호출된 페이지들을 action이라는 이름의 서블릿으로 맵핑합니다. url- pattern 값을 *.nhn으로 바꾼다면 *.nhn으로 호출된 요청들이 ActionServlet으로 맵핑될 것입니다. 스트러츠는 이 ActionServlet에서 요청을 각 Action으로 분기시켜 줍니다. init-param은 서블릿을 초기화할 때 사용할 파라미터 값이며

http://crowe.wowdns.com:8000/archives/dev_note/ (2 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

getInitParameter 메쏘드를 통해 읽어올 수 있습니다. load-on-startup은 서블릿 엔진이 시작될 때 로드될 우선순위를 지 정하는 값입니다.

인덱스 페이지를 지정하는 것도 web.xml에서 할 수 있습니다. 많은 웹 사이트들이 구체적인 경로 지정 없이 도메인명까지 만 써줘도 페이지를 표시합니다. 이를테면 http://www.hangame.com으로 호출할 경우 다음과 같이 설정해두면 www. hangame.com의 /index.jsp를 호출하게 만들 수 있습니다.

<리스트 2> web.xml 인덱스 페이지 설정

index.jsp

태그명에서 짐작할 수 있듯이 인덱스 페이지는 여러 개를 둬서 순서대로 검색하게 할 수 있습니다. 예를 들어 index.html 과 index.jsp가 순서대로 지정된다면 서블릿 엔진은 index.html이 있으면 index.html을 보여주고 없으면 index.jsp를 호 출합니다. 이것도 없으면 404 에러가 나거나 디렉토리 목록이 보이게 됩니다. 이 인덱스 페이지는 모든 경로에 대해 동작합 니다.

이와 같은 설정의 경우 http://www.hangame.com/login/을 호출한다면 http://www.hangame.com/login/index.jsp 를 찾게 되는 것입니다. 이 설정은 사실 아파치 등의 웹서버에서도 해줄 수 있으나 보통 웹 서버에서는 인덱스 페이지가 실 제 파일로 존재해야 보여줄 수 있는데, 서블릿 엔진에서는 실제 파일로 존재하지 않고 서블릿 맵핑으로 지정만 되어 있어도 보여줄 수 있다는 장점이 있습니다.

접근 권한도 설정할 수 있습니다. 권한 체계가 간단한 웹 애플리케이션이라면 web.xml만으로도 충분한 권한 설정을 해줄 수 있습니다.

<리스트 3> web.xml 접근 제한 설정

retail /acme/retail/* GET POST CONTRACTOR HOMEOWNER

<리스트 3>의 예는 서블릿 스펙 문서에 있는 예입니다. 이것의 의미는 GET이나 POST로 /retail/*과 같은 요청은 CONTRACTOR와 HOMEOWNER라는 role을 가진 사용자에게만 허락하겠다는 뜻입니다. 이외의 사용자는 권한이 없다는

http://crowe.wowdns.com:8000/archives/dev_note/ (3 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

401 에러 페이지를 보게 됩니다. 이런 접근 제한 뿐 아니라 로그인 처리도 login-config 설정을 이용하면 가능합니다. 실 제 톰캣의 admin과 manager 애플리케이션은 이 설정을 이용해서 인증과 권한 처리를 합니다.

자세한 스펙은 서블릿 스펙 문서에 정의되어 있으나 실제 활용하기엔 다소 부족한 감이 있고, 톰캣의 실제 활용 예를 보는 것이 도움이 될 것입니다. 이외에도 서블릿 필터 설정, 세션 설정, 리소스 설정 등 여러 가지 유용한 설정을 해줄 수 있고 공 통적인 에외 처리를 위한 에러 페이지 설정도 가능합니다. 에러 페이지 설정 부분은 이후 예외 처리에서 자세히 다룰 것입니 다.

예외 처리 자바의 강점 중 하나가 편리한 예외 처리 방식입니다. C 등 예외 처리 문법이 없는 언어를 먼저 접한 프로그래머에게는 생소 한 개념일 수 있겠지만 알면 알수록 편리한 것이 자바의 예외 처리입니다. 하지만 의외로 많은 자바 프로그래머들이 예외 처 리를 어려워하고 예외 처리를 제대로 하지 않아 여러 가지 문제를 발생시킵니다.

기본이라고 할 수도 있는 부분이긴 하나 사실 이것은 자바의 예외 처리 문법만 배운다고 되는 문제는 아니며 예외 처리에 대 한 많은 고민이 필요합니다. 특히 웹 애플리케이션의 예외 처리는 프로그래머를 위한 부분과 웹 사이트 방문객을 위한 부분 두 가지를 모두 고려해야 합니다.

먼저 프로그래머의 입장을 살펴봅시다. 예외가 발생하면 어디까지는 그냥 던지고 어디서 캐치하는 것이 좋을까요? 자바의 예외는 자바 코드의 모든 영역에서 발생할 수 있습니다. 이 모든 영역에 다 try-catch를 걸고 예외를 잡을 수는 없는 일입니 다. 대부분의 예외는 일단 그냥 던지는 것이 좋습니다. 자바의 예외가 좋은 것은 꼭 예외가 발생한 그 지점에서 처리를 하 지 않아도 된다는 것 때문입니다. 예외를 던짐으로써 예외를 처리하기에 적절한 위치에서 처리하게 만들 수 있습니다. 어떻 게 처리해야 할지 잘 모르겠다면 그냥 그대로 던지도록 하는 것이 좋습니다.

예외를 잡아서 처리해야 하는 곳은 일반적으로 사용자에게 화면을 보여주기 직전이며 이것은 웹 애플리케이션이 MVC (Model-View-Controller) 패턴으로 작성되어 있다면 컨트롤러에서 이 일을 하게 됩니다. 컨트롤러에서 예외를 보고 판단 해 사용자에게 보여줄 화면을 결정하는 것입니다.

쇼핑몰에서 마일리지 적립액으로 상품을 구매하는 과정을 예로 들어보겠습니다. 만약 고객이 자신의 마일리지보다 더 많은 금액의 상품을 구매하려 한다면 구매를 수행하는 모델 객체에서 예외가 발생할 것입니다. 그러면 이 모델 클래스에서 예외 를 바로 잡지 말고 던져서 구매 프로세스의 컨트롤러 객체에서 이를 잡아서 예외 페이지로 포워드시켜 예외 메시지를 보여 주는 식으로 코딩하면 됩니다.

웹사이트 방문객을 위해 중요한 것은 자바 예외가 발생했을 때 이해할 수 없는 시스템 에러 메시지나 스택 정보 등의 황당 한 화면이 아닌 친절한 에러 메시지를 표시해 주는 것입니다. 이를 위해서는 컨트롤러에서도 처리하지 못하고 던져진, 정말 예상 밖의 예외를 모두 끌어 모아 처리하는 부분이 필요합니다.

서블릿/JSP에서는 이런 부분의 처리를 위한 기능을 여러 가지로 제공하고 있고 스트러츠 등의 프레임워크에서도 다양한 방 법을 제공합니다. JSP의 에러 페이지 설정이 그 한 예입니다. 그러나 JSP의 에러 페이지 설정 방식은 모든 JSP 페이지에 설 정해야 작동한다는 단점이 있습니다. 만약 에러 페이지 지정을 빠뜨린 페이지에서 예외가 발생한다면 서블릿 엔진의 에러 메시지가 그대로 웹사이트 방문객에게 전달되고 맙니다.

이런 부분을 쉽게 처리하기 위한 방법이 있습니다. 이것은 앞에서 설명했던 web.xml의 에러 페이지 설정을 이용하는 것입 니다. 우선 다음의 예를 볼까요.

<리스트 4> web.xml 에러 페이지 설정

http://crowe.wowdns.com:8000/archives/dev_note/ (4 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

java.lang.Exception /common/error.jsp

404 /common/error.jsp

이렇게 설정해 두면 웹 애플리케이션 전반에서 발생하는 예외 중 java.lang.Exception을 상속한 예외는 모두 잡혀서 / common/error.jsp 페이지에서 처리하게 됩니다. 예외가 발생하면 request 객체에 예외 상황에 대한 정보가 attribute로 저장된 후 /common/error.jsp로 포워드되어 이 곳에서 request에 담긴 정보를 바탕으로 예외 처리를 해줄 수 있습니다. 이 곳에서는 일반적인 에러 메시지를 사용자에게 보여주면 됩니다. 자바 예외 뿐 아니라 HTTP 에러 코드도 잡아낼 수 있습 니다.

이를테면 없는 페이지를 호출해서 404 에러가 나는 경우 이를 잡아서 페이지가 없다는 에러 메시지를 좀더 친절한 메시지 로 보여줄 수 있습니다. 덧붙여, 이 에러 처리 페이지는 가급적 순수한 서블릿으로 만드는 것이 좋습니다. 스트러츠의 Action으로 에러 페이지를 구성해본 적이 있었는데 설정 상의 문제로 스트러츠의 ActionServlet 로딩이 실패할 경우 예외 를 제대로 표시하지 못합니다. JSP로 만드는 것도 나쁘진 않으나 복잡한 로직이 들어갈수록 서블릿이 코딩하기 더 편할 수 있습니다. 만약 이 에러 페이지 자체에서 또다시 예외가 발생하면 찾기 힘든 경우가 많기 때문에 주의를 많이 기울여야 합니 다.

로깅 에러 페이지에서 해야 할 또 하나 중요한 일은 예외 상황에 대한 로그를 남기는 것입니다. 에러 페이지까지 왔다는 것은 이 미 개발자의 예상을 벗어난 동작을 하고 있다는 것이므로 이 사실은 개발자에게 빨리 전달되어야 합니다. 이 때문에 로그를 제대로 남겨서 조회하기 편한 시스템을 구축해야 합니다. 로깅 API는 여러 가지가 있고 JDK 자체에도 포함되어 있지만 log4j가 가장 널리 사용되고 성능, 기능, 안정성 등 여러 가지 면에서 다른 것들보다 낫습니다.

여러가지 로깅 API를 바꿔가면서 사용할 수 있게 해주는 자카르타의 commons-logging 프로젝트도 쓸만합니다. 로거 객 체는 일반적으로 클래스 당 하나를 클래스의 전체 이름으로 생성해 사용합니다. <리스트 5>는 commons-logging을 사용 하는 예입니다.

<리스트 5> 로그 사용법

package com.hangame.avatar;

import ...

public class Avatar { private static Log log = LogFactory.getLog(Avatar.class);

public void changeBackgroud() {

http://crowe.wowdns.com:8000/archives/dev_note/ (5 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

log.debug("avatar changing.."); } }

이러면 로그 객체는 Avatar 클래스의 전체 이름, com.hangame.avatar.Avatar로 생깁니다. 만약 여기에 log4j를 붙여 서 사용한다면 <리스트 6>과 같은 설정을 사용할 수 있습니다.

<리스트 6> log4j.xml 예제

http://crowe.wowdns.com:8000/archives/dev_note/ (6 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

이 설정은 com.hangame와 org.apache라는 이름의 로거를 두 개 생성하고 있습니다. 로거의 특성은 이름으로 상속됩니 다. com.hangame.avatar.Avatar라는 이름의 로거는 com.hangame의 속성을 모두 상속 받게 됩니다. 그러면 com. hangame이 normal과 memory라는 두 개의 appender를 갖고 있기 때문에 com.hangame.avatar.Avatar 로거가 찍 은 로그는 표준 출력으로도 나가고 메모리에도 남게 됩니다. log4j의 이런 특성을 이용하면 다양한 방식으로 로그를 남길 수 있고 로그를 선택적으로 켜고 끌 수 있습니다. 이런 기능들을 잘 활용하면 로그를 조회하기 쉽게 구성할 수 있습니다.

앞에서 예를 든 것처럼 메모리에 최근 로그를 남겨두고 이를 조회할 수 있는 페이지를 만든다거나 데이터베이스에 로그를 쌓을 수도 있습니다. 그리고 주기적으로 이런 로그 조회 페이지를 모니터하면서 로그 리포트를 개발자에게 메일 등으로 자 동 발송하는 시스템도 구상해 볼 수 있을 것입니다.

예외 추적 예외 처리 시스템을 구축하고 예외 로그를 남겼으면 다음은 이 정보를 바탕으로 문제점을 찾아들어가는 것입니다. 예외 추 적의 출발점은 당연히 예외 스택 정보입니다. 대부분의 문제는 예외 스택 정보만 가지고도 찾아낼 수 있습니다.

하지만 의외로 많은 프로그래머들이 예외가 발생했을 때 스택 정보를 보지 않고 자신의 경험에 의지해 문제점을 예측하려 하곤 합니다. 이런 실제 상황에 기반을 두지 않은 예측은 운 좋게 문제를 바로 짚어내는 경우도 있겠지만 대개의 경우 시간 만 낭비하게 됩니다. 예외가 발생하면 반드시 스택 정보에 찍힌 소스의 라인부터 살펴보는 습관을 기르는 것이 좋습니다.

스택 정보는 가끔 수백 라인에 이를 정도로 길어지는 경우도 간혹 있습니다. 이 모든 정보를 다 찾아볼 필요는 없습니다. 스 택 정보는 메쏘드가 호출된 역순으로 찍히므로 위에 있는 정보가 예외가 발생한 위치와 가까운 정보입니다.

그렇다고 늘 제일 위의 정보를 봐야 하는 것은 아닙니다. 웹 애플리케이션의 경우 스택 정보는 자신이 작성한 클래스뿐 아니 라 서블릿 엔진을 포함한 여러 가지 클래스의 정보가 같이 담겨 있습니다. 이런 정보는 보통 볼 필요가 없고 스택 정보에서 자신이 작성한 클래스 중 제일 위에 있는 것, 이것이 예외가 발생한 지점이며 이 곳을 찾아보면 대부분의 문제점은 정확하 게 추적할 수 있습니다.

또 한 가지 자바 초보자를 괴롭히는 문제는 NullPointerException입니다. 사실 이것은 초보자에게는 아주 까다로운 문제지 만 조금만 알면 가장 찾기 쉬운 문제 중 하나가 NullPointerException입니다. NullPointerException은 객체의 멤버 변수 나 메쏘드를 이용하려고 할 때 그 객체가 null인 경우에 발생합니다. 따라서 NullPointerException이 발생하면 앞의 방법 대로 예외가 발생한 라인을 찾아들어간 다음 그 라인에서 멤버 지정 연산자(.) 앞에 있는 객체를 보면 됩니다. 이 사실만 알 고 있어도 NullPointerException이 발생했을 때 어떤 객체가 null인지를 쉽게 찾아낼 수 있을 것입니다.

간혹 NullPointerException이 싫어서 다음과 같은 코드를 작성하는 경우가 있습니다.

if ("Y".equals(param)) doSomthing(); else doOther();

이런 코드는 조심해서 써야 합니다. param의 null 체크가 귀찮아서 이런 식의 코드를 쓰곤 하는데 만약 param의 값이 Y 인 경우는 doSomething()을 실행하고, N이나 null이면 doOther()를 실행해야 하는 경우라면 이 코드는 문제가 없습니 다. 그러나 만약 param은 null이면 안 되는 상황이라면 어떻게 될까요? 다른 부분의 버그로 param에 null이 들어와도 프 로그래머는 이것을 알아차리지 못하고 넘어가게 됩니다. 즉, 버그를 은폐하는 코드가 됩니다.

당장의 문제를 발생하지 않더라도 이런 코드는 나중에 찾기 힘든 문제를 유발할 수 있습니다. 이런 경우는 그냥 NullPointerException이 발생하도록 내버려 두면 param에 null 값이 들어왔을 때 다른 부분에 버그가 있기 때문이라는 사 실을 감지할 수 있습니다. 상황에 따라 위와 같은 코드를 써도 되는지를 신중히 검토한 후 사용해야 합니다. 예외 발생이 두 려워서 버그를 은폐할 수 있는 코드를 만들지 맙시다.

한글 문제

http://crowe.wowdns.com:8000/archives/dev_note/ (7 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

웹 프로그래머들을 괴롭게 하는 문제를 꼽을 때 빠지지 않는 것이 한글 문제입니다. 한글 문제가 지금처럼 골치 아프게 된 데는 역사적으로 복잡한 원인들이 얽혀 있는데 이런 문제는 접어두고 자바 웹 프로그래머로서 한글 문제를 해결하기 위해 알아야 하는 것들을 살펴보겠습니다.

자바는 문자열과 바이트 스트림을 다르게 취급합니다. 자바의 스트링은 유니코드의 문자셋을 사용하며 문자열을 파일에 쓰 거나 네트워크로 전송하는 등 실제 입출력이 일어날 때는 문자열을 바이트 스트림으로 변환하게 됩니다. 이 때 바이트 스트 림으로 변환하는 규칙이 인코딩입니다. 따라서 바이트 스트림으로 전달된 것을 문자열로 바꾸거나 문자열을 바이트 스트림 으로 전달할 때는 반드시 인코딩을 지정해야 합니다.

이런 인코딩 중 한글을 표현할 수 있는 인코딩은 자바에서 사용하는 이름을 기준으로 하면 EUC-KR, MS949, UTF-8, UTF-16 정도가 있습니다. EUC-KR은 KSC5601-1987 기반 인코딩으로 한글의 모든 문자를 다 표현할 수 없습니다. MS949는 EUC-KR을 확장해 모든 한글을 표현할 수 있지만 비표준이고 코드 자체에 기술적인 결함이 많습니다. UTF-8과 UTF-16은 유니코드의 인코딩들이며 모든 한글을 표현할 수 있고 표준이며 한글 이외의 다른 언어와 함께 표현이 가능합니 다.

보통 많이 쓰이는 EUC-KR은 RFC 표준 인코딩이긴 하나 한글의 확장 문자들을 제대로 표시하지 못합니다. 그래서 자바 웹 프로그래밍에서는 MS949를 많이 쓰게 됩니다. 자바에서 스트링 객체를 생성할 때는 이 중에 하나로 인코딩을 줘서 생성해 야 한글을 표현할 수 있게 인코딩됩니다.

웹서버로 전달되는 요청은 클라이언트의 웹브라우저가 문자열을 바이트 스트림으로 인코딩하는데 이 때 사용하는 인코딩 은 일반적으로 한글 윈도우의 기본 인코딩인 MS949입니다. 그런데 서블릿 엔진에서 요청을 처리하는 데 사용하는 기본 인 코딩이 ISO-8859-1이기 때문에 아무 것도 지정하지 않으면 MS949로 인코딩된 바이트들을 ISO-8859-1 인코딩의 스트 링 객체로 만들기 때문에 한글이 깨져보이게 됩니다. 따라서 기본 인코딩을 MS949로 지정하면 인코딩이 보존된 상태로 한 글이 깨지지 않게 됩니다. HttpServletRequest.setCharacterEncoding() 메쏘드에서 이것을 지정할 수 있습니다.

그러나 이것에도 약간 문제가 있습니다. 서블릿 스펙상 이 메쏘드는 POST 요청에만 적용됩니다. 즉, POST 요청의 파라미 터는 setCharacterEncdoing에서 지정한 인코딩으로 스트링 객체가 생성되기 때문에 한글을 보존할 수 있으나 GET 요청 은 setCharacterEncoding의 적용을 받지 않기 때문에 GET으로 받은 파라미터는 인코딩 변환을 다시 해주어야 합니다. 다 만, 이것은 서블릿 엔진에 따라 다릅니다. 톰캣의 경우도 4.1 버전과 5.0 버전이 다르게 동작하니 주의가 필요합니다.

웹서버에서 다시 클라이언트로 응답할 때는 반대의 과정입니다. 자바의 스트링 객체가 바이트 스트림으로 변환되며 이 때 역시 인코딩을 지정해야 합니다. 이 인코딩은 JSP 페이지에서 페이지 지시자의 pageEncoding 속성을 통해 지정할 수 있 고, 서블릿 2.4 스펙에서는 HttpServletResponse.setCharacterEncoding을 사용할 수 있습니다. HTTP 요청을 읽는 과 정과 역순이라고 생각하면 됩니다.

그리고 웹 서버에서 요청을 읽을 때 MS949를 지정해 주듯이 클라이언트의 웹 브라우저도 웹 서버에서 생성한 응답을 정확 하게 읽으려면 어떤 인코딩을 사용해야 하는지 알아야 합니다. 이것을 지정해주는 것이 HTML의 Content-Type입니다. 다 음과 같이 지정할 수 있습니다.

여기서 지정하는 charset은 원칙적으로는 당연히 웹 서버에서 응답 객체를 생성할 때 지정한 인코딩 값과 같아야 한글로 제 대로 읽을 수 있습니다. 그러나 여기 지정하는 charset이 RFC 표준 문자셋이 아닐 경우 브라우저에 따라 인식하지 못할 수 도 있습니다. 따라서 MS949로 인코딩했다면 MS949를 지정해야 정상이지만 MS949가 RFC 표준이 아니기 때문에 문제 가 생길 수 있습니다.

그렇다고 응답의 인코딩을 EUC-KR로 지정하게 되면 확장 한글을 표시할 수 없기 때문에 문제가 됩니다. 그래서 페이지 인 코딩은 MS949로 하지만 Content-Type에는 euc-kr을 지정해 주게 되는 것입니다. 물론 이렇게 되면 경우에 따라 확장 한 글이 깨질 수 있지만 다행스럽게도 대부분의 브라우저에서 이렇게 지정하면 잘 동작합니다.

사실 이 부분은 응답 스트림에 적용되는 인코딩과 HTML Content-Type에 지정하는 인코딩이 같기만 하면 되기 때문에 굳 이 MS949를 사용할 필요는 없고 UTF-8 등의 인코딩을 사용해도 무방합니다. 따라서 응답 스트림의 인코딩도 UTF-8로 하 고 Content-Type도 UTF-8로 지정하는 것이 가장 확실한 방법일 수 있습니다. 또한, HTML의 Content-Type에 UTF-8이 지정되어 있으면 이 페이지에서 폼을 전송할 경우에도 UTF-8로 인코딩되어 요청을 파싱하는 쪽에서도 UTF-8을 사용할 수 있습니다.

유니코드의 인코딩들인 UTF-8, UTF-16은 한 인코딩으로 다국어를 처리할 수 있기 때문에 다국어 지원이 필요한 웹 애플리 케이션은 실제로 UTF-8로 작성된 것이 많습니다. 다국어 지원이 필요 없다고 해도 UTF-8을 사용하는 것이 오히려 한글 문 제를 더 쉽게 해결하는 방법이 될 수 있습니다. http://crowe.wowdns.com:8000/archives/dev_note/ (8 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

웹 뿐 아니라 데이터베이스나 파일에 입출력을 할 때도 마찬가지의 원리가 적용됩니다. 사용하는 인코딩이 다르면 변환 과 정을 거쳐야 합니다. 이것은 리눅스나 유닉스에서 문제가 될 수 있습니다. 리눅스는 MS949를 지원하지 않고 EUC-KR만 지 원하기 때문입니다. 따라서 윈도우에서 개발하고 리눅스에서 돌리는 경우 문제가 되는 경우가 간혹 있습니다.

MS949가 또 하나 문제가 되는 영역은 XML 파서입니다. 현재 가장 널리 사용되는 XML 파서는 Xerces인데 이 파서는 RFC 표준 문자셋 외에는 지원하지 않기 때문에 MS949 인코딩은 파싱 에러가 납니다. 그런 반면 JDK 1.4에 포함된 파서 인 Crimson은 네임스페이스 파싱에 버그가 있습니다. MS949를 XML 인코딩으로 쓸 경우 XML 파서 선택이 문제가 될 수 있는 것입니다.

다행스럽게도 JDK 5.0에 포함된 파서는 Xerces를 썬에서 패치한 것인데 이것은 아무 문제가 없습니다. 하지만 여전히 많 은 오픈소스 라이브러리들이 Xerces를 사용하고 있기 때문에 문제가 되는 경우는 계속 나타날 수 있을 것입니다. 이것 때문 에라도 UTF-8을 사용할 필요가 있습니다.

자바에서의 한글 문제는 문자열과 바이트 스트림의 변환에 인코딩이 주어져야 한다는 사실만 생각하면 다 쉽게 해결할 수 있습니다. 역시 기본이 잘 갖춰져 있으면 한글 문제도 쉽게 해결할 수 있는 것입니다.

URL 인코드 URL 인코딩이 필요한 것은 URL에 사용 가능한 문자가 제한되어 있기 때문입니다. URL 스펙(RFC 1738)에 정의된 바로는 URL에 사용할 수 있는 문자는 알파벳, 숫자와 몇 가지의 특수문자뿐입니다. 따라서 다양한 문자를 URL로 전달하려면 URL 에서 허용하는 문자로 변환해 전달해야 합니다. 이것은 GET 요청의 파라미터로 값을 전달하려할 때 문제가 됩니다.

예를 들어 http://website.com/process.jsp에 로그인되지 않은 상태에서 접근하면 자동으로 로그인 페이지인 http:// website.com/login.jsp로 리다이렉트된 후 로그인을 하면 원래 요청했던 페이지로 다시 리다이렉트되도록 해야 한다고 합 시다.

그러면 /process.jsp에서는 로그인 페이지로 리다이렉트시키면서 파라미터로 현재 요청한 URL, 즉 /process.jsp를 넘겨 주고 login.jsp에서는 로그인 처리가 끝난 후 이 URL로 다시 리다이렉트시키면 됩니다. 여기서 /process.jsp에서는 http://website.com/login.jsp?redirect=http://website.com/process.jsp와 같은 형식으로 리다이렉트를 해주면 될 것입니다.

여기서 문제는 redirect 파라미터의 값이 URL이기 때문에 URL 안에 URL이 들어간 형태가 되어 제대로 파싱되지 않는다 는 것입니다. 그래서 파라미터로 넘겨야 하는 URL 부분을 URLEncoder로 인코딩해 http://website.com/login.jsp? redirect=http%3A%2F%2Fwebsite.com%2Fprocess.jsp와 같은 형태로 넘겨야 합니다. 이 값을 받는 부분에서는 다 시 디코딩을 해줄 필요가 없습니다.

URL은 자동으로 웹 서버에서 파싱할 때 디코딩을 해주기 때문입니다. URL을 통해 GET 요청의 파라미터로 보내야 하는 값 은 반드시 URL 인코딩을 거쳐야 한다는 사실만 기억하세요. 참고로 자바스크립트에서도 escape, unescape 함수를 통해 URL 인코딩, 디코딩과 유사한 작업을 수행할 수 있습니다.

클래스패스의 리소스 사용법 웹 애플리케이션은 보통 애플리케이션의 설정을 담고 있는 파일이 필요합니다. web.xml, struts-config.xml 등의 설정 파 일들은 보통 웹 애플리케이션의 /WEB-INF/에 위치하는데 그 외에 애플리케이션에서 사용하는 파일들은 어디에 놓고 사용 하는 것이 편리할까요? 가장 관리하기 쉽고 부가적인 작업이 적은 방법은 클래스 패스에 두는 것입니다. /WEB-INF/ classes에 두면 자바의 클래스 로더를 이용해 이런 파일들에 접근할 수 있습니다. log4j 등 많은 라이브러리들이 자신의 설 정 파일을 클래스 패스에서 가장 먼저 찾게 됩니다. <리스트 7>의 예제를 볼까요.

<리스트 7> 클래스패스의 파일 읽기

http://crowe.wowdns.com:8000/archives/dev_note/ (9 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

public File getFile(String name) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); return new File(loader.getResource(name).getFile()); }

public void doSomeProcess() { File file = getFile("config.xml"); }

이 코드는 클래스 패스에서 config.xml을 읽습니다. 웹 애플리케이션의 기본 클래스 패스는 /WEB-INF/classes이므로 기 본적으로 여기서 찾게 됩니다. 이것으로 jar 파일 안의 내용도 읽을 수 있습니다. 이 경우는 ClassLoader. getResourceAsStream을 통해 스트림으로 파일 내용을 읽을 수 있습니다.

대부분의 IDE나 maven 등의 빌드 툴에서는 소스 경로에 있는 파일 중 자바 소스가 아닌 파일들을 자동으로 클래스 패스 로 복사해주므로 이용하기도 편리합니다. 자카르타의 commons-discovery 프로젝트는 이런 기능들을 모아서 편리하게 이 용할 수 있게 제공하고 있습니다.

서블릿/액션 멤버 변수 공유 문제 JSP가 보급되기 시작하던 초기에 많이 발생하던 문제로 웹 사이트 이용자가 접속했을 때 자신의 정보가 아닌 다른 사람의 정보가 나타나면서 엉키는 경우가 있었습니다. 이것의 원인은 서블릿에 대한 이해가 부족해서 발생한 것이었습니다. <리스 트 8>을 봅시다.

<리스트 8> 멤버 변수가 공유되는 서블릿

public class BadServlet extends HttpServlet {

Map userInfo;

protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

String username = req.getParameter("name"); Map userInfo = UserManager.getUserInfo(username); req.setAttribute("userInfo", username); } }

얼핏 별 문제가 없어 보이지만 이 코드는 심각한 문제가 있습니다. 서블릿은 보통 서블릿 엔진에서 하나만 생성되고 한 번 생성된 서블릿 객체가 계속 재활용됩니다. 때문에 A와 B라는 두 사용자가 동시에 이 서블릿을 호출하게 되면 A의 호출을 수 행하는 중에 B의 호출이 userInfo의 값을 바꿔버릴 수 있습니다. 그러면 A는 B의 정보를 보거나 그 반대의 경우가 생길 수

http://crowe.wowdns.com:8000/archives/dev_note/ (10 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

있는 것입니다.

혼자서 테스트할 때는 한 번에 한 쓰레드만 service 메쏘드를 호출하기 때문에 이런 문제가 잘 드러나지 않기 때문에 별 문 제 없는 줄 알고 있다가 서비스를 오픈하고 나면 문제가 되는 경우가 있으므로 조심해야 합니다. JSP에서 <%! %>를 통 해 선언하는 내용도 마찬가지 문제가 발생하므로 주의해야 합니다. 이런 내용 역시 자바 클래스와 멤버 변수의 기본 개념을 이해하고 서블릿 스펙만 한 번 읽어본다면 금방 알 수 있는 내용입니다.

생각하기 이 내용을 읽으면서 모르는 내용이 하나도 없었다면 자바 웹 프로그래머로서 어느 정도 기본은 되어 있다고 할 수 있습니 다. 이런 내용은 그 하나하나에 대한 지식을 쌓는 것도 중요하지만 더 중요한 것은 이런 내용을 알아야 한다는 사실을 아는 것입니다.

무엇을 알아야 하는가를 가르쳐주는 것은 스펙입니다. 스펙 문서들은 대부분 영어이고 그다지 친절하게 되어 있진 않지만 해당 분야에 대해 가장 정확한 정보를 담고 있습니다. 자세한 내용을 다 알진 못하더라도 스펙에 어떤 내용이 있는지 알아 야 그 내용 중 자신에게 필요한 내용을 찾아서 공부할 수 있는 것입니다. 이런 정보를 어디서 찾을 수 있는가를 알고 있는 것 도 중요합니다. 기본적으로 www.ietf.org, jcp.org,java.sun.com, www.w3.org 정도의 사이트에는 익숙해지는 게 좋을 것입니다.

많은 프로그래머들이 실제로 자기 손으로 프로그래밍해보는 게 실력이 느는 제일 좋은 방법이라고 말하지만 필자는 여기에 동의하지 않습니다. 물론, 실제 경험을 쌓는 것이 필수적인 과정이긴 합니다. 그러나 기본 지식을 등한시한 상태에서 코딩 만 해보는 것으로는 실력이 잘 늘지 않습니다.

코딩 기술은 늘 수 있겠지만 정말 실제 서비스를 해야 하는 프로그래밍에서 중대한 실수를 저지르게 되거나 남들이 쉽게 하 는 일들을 어렵게 빙 둘러가면서 하게 될 수 있습니다. 그래서 기본기를 갖추는 것이 중요한 것입니다.

거듭해서 기본의 중요성을 강조했는데 한 가지 덧붙이고 싶은 말은 이런 기본 지식 뿐 아니라 기본을 활용하는 능력을 키우 는 것도 잊지 말아야 한다는 것입니다. 앞서 언급한 예외 처리 같은 내용은 기본이긴 하나 자바 문법만 잘 안다고 알 수 있 는 내용은 아니며 기본을 바탕으로 좋은 판단을 내릴 수 있는 능력이 있어야 합니다.

결국 좋은 프로그래머가 되려면 먼저 좋은 사고 능력을 가지고 있어야 하는 것입니다. 글짓기를 잘하는 방법으로 흔히 다독 (多讀), 다작(多作), 다상량(多商量)을 이야기합니다. 많이 읽고 많이 쓰고 많이 생각하라는 것입니다. 프로그래밍도 이와 비슷합니다. 각종 스펙과 좋은 코드를 많이 읽어보고 직접 코딩도 많이 해보면 분명 실력이 늘지만 이것으로는 충분치 않습 니다.

프로그래밍을 하면서 끊임없이 생각해야 합니다. 지금 작성한 코드는 좋은 코드인가, 이렇게 코딩하면 불편한데 개선할 방 법은 없을까, 이 API들로 무엇을 할 수 있을까, 개발 속도를 향상시키려면 어떻게 해야 할까 등 생각을 많이 해야 진짜 발전 을 이룰 수 있습니다.

만일 손가락이 아플 정도로 하루 종일 키보드를 두드리고 있다면 좋은 프로그래머라고 할 수 없습니다. 생각하는 데 좀 더 많은 시간을 써야 합니다. 모니터를 구부정하게 들여다보면서 키보드를 두드리는 것은 것보다는 의자에 편안히 기대서 생각 하는 시간을 늘려보세요. 복잡한 문제가 있으면 바깥 공기를 쐬면서 산책을 하면서 생각을 하는 것도 좋습니다. 굳이 건강 을 생각하지 않더라도 걷는 것은 두뇌를 활성화하기 때문에 해결책을 더 빨리 찾을 수 있게 합니다.

남들이 보기에는 게을러 보일 수 있지만 놀고 있는 게 아니라는 것은 결과로 충분히 보여줄 수 있습니다. 물론 이런 생각을 잘 이어나가기 위해서는 생각의 재료가 되는 기본에 충실해야 함은 물론입니다. 어둠침침한 구석에 앉아 키보드만 두드리 는 긱(geek)이 아닌 보다 인간다운 프로그래머가 됩시다.@

* 이 기사는 ZDNet Korea의 제휴매체인 마이크로소프트웨어에 게재된 내용입니다.

Posted by Crowe Lee at 07:47 PM | Comments (0) | TrackBack

세련된 자바 웹 프로그래머 되기 | 웹 프로젝트 개발 환경 갖추기

View: ZDNet Korea | [세련된 자바 웹 프로그래머 되기] 웹 프로젝트 개발 환경 갖추기

지난 글에서는 자바 웹 프로그래머로서 한 사람 몫을 해내기 위한 기초에 대해 다루었습니다. 다음 단계는 한 사람 몫을 넘어서 팀 전체에

http://crowe.wowdns.com:8000/archives/dev_note/ (11 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

영향을 미치는 일을 할 수 있는 능력을 기르는 것이겠죠. 팀에서 어떠한 역할을 맡아도 잘해낼 수 있으려면 프로그래밍도 잘해야 하지만 개 발 환경을 구성하는 방법도 잘 알고 있어야 합니다.

이 글에서는 개발 환경이 잘 갖춰진 팀의 프로그래머는 이미 구축된 환경에 대해 좀 더 잘 이해할 수 있도록, 그렇지 못한 경우는 스스로 개 발 환경을 구축해나갈 수 있도록 여러 가지 개발에 필요한 것들을 살펴보겠습니다.

레이싱 경기장에는 피트(pit)라고 부르는 장소가 있습니다. 경주차의 수리나 조정, 타이어 교환, 연료 보급 등을 하는 곳이죠. 이런 일을 담 당하는 사람을 미캐닉(mechanic)이라고 부릅니다. 레이싱 경기가 얼핏 보면 차의 성능과 드라이버의 기량만이 승부를 결정짓는 것 같지 만, 장거리 레이스가 될수록 미캐닉의 역할이 승부를 가르는 경우도 적지 않습니다. 미캐닉이 잘한다고 레이싱에서 승리하는 것은 아니지 만 미캐닉이 못하면 십중팔구 패배하게 됩니다.

웹 프로젝트도 이런 면에서 레이싱과 비슷한 점이 있습니다. 웹 프로젝트의 성공에 있어 가장 중요한 것은 요구사항의 구현이고, 기술적인 성공을 좌우하는 것은 프로그래밍이지만 프로그래밍 외의 기술적인 잡무들은 웹 프로젝트의 실패를 결정지을 수 있는 조건입니다. 소스 관 리를 비롯해서 웹의 특성상 자주 발생하는 서버 설치, 프로젝트의 배치(deploy), 서버 리스타트 등이 그런 일들이죠. 이런 일들은 대체로 단순 반복 작업이면서도 실수할 가능성이 있고 또 실수를 하면 그 파급 효과가 클 수 있습니다.

그래서 이런 개발 주변 환경을 제대로 갖춰놓지 않으면 이것이 프로젝트를 실패로 이끌기도 합니다. 고로, 레이싱에서 승리하기 위해서는 좋은 미캐닉과 좋은 장비들을 확보해서 피트에서의 작업을 효율적으로 해내야 하는 것처럼 웹 프로젝트를 성공으로 이끌기 위해서는 개발 환경을 잘 갖춰서 프로그래머들의 잡무 부담을 줄이는 것이 중요합니다.

경우에 따라서 미캐닉처럼 이런 잡무를 전담하는 사람을 따로 두는 경우도 있습니다. 혹은 일부러 전담자를 두지 않더라도 한두 사람만이 수행 방법을 숙지하게 되어 결국 이 사람들에게 잡무 요청이 몰려 실질적인 전담자가 되버리는 경향이 있습니다. XP(eXtreme Programming)의 지속적인 통합(Continuous Integration)도 이런 문제를 이야기합니다. 통합 작업을 자동화해서 누구나 쉽게 할 수 있 게 만들지 않으면 한두 사람만이 통합 작업을 할 수 있게 되고 통합을 자주 수행할 수도 없게 됩니다.

그러면 각자 개발한 모듈들을 통합할 때 많은 문제가 발생할 수 있고, 그런 경우 통합이 잘못된 것인지 아니면 각자 개발한 소스가 잘못된 것인지 찾기 힘들게 되죠. 이런 상황을 ‘Integration Hell’이라고 부르는데, 이런 상황을 피하기 위해서는 지속적인 통합을 통해 문제를 조 기에 발견하여 문제가 쌓이지 않게 해야 합니다. 이 글에서는 지속적인 통합을 확장해 프로그래머들을 잡무에서 해방시켜 좀 더 창조적인 업무에 전념할 수 있도록 개발 환경을 구축하는 방법들을 이야기할 것입니다.

‘무엇을 해야 하는가’에 대한 고민 좋은 소프트웨어를 만드는 첫걸음은 어떻게 만드는가를 아는 것이 아니라 무엇을 만들어야 하는가를 아는 것입니다. 아무리 고급 코딩 기 술과 어려운 알고리즘을 자유자재로 구사한다고 해도 사용자가 원하지 않는 소프트웨어를 만들어 냈다면 실패한 것입니다. 개발 환경 구축 도 각종 도구들의 사용법을 익히는 것도 중요하지만 그 전에 자신이 속한 조직에서 어떤 일들이 있고 이런 일들을 잘하려면 무엇을 갖추어 야 하는가를 고민하는 것이 첫 번째입니다.

이런 요구사항은 팀의 상황에 따라 많이 달라질 수도 있고 범위를 어떻게 잡느냐에 따라 간단한 빌드 작업으로 한정될 수도 있고 종합적인 프로젝트 관리를 포함하는 광범위한 내용이 될 수도 있습니다. 그럼 웹 프로젝트에는 어떤 요구사항이 있고 이런 요구사항을 충족하기 위 해서 어떤 개발 환경이 필요한지를 살펴보겠습니다.

의사소통 관리 비단 웹 프로젝트 뿐 아니라 대부분의 프로젝트들에서 가장 중요한 성공 요인은 프로젝트의 성공을 향한 팀원들의 의지와 팀원간의 효율적 인 의사소통(communication)입니다. 얼핏 둘 다 비기술적인 이슈라서 프로그래머의 영역 밖이라고 생각할 수도 있겠지만 의사소통은 이 미 개발 방법론의 영역에 들어와 있습니다. 의사소통은 흔히 구성원들의 적극성의 문제, 즉 사람의 문제로 치부되는 경향이 있고 이것이 어 느 정도는 사실입니다.

하지만 최근 유행하는 방법론들에서는 환경적 요인이 의사소통에 미치는 영향이 크다고 보고 의사소통을 효율적으로 하기 위한 장치들을 많이 제시하고 있습니다. 일반적인 프로그래밍 작업에서의 의견 교환, 의사 결정을 위한 회의, 문서화, 업무 요청 관리, 고객의 요구사항 관 리 등이 모두 의사소통이며 개발 환경을 통해 이런 부분들을 향상시킬 수 있습니다.

XP에서는 보통 의사소통 비용은 물리적인 거리의 제곱에 비례해서 늘어난다고 합니다. 그래서 의사소통을 효율적으로 하기 위한 방법들로 물리적인 요소를 많이 제시합니다. 프로그래밍을 두 사람이 붙어 앉아서 같이 하는 짝 프로그래밍(Pair Programming), 회의가 소모적으 로 흐르는 것을 방지하기 위한 기립 회의(Stand up Meeting), 자연스러운 정보 확산과 의견 교환을 위한 정보 방열기(Information Radiator) 등이 그런 것들이죠. 이런 프랙티스(practice)들을 잘 실천하려면 짝 프로그래밍을 할 수 있는 자리 배치, 팀원들이 쉽게 모여 서 이야기할 수 있는 공간, 화이트보드 등 물리적 환경을 갖추어 놓아야 합니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (12 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

소프트웨어적으로 지원할 수 있는 부분도 많습니다. 업무 요청 관리는 보통 사내 그룹웨어 등으로 소화하기 마련인데 이 부분이 잘 되어 있 지 않으면 일의 진행에 병목현상이 발생하고 때로는 팀원간 마찰의 원인이 되기도 합니다. 단순히 메일을 이용하는 것보다는 업무 요청을 보내는 것, 진행 상황, 결과 등을 종합적으로 관리할 수 있는 소프트웨어가 있는 것이 좋습니다. 이슈 트래커(issue tracker)도 어느 정도 이런 부분을 소화할 수 있으나 팀의 상황에 맞게 개발해두는 것도 좋을 것입니다.

문서화 역시 시스템으로 지원해야 할 부분입니다. 문서화 시스템에서 가장 중요한 것은 문서를 빠르게 작성하고 작성한 문서를 즉시 공유 할 수 있는 것입니다. 그리고 버전 관리도 되어야 하죠. 이런 요구사항을 가장 잘 만족하는 것은 위키(wiki)입니다. 대규모 CMS(Content Management System)도 많지만 오히려 단순한 위키가 더 높은 유연성을 발휘하는 경우가 많습니다.

소프트웨어 형상 관리 소프트웨어 개발 환경에서 기술적으로 가장 중요한 것은 소스 버전 관리입니다. 개발팀에서 작성하는 모든 소스는 버전 관리가 되어야 합 니다. 프로그램 소스는 물론이고 스크립트, SQL, 각종 문서, 설정 파일들까지 포함합니다. 소스 버전 관리의 목적은 작업 기록의 보존을 통 해 문제가 발생했을 때 원인을 추적하거나 이전 상태로 되돌리기 위한 것이죠.

보통 소스 버전 관리를 위해서 CVS(Concurrent Versions System)를 많이 사용합니다. CVS를 좀 더 개선한 Subversion도 있고 여러 가지 상용 툴도 있지만 여전히 CVS가 오픈소스 공동체에서 가장 많이 쓰이며 IDE와도 잘 통합되어 있습니다. CVS에 대한 내용 설명은 『실용주의 프로그래머를 위한 버전 관리 using CVS』라는 책을 추천하는 것으로 대신하겠습니다.

소스 관리는 사실 SCM(Software Configuration Management)의 일부분이기도 합니다. SCM은 소스에 대한 버전 관리 뿐 아니라 소프 트웨어의 기능성 수준에 대한 변동 기록, 버그 수정 내역, 요구사항의 변화 등을 종합적으로 관리하는 것을 말합니다. 소프트웨어의 새 버 전이 발표되면 보통 릴리스 노트(Release Notes)가 같이 배포되는데 이런 것이 SCM의 대표적인 산출물이죠.

상용 SCM 툴도 많지만 보통은 소스 관리에 CVS를 쓰고 이외에는 앞에서 언급한 이슈 트래커를 쓰는 것으로 SCM의 대부분의 영역이 커버 됩니다. 소스 외의 형상 관리는 사실 업무 요청 관리와 기능적으로 아주 비슷하기 때문이죠. 오픈소스와 친한 사람이라면 버그질라 (bugzilla)를 접해본 적이 있을 것입니다. 버그를 보고하고 버그가 수정되는 과정을 기록으로 남기고 조회할 수 있게 해주는 시스템이죠.

이런 것을 버그 트래커(bug tracker)라고 하는데 이를 확장한 것이 위에서 언급한 이슈 트래커입니다. 오픈소스 소프트웨어 중에도 Mantis나 TUTOS 등이 있고 위키와 이슈트래커를 합친 Trac이 있습니다. 이런 툴 하나 정도는 갖춰 놓아야할 것입니다.

빌드 자동화 지속적인 통합에서는 빌드 자동화를 가장 중요하게 다룹니다. 빌드는 소스코드를 컴파일하고 여러 가지 변환 작업을 거쳐 프로그램이 동작 할 수 있게 구성하는 작업을 말합니다. 일반적인 자바 애플리케이션은 간단한 컴파일만으로 빌드가 완료되지만 웹 프로젝트는 좀 더 할 일 이 많습니다. 컴파일한 클래스들이 웹 프로젝트 구조에 맞게 위치해야 하고 각종 라이브러리, TLD 파일들도 적절히 위치시켜 웹 컨테이너 (서블릿 엔진)에 배치시켰을 때 정상적으로 동작하도록 해야 합니다.

프로젝트 구조가 복잡하면 그만큼 빌드 자동화도 더 복잡해집니다. 그래서 빌드 자동화 작업을 하기 전에 우선 프로젝트 구조를 잘 구성해 놓아야 합니다. <표 1>은 빌드 자동화 툴인 Ant에서 제시하는 프로젝트 구조에 대한 권고안입니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (13 of 158)2005-07-05 오전 11:32:43 Binary Coder's Weblog: Dev. Note Archives

<표 1> Ant에서 제시하는 프로젝트 구조에 대한 권고안

여기서 눈여겨봐야 할 부분은 src와 web입니다. 자바 소스는 src에, 웹 자원은 web 아래에 컨텍스트 구조대로 배치시킵니 다. 여러 IDE(Integrated Development Environment)들에서 웹 프로젝트를 구성할 때도 src와 web의 이름은 달라지기 도 하지만 기본적으로 src와 web을 나누는 기준은 같습니다. 경우에 따라서 자바 소스는 src 아래에서 다시 한번 분류를 하 기도 합니다. src/java, src/test로 소스와 테스트를 나누기도 하고 src/resources 등의 폴더를 만들어서 xml이나 properties를 보관하기도 합니다. 이 구조를 기준으로 보면 웹 프로젝트 빌드 작업은 다음과 같은 단계로 진행됩니다.

[1] 웹 컨테이너에서 웹 애플리케이션의 루트 역할을 하는 CONTEXT_ROOT 디렉토리를 만든다. 앞의 구조에서 web 디렉토리를 그대로 사용하기도 하고 dist/web, dist/<컨텍스트명>과 같이 만들기도 한다. [2] 자바 클래스를 컴파일하고 CONTEXT_ROOT/WEB-INF/classes로 복사한다. [3] 클래스패스 리소스를 CONTEXT_ROOT/WEB-INF/classes로 복사한다. [4] 필요한 라이브러리를 CONTEXT_ROOT/WEB-INF/lib로 복사한다. [5] JSP, HTML, CSS, JS 등의 웹 자원들을 CONTEXT_ROOT로 복사한다. [6] tld 파일을 CONTEXT_ROOT/WEB-INF/tld나 CONTEXT_ROOT/WEB-INF/lib로 복사한다.

여기서 CONTEXT_ROOT를 web으로 그대로 사용할 수도 있는데, 그렇다면 4~6번 과정은 필요 없거나 한번만 해도 되는 작업이 됩니다. 대신 경우에 따라 프로젝트에서 빌드한 결과물을 지우고 다시 빌드하고 싶을 때 일이 복잡해질 수 있죠. 보 통 IDE에서는 web 디렉토리를 그대로 CONTEXT_ROOT로 사용합니다.

만약 이런 일련의 빌드 과정을 수동으로 한다면 그 비효율은 말할 나위가 없겠죠? 그래서 IDE에서는 간단한 설정만 해두면 자동으로 빌드해줍니다. 하지만 IDE 없이 빌드해야 할 때도 있습니다. 개발할 때는 당연히 IDE로 개발을 하고 빌드도 할 수 있겠지만 IDE에서 지원하는 것 이외의 부가적인 작업을 해야 한다거나 개발자의 PC가 아닌 서버에서 직접 빌드하고 배 치해야 할 때도 있습니다.

이럴 때의 빌드를 자동화하기 위해 여러 가지 빌드 자동화 도구를 사용할 수 있습니다. 유닉스/리눅스 환경에서는 오래 전 부터 이런 목적을 위해 사용해 온 make라는 도구가 있습니다. Makefile이라는 파일에 빌드를 위한 스크립트들을 make 문 법에 맞게 나열해두고 make 명령을 실행하면 빌드가 실행되죠.

하지만 make에서 지원하는 기능이 너무 빈약해서 빌드가 복잡할 경우 Makefile에 모든 빌드 과정을 기술해야 하기 때문 에 Makefile이 복잡해져서 유지보수가 힘들다는 문제가 있습니다. 그래서 자바 진영에서는 make를 넘어서는 툴을 만들고 자 했고 그래서 Ant가 등장했습니다. 그러나 Ant 역시 한계가 있었기 때문에 Ant를 한 단계 더 발전시킨 Maven이 등장했 죠. 현재 대부분의 오픈소스 자바 프로젝트는 Ant나 Maven 둘 중의 하나를 빌드 도구로 사용하고 있습니다. 여기서는 구체 적인 도구의 사용법을 설명하기보다 어떠한 도구들이 있는지를 간단히 살펴보고 각 도구의 장단점을 비교해 보도록 하겠습 니다.

Ant Ant는 구조적으로는 make를 그대로 이어 받았습니다. build.xml에 빌드 설정을 해두고 ant 명령을 실행하면 빌드 작업이

http://crowe.wowdns.com:8000/archives/dev_note/ (14 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

수행됩니다. Ant는 자주 하는 작업들을 미리 자바 클래스로 코딩해서 태스크로 만들어 두었고 클래스패스 설정이나 파일, 디렉토리 선택 등을 쉽게 할 수 있는 문법을 갖추어 놓았기 때문에 make보다 훨씬 간단하게 빌드 설정을 할 수 있죠. <리 스트 1>의 build.xml의 예를 봅시다.

<리스트 1> build.xml의 예

이것은 자바 컴파일을 하고 리소스를 복사하는 빌드 파일입니다. target 태그가 작업의 단위를 정의하는 역할을 하며 다음 과 같이 target을 지정하여 실행할 수 있습니다.

ant

앞의 build.xml에서는 build, compile, resource라는 세 개의 target을 정의하고 있고 build라는 target은 compile, resource에 의존하고 있기 때문에 build target을 실행하면 자동으로 compile과 resource가 먼저 실행됩니다. 그리고 project 태그에서 default로 build를 정의하고 있기 때문에 target을 지정하지 않고 ant를 실행하면 자동으로 build가 실 http://crowe.wowdns.com:8000/archives/dev_note/ (15 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

행되죠. compile target에서는 javac 태스크를 이용해서 컴파일을 하고 resource 태스크에서는 copy 태스크를 이용해서 복사를 합니다. 이것이 Ant의 기본적인 구조이고 다른 부가적인 기능들도 있지만 대체로 이런 식으로 build.xml을 구성하 게 됩니다.

사실 Ant 개발자들은 Ant가 make보다 훨씬 선언적으로 빌드를 정의할 수 있다는 점을 장점으로 내세우지만 이건 사실 javac, copy와 같은 태스크를 미리 자바 클래스로 코딩해 놓았기 때문에 그런 것일 뿐 실제적으로는 Make의 메커니즘과 큰 차이가 없습니다. 그런 반면 build.xml을 선언적으로 작성할 수 있게 하기 위해 build.xml의 문법에 스크립트적인 요소 를 최소한으로 줄였고 또 XML 자체가 프로그래밍이 필요한 부분을 기술하기에는 적합하지 않기 때문에 Ant의 파워는 오히 려 make보다 낮아졌습니다.

그리고 실제로 자바 클래스 컴파일에 파일 몇 개 복사하는 정도라면 아주 간단하게 빌드 파일을 작성할 수 있지만 복잡해지 기 시작하면 build.xml은 점점 이해하기 힘든 코드가 되어갑니다. 그래서 Ant에서는 build.xml에 주석을 충분히 달아놓을 것을 권고하고 있습니다.

하지만 주석을 많이 달아야한다는 것은 주석을 달지 않으면 안될 만큼 지저분한 코드를 만들게 된다는 뜻이기도 합니다. 사 실 빌드 스크립트야 한번 만들어두면 계속 쓰니까 이해하건 말건 무슨 상관이냐고 할 수도 있겠지만 프로젝트 규모가 커지 고 연관 프로젝트가 많아질수록 빌드 요구사항도 계속 변합니다.

그래서 자주 수정할 수 있도록 빌드 스크립트를 명료하게 유지할 필요가 있죠. 결국 Ant는 make보다는 조금 사정이 나아 졌지만 본질적으로는 make와 동일한 문제를 갖고 있습니다.

주석과 나쁜 냄새

학교에서는 꽤 오랫동안 소스코드에는 주석을 많이 달아야 다른 사 람이 유지보수하기 쉽다고 가르쳐 왔습니다. 하지만 이것은 사실이 아닙니다. 오히려 많은 주석은 리팩토링에서 말하는 나쁜 냄새 중 하 나입니다.

마틴 파울러가 쓴 『리팩토링』에서는 나쁜 코드를 식별하는 방법으 로 냄새라는 표현을 사용합니다. 코드에 좋지 않은 부분이 있으면 ‘냄새 나는 코드’라는 거죠. 마틴 파울러는 주석은 그 자체가 나쁜 냄 새를 풍기는 것은 아니지만 보통 코드의 나쁜 냄새를 감추기 위한 탈 취제로 사용되기 때문에 주석을 써야 할 필요성을 느낀다면 주석을 쓰는 대신 코드를 리팩토링해서 코드에서 냄새가 나지 않도록 하라 고 말합니다. 좋은 코드는 주석을 많이 달아놓은 코드가 아니라 코 드 자체만으로도 쉽게 이해할 수 있는 코드라는 뜻이죠.

썬에서 제시하는 자바 코드 컨벤션(Java Code Convention)에서 도 구현에 대해 설명하는 주석은 되도록 쓰지 말라고 권고하고 있습 니다. 빌드 스크립트라고 예외가 되어선 안됩니다. 개발팀에서 개발 하고 유지하는 모든 소스코드는 냄새가 나지 않는 ‘깨끗한 코드’가 되어야 합니다.

Maven Maven은 이런 점들을 해결하고 나아가 종합적인 프로젝트 관리까지 소화하기 위해 만들어졌습니다. 그래서 기본 구조 자 체도 make, Ant와는 상당히 다릅니다. 우선 Ant의 문제점이라고 지적되던 부분, 빌드를 위한 프로세스와 데이터가 섞여

http://crowe.wowdns.com:8000/archives/dev_note/ (16 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

서 build.xml에 기술된다는 것을 해결하기 위해 데이터와 프로세스를 완전히 분리했습니다. Maven에서는 make의 Makefile, Ant의 build.xml에 대응되는 것으로 project.xml을 작성하는데 이 project.xml에는 프로세스가 전혀 들어가 지 않고 오로지 프로젝트에 대한 설명만이 들어갑니다. <리스트 2>를 봅시다.

<리스트 2> project.xml의 예

3 fireside fireside fireside 1.0 2005 /images/logo.gif ${pom.name} Dev List ${pom.name}User List jsp-api jsp-api jsp-api.jar jar servlet-api servlet-api servlet-api.jar jar src/java http://crowe.wowdns.com:8000/archives/dev_note/ (17 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

src/test **/*Test.java src/conf false

이것이 project.xml의 예입니다. 보다시피 어떻게 빌드를 할 것인가에 대한 내용은 전혀 없습니다. 프로젝트의 구조에 대 한 설명들만 있죠. 어떻게 빌드할 것인가는 Maven의 플러그인에 있습니다. 플러그인은 Jelly 스크립트라는 XML 기반의 스 크립트 언어와 자바 클래스를 결합해서 만들게 되며 goal이라는 것을 정의하는데 이것은 Ant의 target과 비슷하며 다음과 같이 실행할 수 있습니다.

maven

Maven 플러그인의 Jelly 스크립트는 Ant의 build.xml 보다 훨씬 더 프로그래밍 요소를 많이 포함하고 있기 때문에 더 강 력합니다. 게다가 데이터와 프로세스가 완전히 분리되어 있어 사용하는 입장에서는 데이터만 잘 정의해도 됩니다. 따라서 Ant보다 더 선언적이고 작성하기도 쉽습니다.

그리고 Ant는 라이브러리 의존성, 프로젝트간 의존, 상속 관계 등에 대한 지원이 전혀 없었는데 Maven에서는 Repository 라는 개념을 도입하여 이런 문제를 해결하고 있습니다. 프로젝트가 어떤 라이브러리를 사용하는지, 어떤 프로젝트의 특성 을 상속받는지 등을 project.xml에 설정만 해주면 필요한 jar 파일을 자동으로 Maven Repository에서 다운받아서 빌드 를 해주는 것이죠. 데비안 GNU/리눅스를 사용해 봤다면 dselect를 써봤을 것입니다.

dselect에서는 어떤 소프트웨어를 설치하려고 선택하면 그 소프트웨어가 필요로 하는 라이브러리나 다른 소프트웨어를 자 동으로 다운로드해서 설치해줍니다. Maven이 제공하는 의존성 관리도 이와 같은 개념입니다.

또 Maven은 프로젝트에 대한 각종 문서가 집약된 사이트를 생성하도록 해줍니다. 오픈소스 프로젝트의 홈페이지를 돌아다 니다 보면 ‘built by maven’이라는 딱지가 붙은 사이트가 많습니다. 이런 사이트에는 공통적으로 Project Info와 Project Reports가 있고 이 안에 소스 저장소 연결, Javadoc, 테스트 리포트, checkstyle 리포트 등의 문서들이 있습니다.

이것은 Maven의 site 플러그인이 자동으로 생성한 사이트입니다. 여러 가지 유용한 정보들을 자동으로 생성해주기 때문에 프로젝트에 대한 정보를 팀내에서 쉽게 공유할 수 있죠. 스케쥴러를 이용해서 매일 특정 시간에 빌드하고 사이트를 생성하 도록 하면 매일 매일 개발 진척 상황을 살펴볼 수 있습니다.

고민할 필요 없이 Maven을 쓰면 되겠구나 싶지만 아직은 아닙니다. Maven은 지원하는 기능이 많다보니 maven을 제대 로 배우는 데는 적지 않은 시간이 걸립니다. 게다가 플러그인들은 여러 가지 구조적인 부분에서 많은 ‘가정’을 포함한 상태 로 개발되어 있습니다. 이를테면 Maven으로 빌드하는 jar 파일은 항상 버전 넘버가 뒤에 붙어야 한다는 식이죠.

이런 암묵적인 룰들이 많기 때문에 Maven 플러그인을 확장하고 싶을 때 자신의 생각했던 것과 다르게 동작하는 경우가 많 습니다. 게다가 Maven에서 사용하는 스크립트 언어인 Jelly 스크립트는 Ant보다는 낫지만 여전히 복잡한 요구사항을 수용 http://crowe.wowdns.com:8000/archives/dev_note/ (18 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

하기에는 ‘진짜 프로그래밍 언어’에 비해 부족한 점이 많습니다. 결국 Jelly 스크립트로 작성한 코드는 Ant의 build.xml 못 지 않게 어지러운 코드가 되곤 하죠. 게다가 Maven은 속도가 Ant에 비해 아주 느린데 이 점이 의외로 개발하면서 자주 빌 드해야 할 때는 치명적인 문제가 될 수 있습니다.

선택의 문제 이런 점들 때문에 Maven을 무작정 권고하기는 어렵습니다. 그래서 또 다른 대안을 찾는 사람들도 있습니다. Groovy+Ant 라는 것도 있습니다. Groovy라는 자바 기반의 스크립트 언어에서 Ant의 태스크들을 쉽게 사용할 수 있게 구성해 놓은 것이 죠. 나름대로 괜찮은 대안이지만 아직 Maven의 다양한 기능들을 소화하고 있진 못합니다. 결국 선택은 개발자의 몫입니 다. Ant는 한계가 있지만 쉽고 가벼운 툴이고, Maven은 기능이 다양하지만 어렵고 약간의 문제가 있습니다.

일단은 Ant로 구성해 놓고 Maven을 개선한 새로운 툴이 나오기를 기다리는 것도 나쁘지 않을 것입니다. 그리고 사실 빌드 도구 만드는 것이 어려운 일은 아니므로 스스로 자신의 팀에 맞는 빌드 도구를 만들어보는 것도 좋습니다. 목적은 위에서 나 열한 웹 프로젝트의 빌드 과정을 효과적으로 해내는 것이지 어떤 도구를 사용하느냐가 중요한 것은 아닙니다.

관리의 자동화 자동화의 대상은 프로젝트의 빌드만이 아닙니다. 그 외에 개발에서 발생하는 수많은 잡무들도 모두 자동화 대상입니다. 다 음이 그런 작업들입니다.

[1] 서버 설치 및 서버 환경 구성 [2] 빌드한 프로젝트를 웹 컨테이너에 배치하기 [3] 웹 컨테이너를 스탑/스타트/리스타트 시키기 [4] 주기적으로 프로젝트를 테스트하고 결과를 리포팅하기 [5] 상황에 따라 서버의 셋팅 변경하기

Ant와 Maven은 이런 일에도 상당 부분 도움을 줍니다. 이미 이런 작업들이 Ant 태스크나 Maven 플러그인으로 많이 만들 어져 있기도 하고 또한 빌드 자동화와 일관된 방식으로 문제를 해결할 수 있다는 것도 장점입니다. 하지만 사실 이런 부분들 은 빌드 툴보다는 bash 같은 셸 스크립트가 더 간편한 경우가 많습니다. 서버에 접속해야 하는 일들도 많고 Ant나 Maven 의 태스크보다 리눅스/유닉스의 툴들이 더 유용한 경우가 많습니다. 리눅스/유닉스 세계에서는 이미 오래 전부터 이런 일들 을 쉘 스크립트로 해왔습니다.

하지만 여기에도 문제가 있는 것이 셸 스크립트 역시 Ant나 Maven의 Jelly 스크립트처럼 완전한 프로그래밍 언어가 아니 기 때문에 파워도 부족하고 문법도 좀 덜 친숙합니다. 그래서 이런 경우는 펄(Perl)이나 파이썬 등의 스크립트 언어를 사용 하기도 합니다. 스크립트 언어는 셸 명령을 셸 스크립트에 비해 큰 불편 없이 실행시킬 수 있고 프로그래밍 언어로서의 파워 도 갖고 있다는 장점이 있는 반면 배워야 할 언어가 하나 더 늘고 이런 류의 작업을 위해 이미 만들어진 것들이 많지 않다는 문제가 있습니다. 결국 또다시 선택의 문제로 귀결됩니다.

참고로 필자의 선택은 간단한 경우는 셸 스크립트, 좀 복잡해지면 파이썬입니다. 중요한 것은 잡무를 모두 자동화해서 프로 그래머가 개발한 것을 쉽게 테스트하고 또 실제 서비스에 쉽게 반영할 수 있게 하는 것입니다. 필요하다면 어떤 도구라도 사 용할 수 있다는 유연한 사고방식이 필요합니다.

게으를 수 있는 권리 “인간은 누구나 게으를 권리가 있다.” 폴 라파르크의 『게으를 수 있는 권리』에 나오는 말입니다. 이 책에서는 일의 노예 가 되어가는 현대인들에게 삶의 목적에 대해 다시 한 번 생각해볼 것을 요구하며 또한 무작정 부지런하기만 한 것이 효율적 이지도 않다고 말합니다.

프로그래머에게도 이 말은 적용됩니다. 주어진 일은 무조건 열심히 하고 보자는 생각에 낡은 방식대로 묵묵히 지겨운 일을 해내는 것은 개인에도 조직에도 바람직하지 않습니다. 이건 조직의 입장에서도 생산성 향상을 할 수 있는 기회를 놓치기 하 기 때문에 또다른 의미의 태업입니다. 프로그래머는 귀찮은 일에 대해 게을러져야 더 나은 방법을 찾아낼 수 있습니다. 지퍼 의 발명도 구두끈을 매기 귀찮아하는 사람이 해낸 것이고 많은 발명들이 게으름의 소산입니다.

프로그래머에게 이런 게으름은 이미 권리를 넘어서 의무입니다.

물론 게으름만으로는 충분하지 않습니다. 마냥 게을러지기만 해서는 귀찮은 일을 하지 않게 될 뿐이고 더 나은 방식으로 하 게 하진 못합니다. 결국 할 일을 하면서 좀 더 게을러지기 위해서는 귀찮음을 해결하는 과정에서의 부지런함은 필요합니다. 구두끈을 매기 귀찮아 하는 사람이 지퍼를 발명하는 수고를 해야 했듯이 말이죠. 그리고 기존의 방식을 개선해야 하기 때문 에 어느 정도의 창의성도 있어야 합니다.

‘우리는 이제까지 잘해왔다’는 논리에 빠지는 것 또한 경계 대상입니다. 한번 성공을 거둔 경험이 있는 조직은 자신들의 방 식이 성공을 가져왔기 때문에 쉽게 매너리즘에 빠집니다. 하지만 IT 세계는 빠르게 발전하고 있고 예전에는 효율적이었던 http://crowe.wowdns.com:8000/archives/dev_note/ (19 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

방식이 더 효율적인 기술들이 나옴에 따라 오히려 발목을 붙잡고 있는 경우도 많다는 사실을 기억해야 합니다.

그럼 이런 자동화로 절약한 시간들은 어떻게 써야 할까요? 물론 더 게을러지기 위해서 한 일들이지만 그렇다고 정말로 게을 러져서는 곤란합니다. 프로그래머의 게을러질 권리는 단순한 반복 작업에만 해당되는 것입니다. 좋은 개발 환경으로 인해 절약한 시간들은 좀 더 창조적인 일을 하는데 재투자해야 합니다. 프로그래밍 자체에 집중하는 것도 좋고 좀 더 높은 수준 의 자동화에 도전해보는 것도 좋습니다. 개발 환경 구축 과정에서 경험하게 된 것들을 공유해보는 것도 여러 사람에게 유익 한 결과가 될 것입니다.

학창 시절 배웠던 러다이트 운동(Luddite Movement)을 기억할 것입니다. 산업혁명 이후 기계가 인간이 할 일을 대체하 기 시작하면서 많은 사람들이 일자리를 잃고 임금도 급락했습니다. 그래서 노동자들이 들고 일어나서 기계를 파괴하고 다니 는 비밀 조직을 결성한 것이 바로 러다이트 운동입니다.

프로그래머에게도 이런 일이 닥치지 말란 법이 없습니다. 기계가 할 수 있는 일은 기계에 맡기고 좀 더 창조적인 일을 할 줄 알아야 기계에게 일자리를 빼앗기지 않겠죠. Ant라는 이름에도 이런 생각이 담겨 있습니다. 모 광고 카피처럼 단순 반복 작 업은 부지런한 개미에게 맡기고 당신은 프로그래밍을 즐기라는 뜻인 거죠.

사실 자신의 잡무를 스스로 자동화할 수 있는 사람은 프로그래머 밖에 없습니다. 이것이 프로그래머라는 직업의 매력이기 도 하죠. 하루 일과에서 가장 많은 시간을 일하면서 보내는데 이 일이 즐거워야 하지 않겠습니까. 즐거운 프로그래밍을 하 는 첫걸음은 귀찮고 일을 제거하는 것입니다. 귀찮은 것들일랑 모두 컴퓨터에 맡기고 우리는 프로그래밍을 즐겨 봅시다.@

* 이 기사는 ZDNet Korea의 제휴매체인 마이크로소프트웨어에 게재된 내용입니다.

Posted by Crowe Lee at 07:42 PM | Comments (0) | TrackBack

세련된 자바 웹 프로그래머 되기 | 패턴·프레임워크·XP

View: ZDNet Korea | [세련된 자바 웹 프로그래머 되기] 패턴·프레임워크·XP

이제 기초를 익히고 스스로 개발 환경을 갖출 줄도 알게 되었으니 어디 가서 프로그래머라고 말할 수 있게 되었습니다. 다음은 사람들을 이 끌고 나갈 수 있는 능력을 갖추는 것입니다.

패턴과 프레임워크, 다소 어려워 보일 수 있는 주제이지만 프로그래머로써 한 단계 거듭나기 위해 거쳐야 할 과제입니다. 이제까지 기본기 로만 싸워왔다면 이제부터는 좀 고난이도의 기술과 새로운 무기들을 써 봅시다. 지금부터가 정말 즐거운 프로그래밍의 세계로 들어가는 길 입니다.

탈무드에는 배고픈 자식에게 고기를 잡아주기보다 고기를 잡는 방법을 가르쳐주라는 말이 있습니다. 좋은 프로그램을 고기라고 본다면 좋 은 프로그램을 만들어주는 것보다 좋은 프로그램을 만드는 방법을 가르쳐야 한다는 말이 되겠죠. 하지만 그러기엔 마소 주니어의 지면은 넉넉지 않습니다. 사실 초급자는 잡지에서 새로운 것을 익히려 하기보다 입문서에서 배워야 합니다.

그렇다면 이 한정된 지면에서 할 수 있는 최선은 자세한 내용을 기술하는 것이 아니라 무엇을 공부해야 하는지, 어디서 정보를 얻을 수 있 는지, 요즘 추세는 어떤지를 알려주는 것이겠지요. 말하자면 좋은 프로그램이 아니라 그것을 만드는 방법을 고기로 보고 이것을 잡는 방법 을 제시하겠다는 뜻입니다.

필자의 짧은 세 번의 연재에는 모두 이런 생각이 담겨 있고 그래서 기술적인 상세 내용 설명보다는 무엇을 공부해야 하는가에 대한 내용에 치중했습니다. 이번 글 역시 디자인 패턴, 프레임워크, XP 세 가지를 다루고 있지만 각각의 상세한 설명보다는 이런 개념들을 어떻게 이해 하고 활용해야 하는가에 초점을 맞출 것입니다.

디자인 패턴과 프레임워크는 먼저 용어에 대한 설명부터 시작합니다. 그리고 그것들의 목적과 가치에 대해 살펴보고 몇 가지 예를 살펴볼 것입니다. XP는 제목에 포함시키긴 했지만 이것은 디자인 패턴과 프레임워크를 XP의 시각에서 바라본다는 의미이고 개별적인 주제로는 다 루지 않습니다.

용어로서의 디자인 패턴 디자인 패턴이란 프로그래밍에서 발생하는 여러 가지 문제 영역에 대한 해결 방법들을 모아서 정리해놓은 것입니다. GoF(Gang of Four) 가 여러 가지 패턴들을 모아 『디자인 패턴』이라는 책을 내면서부터 디자인 패턴이라는 용어가 일반화되기 시작했습니다. GoF의 디자인

http://crowe.wowdns.com:8000/archives/dev_note/ (20 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

패턴 중 하나를 예로 들면 데코레이터(Decorator)라는 패턴이 있습니다. 랩퍼(Wrapper)라는 말로도 쓰이는데 이것은 어떤 객체에 기본 적인 동작은 유지하면서 부가적인 기능을 넣고 싶을 때 사용합니다.

이 패턴의 간단한 예로 JDBC에서 수행한 쿼리를 로그로 남기고 싶을 때 로깅을 위한 데코레이터를 사용할 수 있습니다. 보통 JDBC에서는 Connection 객체를 이용해서 Statement 객체를 생성하고 이것을 이용해서 쿼리를 수행하게 되는데 이 때 Statement 대신 LoggableStatement를 사용하는 것입니다.

LoggableStatement는 Statement를 상속하며 생성자에서 Connection 객체가 만든 Statement 객체를 받아서 멤버로 갖고 있습니다. 그리고 쿼리 요청이 오면 먼저 로깅을 하고 실제 쿼리는 생성자에서 받은 Statement 객체에 위임합니다. 이런 방식으로 Statement의 동 작은 그대로 유지하면서 로그를 남기는 기능을 추가할 수 있습니다.

이런 것이 디자인 패턴입니다. 수학 공식처럼 같은 종류의 문제들에 대해 이미 정리된 해결책을 이용함으로써 매번 같은 문제를 해결하는 데 중복으로 투자되는 노력을 줄이려는 것이죠.

하지만 디자인 패턴을 그대로 사용하는 것이 좋은 것만은 아닙니다. 오용의 위험성이 크기 때문입니다. 사실 어떤 문제 상황에 어떤 패턴 을 적용하면 되는가를 판단하기란 쉬운 일이 아니기 때문에 필요하지도 않은 복잡한 패턴을 도입하는 경우가 많습니다. 그 때문에 간단하 게 해결될 문제가 더 복잡해지기도 하죠. 그래서 안티 패턴 같은 이야기도 나오고 있습니다. XP(eXtreme Programming)에서도 디자인 패턴을 의식하지 말라고 합니다.

그보다 원하는대로 동작할 수 있는 가장 간단한 방법으로 코딩을 하다가 중복이 생기거나 코드가 지저분해지면 리팩토링을 합니다. 그러면 서 자연스럽게 코드가 디자인 패턴의 모습을 갖추어 나가는 것이 바람직합니다.

이처럼 디자인 패턴의 원래 목적은 문제 해결 시간을 단축시키는 것이었지만 문제 영역과 패턴을 정확히 매칭시키기 어렵고 Over- Engineering이 나오는 경우가 많기 때문에 실제 개발에서는 디자인 패턴을 직접적으로 사용하지 않는 것이 좋습니다.

디자인 패턴의 가치는 그보다도 의사소통 비용을 줄여줄 수 있다는 데 있습니다. 서로가 패턴에 대해 잘 알고 있다면 복잡한 설명 없이도 아주 간단하게 의사소통이 이루어질 수 있죠. 이를테면 이 예를 설명하기 위해 복잡하게 LoggableStatement의 구조를 설명하는 것 대신 에 ‘이 LoggableStatement 클래스는 Statement의 데코레이터로 로깅을 추가로 하게 되어 있어’라고 말할 수 있다는 것입니다. 즉 패턴 은 해결책으로서보다 의사소통을 위한 용어로서 활용하는 거라고 생각하는 것이 좋습니다.

자바빈즈의 해악 패턴 오용의 한 예로 자바빈즈를 들 수 있습니다. ‘해악’이라는 표현까지 써가면서 자바빈즈를 언급한 것에 대해 놀라는 사람도 많을 것입니 다. 자바를 배우면서 보통 자바빈즈의 장점에 대한 이야기를 많이 듣게 되니까요. 하지만 점점 자바빈즈를 해로운 것으로 인식하는 사람들 이 많아지고 있습니다. 먼저 자바빈즈가 무엇인지부터 다시 살펴봅시다.

자바빈즈는 원래 컴포넌트 아키텍처를 자바에서 구현하기 위해 도입된 기술입니다. 객체의 프로퍼티의 조작 메커니즘을 제공하여 재사용성 이 높고 유연한 컴포넌트를 만드는 것이 목적이죠. 자바빈즈의 특징으로 언급되는 것이 여러 가지가 있지만 다른 자바 객체와 구별되는 특 징은 프로퍼티 관리 방식입니다. 프로퍼티로 private 필드를 두고 그 필드에 대한 public getter/setter를 만들어서 이를 이용해서 프로퍼 티에 접근을 합니다.

사실 이것은 일반적인 필드 인캡슐레이션(field encapsulation)과 크게 다르지 않습니다. 다른 점은 이 프로퍼티의 이름과 getter/setter 의 이름에 네이밍 룰을 부여해서 빈즈 API를 통해서 프로퍼티의 이름으로 이 프로퍼티를 읽거나 쓸 수 있다는 점입니다.

예를 들어 name이라는 프로퍼티가 있다면 getName, setName이라는 이름으로 getter/setter를 만들고 빈즈 API에서는 name이라는 이름으로 프로퍼티에 접근할 수 있습니다. 즉, 일반적인 필드 인캡슐레이션을 하면 프로퍼티에 접근하기 위해 메쏘드명을 코드에 직접 써 야 하지만 빈즈 규약에 맞게 getter/setter를 만들고 빈즈 API를 이용하면 String으로 주어진 프로퍼티에 접근하는 것이 가능해진다는 것 입니다.

이 점을 이용하면 복잡한 애플리케이션에서 자바빈즈 객체의 프로퍼티를 동적으로 쉽게 조작할 수 있습니다. 그래서 정보 은폐 (information hiding, data hiding)와 동적인 프로퍼티 접근의 두 마리 토끼를 동시에 잡는 것이 자바빈즈의 목적입니다.

그러면 무엇이 문제일까요? 우선 첫 번째 목적인 정보 은폐가 제대로 되지 않습니다. 사실 필드 인캡슐레이션은 정보 은폐를 위한 것이라 고 하지만 이것은 허구에 불과합니다. 실질적으로 프로퍼티에 대한 접근성은 public field와 private field, public getter/setter가 완전 히 동일합니다. 똑같이 읽고 쓸 수 있는데 뭐가 다르겠습니까. 단지 좀 더 조심스럽게 쓰지 않을까라고 추측하는 것뿐입니다. 그래서 자바 빈즈를 사용할 때 꼭 필요한 경우가 아니라면 setter는 만들지 않기를 권하기도 합니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (21 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

하지만 일반적인 필드 인캡슐레이션이라면 setter를 만들지 말라는 조언을 할 수 있겠지만 자바빈즈는 그 목적이 프로퍼티를 동적으로 조 작하는 것이므로 컴포넌트로서의 역할을 제대로 하려면 setter도 당연히 필요합니다. 결국 필드 인캡슐레이션으로 인해 코드는 길어졌지 만 정보 은폐는 달성하지 못했으므로 실질적인 이득이 없습니다.

또 다른 문제는 자바빈즈의 한계에 관한 것입니다. 동적으로 프로퍼티의 내용에 접근할 수는 있지만 프로퍼티를 추가하거나 없애는 정도 의 유연성은 소화할 수 없습니다. 많은 경우 자바빈즈를 데이터 전송 객체(Data Transfer Object)로 사용하는데 이런 경우는 프로퍼티의 내용 뿐 아니라 프로퍼티 종류 자체가 자주 변합니다. 그런데 자바빈즈로 만들게 되면 프로퍼티가 늘어날 때마다 필드와 메쏘드를 추가해 야 합니다. 그리고 데이터의 종류가 달라질 때마다 새로운 클래스를 만들어야 하구요. 자바 컬렉션에 Map이 있는데 이런 비효율을 감당해 야 할 이유는 별로 없습니다.

Map에서는 프로퍼티에 동적으로 접근하는 것은 물론이고 프로퍼티가 자유롭게 늘어나거나 줄어들 수 있습니다. 게다가 빈즈 API는 String으로 주어진 프로퍼티명에 대한 getter/setter를 호출하기 위해 내부적으로 Introspection이라는 기술을 사용하는데 이것은 자바 의 Reflection API를 이용합니다. 때문에 성능상에서도 해싱(Hashing)을 이용하는 HashMap에 비해 나은 것이 없습니다.

그래서 자바빈즈의 단점에 대해 많은 사람들이 공감하기 시작했고 최근에 등장하는 각종 프레임워크들도 이런 경향을 반영하고 있습니다. 초기의 프레임워크들은 자바빈즈를 활용하는 기능들을 제공했으나 최신 프레임워크들은 자바의 컬렉션을 적극적으로 활용하고 있습니다.

일반적으로 데이터 전송 객체의 경우는 Map을 그냥 사용하는 것이 좋고 그 외에 복잡한 로직을 수행하는 객체인데 프로퍼티를 많이 사용 해야 한다면 Map을 데코레이터로 사용하면 편리합니다. 그리고 일반적으로 클래스를 설계할 때 setter는 되도록 만들지 않는 것이 좋습니 다.

프레임워크의 가치 프레임워크(framework)란 단어는 여러 가지 의미로 사용되지만 보통은 어떤 영역의 API들을 사용하기 편리한 형태로 포장해놓은 것을 말 합니다. 이를테면 자바 컬렉션 프레임워크는 자바에서 각종 컬렉션을 사용하기 쉽게 모아놓은 것이고 자카르타 프로젝트의 BSF()는 자바 애플리케이션에서 자바빈즈나 스크립팅을 하기 쉽게 만든 API의 집합입니다. 그리고 지금 논의하려는 웹 애플리케이션 프레임워크는 웹에서 서블릿 API들을 좀 더 편리하고 객체지향적으로 사용할 수 있게 해주는 것입니다.

현재 웹 애플리케이션 프레임워크의 주류는 단연 스트럿츠(struts)입니다. 전 세계 자바 웹 개발자의 60%가 스트럿츠를 사용하고 있다고 하죠. 하지만 스트럿츠가 있음에도 계속해서 새로운 프레임워크가 등장하고 있습니다. 현재까지 공개된 프레임워크의 숫자는 이미 60개를 넘습니다. 스트럿츠에는 어떤 부족함이 있길래, 그리고 다른 프레임워크들은 뭐가 부족하길래 개발자들이 새로운 프레임워크를 계속 만들 까요.

프레임워크의 정의

스트럭처(structure), 아키텍처(architecture), 프레임워크 (framework)란 용어는 기술과 시대가 변하면서 조금씩 그 의미를 달리해 가는 것 같습니다. 스트럭처라는 용어가 Tree와 같은 계층적 인 탄탄한 기반 구조를 말하는 반면, 프레임워크는 다소 수평적인 의 미를 가지는 하부 구조를 나타냅니다. 또한 아키텍처라는 것은 이 두 부분을 모두 포함하는 더 포괄적인 개념 체계적인 기반 구조를 나 타냅니다.

그러나 프레임워크이란 용어는 스트럭처나 아키텍처보다 더 낮은 레 벨의 의미를 지닙니다. 즉, 프레임워크의 실체는 때론 API의 집합으 로 나타나기도 한다는 것이지요. 썬 마이크로시스템즈의 Java Activation Framework가 그러하고 Java Media Framework가 그 러합니다. 그러나 최근에 와서 IBM의 샌프란시스코 프레임워크 (San Francisco Framework)라는 용어가 등장하면서 ‘반제품’의

http://crowe.wowdns.com:8000/archives/dev_note/ (22 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

의미를 강하게 띄는 것 같습니다.

알다시피 샌프란시스코 프레임워크는 정형화된 업무를 위한 비즈니 스 컴포넌트를 미리 만들어 두고, 이를 조립함으로써 생산성을 극대 화시키자는 것이 요지입니다. 어쨌거나 현재의 프레임워크란 것은 ‘기반 틀 구조’라는 모호한 추상적인 개념이기보다는 물리적인 실체 이면서 반제품 성격의 구체적이고 체계화된 API를 제공하는 개념이 라고 봐야 할 것입니다. - javaservice.net 이원영님의 글

프레임워크의 가치는 기술적인 영역에서 소모적이고 반복적인 코딩을 프레임워크가 소화하여 개발자가 비즈니스 로직에만 전념할 수 있도록 해주는 데 있습니다. 이를테면 웹 프로그래밍을 한다면 HTTP 프로토콜도 어느 정도 알아야 하고 서블릿 API를 익히고 쿠키도 쓸 줄 알아야 합니다.

하지만 JSF(Java Server Faces) 같은 프레임워크를 이용하면 일반 자바 GUI 프로그래밍을 하듯이 프로그래밍할 수 있습 니다. 서블릿 API를 몰라도 웹 애플리케이션을 잘 만들 수 있는 것이죠. 로깅(logging) 프레임워크가 없다면 개발자가 로그 를 남기고 싶을 때 직접 파일을 열어서 파일에 내용을 쓰는 등의 복잡한 코드를 써나가야 하지만 log4j와 같은 프레임워크 를 쓰면 단순히 Logger 객체에 로그 메시지를 넘겨주는 것으로 로깅을 할 수 있습니다.

EJB도 이런 프레임워크의 일종입니다. EJB가 없다면 분산 트랜잭션이 생길 때마다 개발자가 직접 분산 트랜잭션 관리를 해 야 하지만 EJB 엔진이 이를 대신해주면 개발자는 비즈니스 로직의 구현에만 집중할 수 있죠. 이처럼 기술적인 영역이 달라 질 때마다 개발자는 새로운 API를 익히고 또 소모적이고 반복적인 코드를 써야 하는데 해당 분야에 좋은 프레임워크가 있으 면 중복 코드도 쉽게 제거할 수 있고 기술적인 세부사항에 신경쓰지 않아도 됩니다.

결국 프레임워크의 가치는 개발자의 할 일을 줄여주는 데 있는 것입니다.

하지만 간혹 프레임워크의 가치를 유지보수의 용이함에서 찾는 경우가 있습니다. 프레임워크가 꽉 짜놓은 틀 안에서만 코딩 을 하면 개발자들이 모두 일관된 방식으로 개발하기 때문에 다른 사람이 작성한 코드를 파악하기 쉬워서 유지보수하기도 쉽 다는 것이죠.

그러나 이렇게 일관성을 필요 이상으로 강조하면 때때로 문제가 될 수 있습니다. PEAA(Patterns of Enterprise Application Architecture)에서는 기업용 애플리케이션(Enterprise Application)에는 모두 각각의 어려움을 가지고 있는 데 이 어려움은 모두 종류가 다르기 때문에 이 모든 어려움을 해결하는 하나의 아키텍처는 불가능하다고 말합니다. 다른 문 제 영역에는 다른 해결책을 써야한다는 것이죠. 그런데 프레임워크를 통해서 지나치게 틀을 고정시켜 놓게 되면 그 틀에서 수용할 수 없는 문제를 만났을 때 아주 비효율적인 코딩을 하게 됩니다. 물론 이것은 프레임워크 자체가 가져오는 직접적인 문제는 아닙니다.

그보다도 프레임워크의 장점을 ‘일관성’에서 찾는 사람들이 일반적으로 범하는 오류입니다. 일관성의 가치를 지나치게 높 게 평가한 나머지 코딩 패턴에 너무 많은 구속을 하게 되고 그 결과로 문제 영역에 맞는 패턴을 사용하는 것이 아니라 패턴 에 코드를 끼워 맞추는 코드를 만들게 됩니다. 그래서 결과적으로 프레임워크를 유연하게 활용하지 못하고 오히려 코드가 더 길고 복잡해지는 경우가 많습니다. 앞서 언급한 디자인 패턴의 오용과 같은 것이죠.

사실 일관성의 문제는 프레임워크보다도 컨벤션을 통해 해결해야 하는 것입니다. XP에서는 코드에 대한 소유권을 개발자 한 명이 아니라 모두가 갖게 되는 코드 공동 소유(collective code ownership)를 권장합니다. 그래서 다른 사람의 코드를 읽고 수정하는 일은 아주 일상적인 일입니다. 이런 상황에서 개발자 각각의 개성이 강해서 서로의 코드를 읽기 어렵다면 문 제가 크겠죠.

그럼에도 불구하고 XP는 프레임워크에 대해 약간의 부정적인 입장을 취하고 있습니다. 이것은 프레임워크가 디자인 패턴 과 같은 문제를 발생시킬 수 있다고 보기 때문이기도 하지만 고정적인 패턴에 따라 코딩된 코드보다도 코드 컨벤션이 통일 되고 리팩토링이 잘된 코드가 더 읽기 쉽다고 보기 때문입니다. 실질적으로 코딩 패턴의 강제를 통해 얻을 수 있는 것은 그 리 많지 않습니다. 어차피 이해하기 가장 어려운 부분은 비즈니스 로직인데 이 부분까지 패턴이나 프레임워크로 강제할 수 는 없기 때문입니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (23 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

결국 아무리 다양한 기능을 제공한다 해도 결과적으로 개발자가 작성해야 하는 코드를 줄여주지 못한다면 좋은 프레임워크 라고 할 수 없습니다. 스트럿츠에 대한 대안이 계속 쏟아져 나오고 있는 것도 실제적으로 스트럿츠가 개발자의 일을 많이 줄 여주지 못하기 때문입니다.

스트럿츠의 강점으로 컨트롤러(controller)를 꼽는데 사실 스트럿츠의 컨트롤러는 HttpServlet의 역할을 Action으로, web.xml의 역할을 struts-config.xml로 옮겨온 것 밖에 없습니다. 사실 스트럿츠의 컨트롤러 기능 자체는 서블릿 API의 래퍼 몇 개로 완전히 대체할 수 있습니다. 이런 점을 깨달은 개발자들이 정말로 개발자의 일을 줄여주는 것을 목표로 다양 한 프레임워크를 쏟아내고 있는 것입니다.

프레임워크의 구성 요소 실제로 어떤 프레임워크를 사용하든 그대로 사용하는 경우는 그다지 많지 않습니다. 다른 프레임워크와 결합해서 쓰기도 하 고 상속을 통해서 확장하거나 혹은 직접 소스코드를 고쳐서 사용하기도 합니다. 어차피 모든 영역에 맞는 프레임워크는 없 기 때문에 각자의 환경에 맞게 커스터마이징하는 것은 바람직한 일입니다. 그래서 이번에는 프레임워크를 구성하는 요소들 에 대해 살펴보겠습니다.

제어 구조의 반전 요즘 나오는 프레임워크들은 공통적으로 제어 구조의 반전(IoC, Inversion of Control)를 내세우고 있습니다. 제어 구조 의 반전이라는 말의 의미는 개발자가 제어하던 것을 프레임워크로 옮겨서 프레임워크에서 제어한다는 것입니다.

예를 들면 GUI로 입력을 받는 프로그램을 개발자가 직접 모든 부분을 만든다면 입력 항목을 출력하고 사용자의 입력을 기 다린 다음 사용자의 입력을 판단해서 로직을 실행하는 코드를 모두 순차적으로 쓰게 됩니다. 그런데 이걸 GUI 프레임워크 를 이용하면 화면과 입력에 관한 부분은 프레임워크가 처리하고 사용자는 단지 사용자가 입력했을 때 반응하는 이벤트 핸들 러만 작성합니다. 즉 입력 제어가 개발자에서 프레임워크로 이동하는 것입니다.

윈도우 프로그래밍에서 콜백(callback)이라고 부르는 것도 비슷한 개념입니다. 사실 서블릿 자체도 일종의 프레임워크이 고 이미 IoC가 일어나고 있습니다. HTTP 요청을 대기하고 받아들여서 서블릿을 실행하는 코드를 개발자가 작성하는 것이 아니라 서블릿 엔진이 제공하고 개발자는 단지 서블릿만을 작성하게 되죠. 결국 이런 컨트롤의 반전을 통해서 중복 코드를 효과적으로 제거할 수 있습니다. 프레임워크의 목적이 소모적이고 반복적인 코드를 제거하는 것인 만큼 대부분의 프레임워 크에는 프레임워크 개발자가 의도했든 아니든 IoC가 사용되고 있습니다.

하지만 IoC 역시 주의해서 사용해야 합니다. 일상적인 프로그램과는 달리 제어 구조가 거꾸로 되기 때문에 디버깅하기도 어 렵고 컨트롤이 반전된 부분에 대한 선행 지식이 없으면 이해하기 어려운 코드가 됩니다. 그래서 IoC는 꼭 필요한 경우가 아 니면 사용하지 않는 것이 좋습니다.

MVC 패턴 요즘 프레임워크가 IoC를 강조한다면 스트럿츠처럼 좀 오래된 프레임워크들은 MVC(Model-View-Controller)를 내세웠습 니다. UI가 포함된 프레임워크에는 거의 필수적으로 사용되는 패턴입니다. 원래 MVC는 웹보다도 일반 GUI 애플리케이션 을 개발할 때 UI(User Interface)와 비즈니스 로직을 효과적으로 분리하기 위해서 고안되었습니다. 현재는 웹에서도 그 효 과가 입증되었기 때문에 널리 쓰이고 있습니다.

MVC의 구조는 MFC 프로그램에서 등장했던 Document-View 구조를 한 차원 더 발전시킨 것으로 프로그램의 구성 요소 를 모델, 뷰, 컨트롤러로 나누어서 각각 다른 역할을 맡게 하는 것입니다. 모델은 비즈니스 로직을 담는 객체를 말하며 이 모델은 PEAA에서 말하는 도메인 모델이 됩니다.

<그림 1>MVC 패턴의 구조

데이터베이스에 접근하는 것도 모델 객체의 몫입니다. 모델 객체들 자체로 UML 클래스 다이어그램을 그린다면 그 자체로 비즈니스 로직의 표현이 될 수 있습니다. 뷰는 화면 UI를 구성하는 요소이며 일반적으로 웹에서는 JSP가 뷰의 역할을 맡습 http://crowe.wowdns.com:8000/archives/dev_note/ (24 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

니다.

그리고 컨트롤러는 사용자의 요청을 받아서 모델 객체를 실행하고 그 결과를 뷰로 전달하는 역할을 맡게 되는데 일반적으 로 이 부분은 프레임워크에서 담당하며 개발자는 어떤 모델을 실행하고 어떤 뷰를 선택할 것인지를 컨트롤러에 알려주기만 하면 됩니다. 이런 구조가 이상적인 MVC 패턴의 구조입니다.

MVC 패턴의 가장 큰 장점은 모델과 뷰의 분리를 통해 화면 UI를 위한 코드와 비즈니스 로직을 위한 코드가 섞이지 않는다 는 것입니다. 그래서 때때로 화면 UI 개발자와 비즈니스 로직 개발자를 따로 두는 것도 가능합니다. 그리고 이 패턴을 통해 프로그램 디자인의 기본 원칙인 low coupling, high cohesion을 자연스럽게 달성할 수 있죠.

무언가 변경하고 싶을 때 여러 컴포넌트들을 같이 변경해야 하는 coupling은 줄이면서 컴포넌트간의 협력(cohesion)은 컨 트롤러를 통해 자유롭게 할 수 있는 것입니다. 또한 모델과 뷰가 분리되면 한 모델에 여러 가지 다양한 뷰를 붙이는 것도 가 능하고 그 반대의 경우도 가능합니다. 그래서 요구사항의 복잡도는 높지만 규격이 잘 정해진 애플리케이션을 만들 때는 개 발자의 일을 획기적으로 줄일 수 있습니다. MVC 프레임워크란 결국 이런 모델과 뷰의 분리를 효과적으로 할 수 있는 컨트 롤러를 제공한다는 데에 그 가치가 있습니다.

XML 설정 파일의 역할 프레임워크에서 또 하나 중요한 것은 설정 파일의 활용입니다. 유행처럼 번져나가기 시작한 XML 설정 파일은 요즘 거의 프 레임워크의 필수사항처럼 되어가고 있습니다. 초기에 설정 파일이 등장하기 시작한 이유는 소스의 내용 중 상수에 해당하 는 값들을 설정 파일로 빼 놓으면 컴파일을 다시 하지 않아도 동작을 변경할 수 있기 때문입니다. C++ 등의 언어에서는 컴 파일 후에 링크까지 해야 하기 때문에 이 차이는 적지 않습니다.

하지만 자바에서는 필요한 부분만 간단하게 컴파일하면 바로 동작하게 할 수 있고 XP의 지속적인 통합(Continuous Integration)이 일반화되면서 소스를 변경하는 비용이 크게 줄어들었죠. 그래서 사실 지금은 XML 설정 파일에 상수를 넣 어두는 것이 자바 코드에 넣는 것보다 개발시의 변경 비용이 적다고 하기 힘듭니다.

하지만 여전히 애플리케이션 사용자의 입장에서는 XML이 훨씬 변경하기 쉽습니다. 클라이언트로 배포된 프로그램 혹은 서 버에 배치된 프로그램의 설정을 바꾸기 위해 다시 빌드를 할 수는 없는 일이니까요.

하지만 이런 비용적인 측면 외에도 XML 설정 파일은 자바 코드보다 좀 더 정보를 서술적(descriptive)으로 담아놓을 수 있 다는 장점이 있습니다.

XML은 구조적인 정보와 메타 정보를 포함한 모든 정보를 한 눈에 알아보기 쉽게 정리할 수 있습니다. 또한 자바 코드에서 여러 클래스로 분산될 수 있는 설정 내용을 개발자가 관리하기 편한 단위로 손쉽게 통합해서 관리할 수 있죠.

자바의 프로퍼티도 설정 파일 용도로 많이 사용되어 왔습니다만 구조적인 정보를 제대로 정의하기 어렵기 때문에 설정 파일 로는 부적합합니다. 게다가 프로퍼티는 한글 문제도 있기 때문에 앞으로 프로퍼티는 쓰지 않는 것이 좋다고 봅니다.

뭐든지 잘못 쓰면 해가 된다는 점에서 XML 역시 예외가 아닙니다. XML 설정 파일을 오용하는 대표적인 사례로 Jelly 스크 립트가 있습니다. 자카르타 commons 프로젝트의 하나인데 XML로 스크립팅을 할 수 있게 해주는 엔진입니다. Jelly 스크 립트의 파워는 여전히 진짜 프로그래밍 언어에 미치지 못하면서 Jelly로 스크립트를 짜면 가독성도 떨어지고 리팩토링하기 어렵기 때문에 점점 지저분한 코드가 되고 맙니다. Ant도 이와 비슷한 문제점이 있습니다. 그래서 복잡한 제어 흐름이 필요 한 부분에서는 자바 코드를 직접 이용하고 이것이 무겁다면 좀더 가벼운 스크립트 언어를 자바에 임베딩해서 사용하는 것 이 좋습니다.

단, SQL의 경우는 언어 자체가 결과물에 대한 정의를 표현하는 것이기 때문에 복잡한 제어 구조가 필요하지 않아 XML로 별 무리 없이 소화가 가능합니다. 실제로 XML과 JDBC를 결합시킨 iBatis에서 XML로 SQL들을 관리해보면 실제 자바 코드 보다 훨씬 쉽고 보기 좋게 코딩할 수 있습니다.

또 한 가지 주의해야 할 것은 XML 설정 파일을 이용하게 만들더라도 XML 없이도 프레임워크가 동작하게 만들어야 한다는 것입니다. 앞서의 iBatis는 구현된 기능들이 정말 편리하고 좋지만 각 컴포넌트들이 지나치게 강하게 결합되어 있고 XML 설정 파일 없이는 아무 것도 하지 못합니다. 그래서 추가 기능을 넣고 싶다거나 개선하고 싶은 부분이 있을 때 상속 등의 방 법으로는 해결하기 어렵고 거의 대부분 소스를 수정해야 해결할 수 있죠. 그래서 프레임워크를 만들더라도 일단 자바 코드 로 모두 만든 다음 마지막에 XML로 설정 내용들을 빼는 작업을 하는 게 좋습니다.

살펴볼만한 프레임워크 그림도 코드도 없는 지루한 텍스트를 읽어 내려오느라 지루했을 독자들을 위해 구체적인 프레임워크들을 몇 가지 소개할까 합니다. 웹 애플리케이션 프레임워크 뿐 아니라 여러 영역에서 유용하다고 생각되는 것들을 뽑았습니다. 단지 필자의 경험 속에서 유용했던 것들이므로 한 번 살펴볼 가치가 있다는 정도로 받아들이면 될 것입니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (25 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

스트럿츠와 JSF 웹 애플리케이션 프레임워크로는 스트럿츠가 현재의 주류이며 JSF가 일부에서 미래의 주류로 꼽힙니다. 모두 한 사람이 주 도한 프레임워크입니다.

필자의 판단을 덧붙이면, 스트럿츠는 실제로 개발자의 일을 거의 줄여주지 못하며 오히려 늘이는 경우조차 간혹 있습니다. 스트럿츠를 쓸 바에는 직접 스트럿츠와 유사한 형태로 서블릿을 직접 사용하도록 재구성하는 것이 좋으리라 생각됩니다. 물 론 유효성 검사 기능과 Tiles는 좋은 기능이지만 이들은 스트럿츠와 분리해서도 사용 가능합니다.

JSF는 미래의 주류라고 하기엔 부족함이 너무 많습니다. 썬에서 Java Studio Creator를 통해 멋진 가능성을 내보이긴 했 지만 그런 IDE를 사용하지 않을 때의 JSF 코딩은 너무 번잡하고 복잡도도 큽니다. 이는 구조적으로 아직 개선의 여지가 많 다는 것이죠. JSF 스펙 역시 꾸준히 발전하고 있으니 미래는 어떨지 모르겠지만 지금 JSF를 사용하는 것은 시기상조라 생 각됩니다. 오히려 JSF와 비슷한 구조인 Tapestry가 더 완성도가 높다는 평가를 받고 있습니다.

Turbine Turbine도 꽤 널리 쓰이는 프레임워크 중 하나입니다. 기본 틀은 스트럿츠와 비슷하나 훨씬 많은 부분들을 커버하고 있기 때문에 코딩량은 상당히 줄어듭니다. 개발자의 편의를 위한 클래스들도 많으며 Turbine 개발 과정에서 만들어진 컴포넌트 들이 상당수 독립해서 별도의 프로젝트로 진행되고 있습니다. 전에는 유연성이 다소 떨어진다는 것이 단점이었는데 점점 나 아지고 있습니다.

Spring Spring은 요즘 주목 받는 프레임워크로 AOP(Aspect Oriented Programming)를 내세우고 있으며 low coupling, high cohesion이라는 디자인 원칙이 잘 지켜진 프레임워크입니다. 딱히 웹을 염두에 두었다기보다 종합적인 애플리케이션 프레 임워크이기 때문에 부분별로 사용할 수도 있고 다른 프레임워크에 붙여서 사용하기도 합니다. 데이터베이스와 관련해서 풍 부한 API를 제공하며, Mock Object를 프레임워크 자체에서 제공하고 있다는 것도 특이한 점입니다. 현재 가장 추천할 만 한 프레임워크입니다.

iBatis 데이터베이스와 관련해서는 앞서의 Spring으로도 충분할 수 있지만 XML로 SQL을 관리하고자 한다면 iBatis가 최선의 선 택일 것입니다. 내부적인 코드의 품질은 다소 떨어지지만 기능상으로는 별다른 부족함이 없습니다. Spring과 iBatis가 JDBC에 대한 단순 래퍼 수준의 API를 제공한다면 Hibernate는 OR(Object-Relation) 맵핑을 통해서 개발자는 객체만을 바라보고 데이터베이스를 간접적으로 이용할 수 있는 프레임워크입니다. 기본적으로 구조가 깔끔하고 편리하다는 평가를 받고 있으며 비즈니스 로직을 도메인 모델로 직접 다룰 수 있다는 점에서 높이 평가할만 합니다.

OSCache, Quartz, JUnit 이외에 웹에서 캐싱 기법으로 성능을 향상시키기 위한 OSCache, 스케쥴러를 만들기 위한 API인 Quartz도 아주 유용합니 다. 물론 테스팅 프레임워크로 JUnit은 언급할 필요가 없을 것입니다. 이런 프레임워크들을 꼭 사용하지 않더라도 한 번 살 펴보면 배울 만한 점들을 발견할 수 있고 이런 점들이 나중에 도움이 되는 경우가 적지 않을 것입니다.

많은 논란거리들 패턴이나 프레임워크는 특정 영역에 대해 어느 정도 검증된 해결책입니다. 그래서 때때로 이에 대한 맹신이 나타나기도 합 니다. 검증된 방법이라는 논리로 너무 쉽게 문제 영역에 패턴을 적용하거나 프레임워크를 사용하곤 하죠. 하지만 그 문제 영 역이 패턴이나 프레임워크를 활용할 수 있는 바로 그 영역이라는 점은 검증하기 힘든 것입니다. 때문에 패턴 중심적인 사고 방식이나 프레임워크에 끼워 맞추는 코딩을 지양하고 문제 영역의 해결에 충실하면서 패턴을 생각하는 것이 더 좋습니다.

바둑 격언 중에 ‘정석은 배우고, 잊어버려라’라는 말이 있습니다. 정석을 익혀서 알고는 있되 실전에서는 정석에 집착하지 말고 상황에 따라 유연하게 대처해야 좋은 바둑을 둘 수 있다는 뜻입니다. 패턴은 프로그래밍의 정석과도 같습니다. 상황에 대한 검증된 해결책이지만 그 상황은 계속 변하고 그 변화에 빠르게 대처해야 합니다.

패턴이나 프레임워크에 대한 맹신으로 빠져드는 것을 경계하기 위해 어떤 사람들은 무엇이든지 장단점이 있으니 너무 집착 하지 말라고 말합니다. 이것은 옳은 말이지만 사실 큰 의미가 있는 말은 아닙니다. 무엇이든 장단이 있다는 사실은 누구도 부인할 수 없지만 또한 누구도 부인할 수 없는 자명한 사실이기에 의미 없는 말이기도 합니다. 실제 상황에 맞게 잘 쓰려면 구체적으로 장점이 무엇인지, 단점이 무엇인지를 파악하는 것이 중요합니다. 앞서 패턴과 프레임워크를 다루면서 각각의 단 점들을 구구절절이 따지고 들어간 것도 그런 이유입니다.

나날이 신기술이 쏟아져 나오고 있지만 그에 못지않게 패러다임도 많이 변화하고 있습니다. 유연성 높은 컴포넌트 기술로 주목받던 자바빈즈가 해로운 것이라는 평가를 받기 시작하고, 많이 써야 좋은 것이라고 하던 주석을 이제는 적게 쓸수록 좋 은 것이라고 합니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (26 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

신기술을 따라가는 것도 필요하지만 이러한 패러다임의 변화를 읽는 것이 더 중요합니다. 현재의 방식이 지금은 가장 효율 적인 방식일 수 있겠지만 지금보다 훨씬 더 효율적인 방식이 나오는데 모르고 있으면 도태될 수밖에 없습니다. 흔히 프로그 래머는 워낙 기술이 빨리 발전하기 때문에 후배들을 당할 수가 없다는 말을 하곤 합니다.

하지만 현실은 결코 그렇지 않습니다. 기술의 변화는 단지 API가 변하는 것 뿐 그렇게 중요한 것은 아닙니다. 프로그래밍 의 주요한 패러다임은 그렇게 빠른 속도로 변하지 않으며 또한 그런 패러다임의 변화를 주도하는 것은 반짝이는 아이디어 가 아니라 경험 속에서 축적된 고민들입니다. 끊임없이 고민하면서 프로그래밍을 해왔다면 이런 변화를 따라잡는 것은 어려 운 일이 아닙니다.

좋은 프로그래머가 되기 위해서 폭넓은 지식과 빠른 학습 능력은 물론 필수적인 것입니다만 그보다 더 중요한 것은 늘 고민 하는 자세와 깊이 있는 사고 능력입니다. 변화를 따라잡는 힘은 빠른 학습 능력에서 나오는 것이 아니라 변화의 중요성을 인 식하고 발전을 위해 고뇌하는데서 나오는 것입니다.

이번 연재는 아마 지난 두 번보다 훨씬 더 많은 논란거리들을 담고 있을 것입니다. 패러다임은 한 사람의 고민에 의해서도 발전하지만 많은 사람들의 머리를 맞댄 토론에서도 발전합니다. 필자의 글에 반론이 있거나 더 깊은 논의를 원하는 독자들 은 youngrok.com이나 javaservice.net, 혹은 c2.com에 제시해 주세요. 서로를 발전시킬 수 있는 좋은 기회가 있길 바랍 니다.@

* 이 기사는 ZDNet Korea의 제휴매체인 마이크로소프트웨어에 게재된 내용입니다.

Posted by Crowe Lee at 07:38 PM | Comments (0) | TrackBack

March 02, 2005

Color Table (Internet Explorer - DHTML) | MSDN

View: Color Table (Internet Explorer - DHTML)

Colors can be specified in HTML pages in two ways—by using a color name, or by using numbers to denote an RGB color value. An RGB color value consists of three two-digit hexadecimal numbers specifying the intensity of the corresponding color.

For example, the color value #FF0000 is rendered red because the red number is set to its highest value, FF (or 255 in decimal form).

When you use the !DOCTYPE declaration to specify standards-compliant mode, Microsoft Internet Explorer 6 and later versions ignore style sheet declarations that do not comply with Cascading Style Sheets (CSS), Level 1 (CSS1). According to CSS 1, colors specified with hexadecimal RGB values must have a leading '#' character. Values like "FFFFFF" are ignored, not treated the same as "#FFFFFF" (also known as white), as with previous versions of Internet Explorer. This affects all properties that accept a color value.

Note While these color names might not be recognized by other browsers, the RGB color values should display

accurately across browsers. When specifying color values for Web pages targeted to work across different browsers, use

the RGB color values.

The following table lists the colors supported as of Internet Explorer 4.0.

F0F8FF aliceblue FAEBD7 antiquewhite

http://crowe.wowdns.com:8000/archives/dev_note/ (27 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

00FFFF aqua 7FFFD4 aquamarine

F0FFFF azure F5F5DC beige

FFE4C4 bisque 000000 black

FFEBCD blanchedalmond 0000FF blue

8A2BE2 blueviolet A52A2A brown

DEB887 burlywood 5F9EA0 cadetblue

7FFF00 chartreuse D2691E chocolate

FF7F50 coral 6495ED cornflowerblue

FFF8DC cornsilk DC143C crimson

00FFFF cyan 00008B darkblue

008B8B darkcyan B8860B darkgoldenrod

A9A9A9 darkgray 006400 darkgreen

BDB76B darkkhaki 8B008B darkmagenta

556B2F darkolivegreen FF8C00 darkorange

9932CC darkorchid 8B0000 darkred

E9967A darksalmon 8FBC8B darkseagreen

483D8B darkslateblue 2F4F4F darkslategray

00CED1 darkturquoise 9400D3 darkviolet

FF1493 deeppink 00BFFF deepskyblue

696969 dimgray 1E90FF dodgerblue

B22222 firebrick FFFAF0 floralwhite

228B22 forestgreen FF00FF fuchsia

DCDCDC gainsboro F8F8FF ghostwhite

FFD700 gold DAA520 goldenrod

808080 gray 008000 green

ADFF2F greenyellow F0FFF0 honeydew

FF69B4 hotpink CD5C5C indianred

4B0082 indigo FFFFF0 ivory

F0E68C khaki E6E6FA lavender

FFF0F5 lavenderblush 7CFC00 lawngreen

FFFACD lemonchiffon ADD8E6 lightblue

F08080 lightcoral E0FFFF lightcyan

FAFAD2 lightgoldenrodyellow 90EE90 lightgreen

D3D3D3 lightgrey FFB6C1 lightpink

FFA07A lightsalmon 20B2AA lightseagreen

http://crowe.wowdns.com:8000/archives/dev_note/ (28 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

87CEFA lightskyblue 778899 lightslategray

B0C4DE lightsteelblue FFFFE0 lightyellow

00FF00 lime 32CD32 limegreen

FAF0E6 linen FF00FF magenta

800000 maroon 66CDAA mediumaquamarine

0000CD mediumblue BA55D3 mediumorchid

9370DB mediumpurple 3CB371 mediumseagreen

7B68EE mediumslateblue 00FA9A mediumspringgreen

48D1CC mediumturquoise C71585 mediumvioletred

191970 midnightblue F5FFFA mintcream

FFE4E1 mistyrose FFE4B5 moccasin

FFDEAD navajowhite 000080 navy

FDF5E6 oldlace 808000 olive

6B8E23 olivedrab FFA500 orange

FF4500 orangered DA70D6 orchid

EEE8AA palegoldenrod 98FB98 palegreen

AFEEEE paleturquoise DB7093 palevioletred

FFEFD5 papayawhip FFDAB9 peachpuff

CD853F peru FFC0CB pink

DDA0DD plum B0E0E6 powderblue

800080 purple FF0000 red

BC8F8F rosybrown 4169E1 royalblue

8B4513 saddlebrown FA8072 salmon

F4A460 sandybrown 2E8B57 seagreen

FFF5EE seashell A0522D sienna

C0C0C0 silver 87CEEB skyblue

6A5ACD slateblue 708090 slategray

FFFAFA snow 00FF7F springgreen

4682B4 steelblue D2B48C tan

008080 teal D8BFD8 thistle

FF6347 tomato 40E0D0 turquoise

EE82EE violet F5DEB3 wheat

FFFFFF white F5F5F5 whitesmoke

FFFF00 yellow 9ACD32 yellowgreen

User-Defined System Colors

http://crowe.wowdns.com:8000/archives/dev_note/ (29 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Microsoft Windows maintains a set of system colors for painting various parts of the display. In addition to the colors defined in the color table, as of Internet Explorer 4.0 these system colors may also be specified as color values in Web pages.

Users can set system colors for future Windows sessions using the Windows Control Panel. The following table illustrates the correspondence of these color names with the colors defined in the Control Panel.

Note The colors that do not correspond to any Control Panel colors can only be set programmatically using Windows

APIs and cannot be defined by the user.

activeborder activecaption

appworkspace background

buttonface buttonhighlight

buttonshadow buttontext

captiontext graytext

highlight highlighttext

inactiveborder inactivecaption

inactivecaptiontext infobackground

infotext menu

menutext scrollbar

threeddarkshadow threedface

threedhighlight threedlightshadow

threedshadow window

windowframe windowtext

Posted by Crowe Lee at 02:48 PM | Comments (0) | TrackBack

January 25, 2005

Struts best practices

View: Struts best practices | JavaWorld.com

Struts best practices Build the best performing large applications

Summary Multiple options are available for solving problems with Struts. When deciding among these alternatives, the choice must be based on parameters such as the scale of work and availability of time. However for large applications and the best quality-of-service needs, every decision becomes crucial and extra efforts are required to choose the appropriate solution. To http://crowe.wowdns.com:8000/archives/dev_note/ (30 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

help you make these decisions, Puneet Agarwal discusses some of the best practices for developing Struts-based applications. (2,800 words; September 13, 2004)

By Puneet Agarwal

rue to the literal meaning of the word, "Struts" provides supporting building blocks and infrastructure components to build a Web-based application. It is an MVC-based (Model View Controller) open source framework developed and supported by the Apache Software Foundation. Because of its support for extensibility and plug-ins, the framework has picked up stupendous popularity among J2EE-based application developers. The framework can be extended and customized to suit a particular application need.

Though covering all the aspects of this framework and documenting the best practices may not be possible in one article, the subsequent sections discuss some of the best practices for developing with Struts.

The primary sources of information for this article are the Struts users' mailing list, the Struts developers' mailing list, and my experience with Struts-based applications.

The article discusses the following main points:

● Screens with dynamic fields

● Safeguarding JSP pages

● Error categorization

● Validation of service requester

● Application security

● Prepopulation

● Stack maintenance (for bread crumbs)

● Context-related problems

● Form-bean scope

● Data transfer object implementation

● Exceptions

● Action chaining

Screens with dynamic fields Problem The (JCP) has released the Java Metadata Interface Specification, and some programmers are involved in the open source project Beehive. Both of these projects strive to reduce coding. However, the question is whether Struts has a facility that can be used for writing a generic JSP (JavaServer Pages) page for specific types of screens in an application so that a separate JSP page doesn't have to be written for each screen. For example, to reduce our coding efforts, we might want to develop a generic JSP page for all search screens in an application or for submitting batch processes or reports, where the parameters to be input vary for every report/batch.

Form beans are classes that must have getter and setter methods for every field in JSP, and the problem is how to write these methods for dynamic fields.

Struts best practice

http://crowe.wowdns.com:8000/archives/dev_note/ (31 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Possible solutions are:

● Let the JSP page have fields in a specific pattern such as field1, field2, field3, and so on, and provide their getter and setter methods in the form bean. Here, the number of fields that can appear on the screen cannot be more than the number of variables in the form bean.

● Utilize the indexed getter and setter methods available in the form bean for all dynamic fields in the JSP page.

In the second approach, an increase in the number of fields in JSP requires no alteration in any component; therefore, it is the recommended best practice. The implementation details follow:

1. Assuming an array of strings carries the resource IDs for all the dynamic fields in the form bean, the JSP page can be written as:

2. Declare two methods in the form bean, as shown below. These methods will work as the getter and setter methods for all the dynamic fields in the JSP page. Whatever appears in small brackets—()— in front of dynaProperty (in the JSP page as shown above), is taken as key, and either the getDynaProperty() or setDynaProperty() method from the form bean is called. These values should be stored in a HashMap against the key, which can later be retrieved in the Action class from the HashMap against the key.

public class testVarForm extends ActionForm { private HashMap hMap = new HashMap();

public testVarForm() { }

public void setDynaProperty(String key, Object value) { this.hMap.put(key, value); }

public Object getDynaProperty(String key) { return this.hMap.get(key); }

public HashMap getHashMap() { return this.hMap; } public void setHashMap(HashMap newHMap) { this.hMap =newHMap; } }

http://crowe.wowdns.com:8000/archives/dev_note/ (32 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Safeguard your JSP pages Problem When developers use Web-based applications, they often try to break into the security. The most common habit is to view the source of HTML in the browser and somehow determine the path of JSP pages and access them. The intent is to highlight the vulnerability of JSP pages accessible without authorization. Users who lack authorization to view the source might observe the source URL while sitting with another user who is authorized to work on that specific screen. Later, this unauthorized user could log in to the application and type the URL in the browser. In some cases, such users are able to make their way through.

Struts best practice The possible solutions to this problem:

● Do not let users access any JSP page directly. The starting page can be an HTML document. Add the following lines to the web.xml file to prevent users from accessing any JSP page directly:

... no_access *.jsp ...

● The most popular option is to keep JSP pages behind the WEB-INF folder. This has a few tradeoffs. For example, you cannot take the JavaScript/CSS (Cascading Style Sheets) files behind WEB-INF, and if using Struts modules, you may encounter some context-related problems. Refer to the section "Context-Related Problems," which appears later in this article, to circumvent such issues.

The second approach allows some JSP pages (which are not behind WEB-INF) to be visible directly. It does not require a descriptor file entry, therefore the best practice is to keep the pages behind WEB-INF.

Error categorization Problem Error handling becomes complex for an n-tiered application. In a browser-based application, the errors can be handled in the client layer using JavaScript and in the Web tier or EJB (Enterprise JavaBeans) tier using custom Java methods. Building an infrastructure for consistent error reporting proves more difficult than error handling. Struts provides the ActionMessages/ActionErrorsclasses for maintaining a stack of error messages to be reported, which can be used with JSP tags like to display these error messages to the user. The problem is reporting a different category/severity of the message in a different manner (like error, warning, or information). To do that, the following tasks are required:

1. Register the errors under the appropriate category 2. Identify these messages and show them consistently http://crowe.wowdns.com:8000/archives/dev_note/ (33 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Struts best practice Struts' ActionErrors class comes in handy in resolving the first issue of stacking messages of different categories. To display the error messages of different categories, define these categories such as FATAL, ERROR, WARNING, or INFO, in an interface. Then, in the Action or form-bean class, you can use:

errors.add("fatal", new ActionError("....")); or errors.add("error", new ActionError("....")); or errors.add("warning", new ActionError("....")); or errors.add("information", new ActionError("....")); saveErrors(request,errors);

Having stacked the messages according to their category, to display them according to those categories, use the following code:

Or use:

showError(''); // JavaScript Function

Validation of service requester: Login-check Problem Authentication in a Web-based application can be done in any class, depending upon whether an SSO-based (single sign- on) or a JAAS-based (Java Authentication and Authorization Service) mechanism is being used. The challenge is identifying the placeholder for checking the service requester's authenticity and the user session's validity.

Struts best practice Usual practice is to store user credentials in HttpSession after authentication. Subsequent calls check credentials' existence in session context. The question is where to place these checks. Some options are listed below, but they must be rationalized on the basis of performance overhead, possibility of future changes, and application manageability:

● Authenticate against the session context before doing any operation (as done in Struts-example. war's CheckLoginTag.java)

● Authenticate against session context in the Action class

● Write servlet request filters that perform authentication

http://crowe.wowdns.com:8000/archives/dev_note/ (34 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

● Extend RequestProcessor

The first two options require every JSP page or the Action class to perform the authentication against the session context. Change in the interface mandates change in all these JPS pages and classes. The third option is efficient, but overkill for the problem at hand.

The best practice is to extend the RequestProcessor class and perform authentication in methods such as processActionPerform() or processRoles().

Application security Problem The usual demand in Web-based applications is to have screen-level, function-level, data-row-level, and field-level security. If not suitably designed, incorporation of these security levels in an application may cause not only performance overheads, but also maintenance nightmares.

For all the security types mentioned above, the preferred approach is to place the security check in one class instead of in every component—i.e., in every JSP page or Action class.

Struts has a method processRoles() for screen- and function-level security checks, however nothing is provisioned for field- and column-level security types, making it the most challenging for most Struts users.

Struts best practice Irrespective of where the security realm is set up (database or directory service), the best practices for the various security levels are described below:

● For screen- and function-level security, extend RequestProcessor and override the method processRoles() to perform the check against a HashMap that stores a mapping of roles and screen IDs/function IDs

● Row-level security is best implemented in the application's object relational mappings

● For field-level security, tag libraries are extended to perform the check against the field ID

Prepopulation Problem One requirement difficult to achieve is data prepopulation in a drop-down list (HTML Select tag) or in other fields. Some of these values come from a database, some from the application's context, and the rest are passed from calling screens. These values must be populated in a form bean before forwarding the control to the JSP page.

Struts best practice The form bean's prepopulation can be accomplished using one of the following approaches:

● The on-demand lazy-loading technique for all such data in the application's context, which is required for prepopulation. An application screen's (JSP page) invocation mechanism can always be routed though a specific method of corresponding Action classes. Prepopulation-related code can be placed

http://crowe.wowdns.com:8000/archives/dev_note/ (35 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

in this method.

● Have different Action classes—one for requesting and another for submitting the JSP pages. The entire module or application can share the Action class used for requesting.

The second approach works well for small applications. However, for large applications, managing numerous Action classes grows cumbersome. Therefore, the first approach should be adopted.

Stack maintenance (for bread crumbs) Problem What if you need to go to a JSP page from two or more different JSP pages and subsequently return to the calling JSP page? Similarly, often developers need to go to various JSP pages in a criss-cross manner, which grows more complex when breadcrumbs need to be shown. Thus, the application must remember the pages visited.

Struts best practice The remedy is to maintain the paths of all the forwarded JSP pages in a Stack (java.util.Stack). The following steps are required for maintaining such a Stack:

1. Extend the RequestProcessor class and override the method processActionPerform(). In the overridden method, after the call to super.processActionPerform(), ActionForward's path should be stored in the Stack.

2. In the application's parent Action class, provide methods to traverse forward and backward in the Stack.

Another option is to utilize an already available open source project at sourceforge.net like Open Tranquera (see Resources for more details).

Context-related issues Problem If ActionForward's path and the path to which the control is forwarded differ, a context problem has resulted. More such context-related problems are quite frequent in Struts-based applications, most of which can be solved using some of Struts' less exploited features.

Struts best practice Mention the JSP page's name as a path for ActionForward and let the prefix—i.e., the JSP page's access path—come from a preconfigured place. Do that in the following manner:

struts-config.xml(s) has a controller tag; add a property in this tag named forwardPattern. This property's value can be prefixed to all the ActionForward paths. This property's default value is $M$P, which means the module prefix will be prefixed to ActionForward's path. We can change this value to anything; e.g., WEB-INF/ pages/$M$P. As a result, all the ActionForward paths will be searched in the directory WEB-INF/pages/ /.

http://crowe.wowdns.com:8000/archives/dev_note/ (36 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

For above solution to work, make sure that ActionForward's contextRelative property is marked false. If this property is marked true, the ActionForward paths will be taken since they are not modified.

Also note that for forwardPattern to work, the path of all ActionForwards must start with a /.

Form-bean scope Problem A well-managed session context can greatly boost application performance. It is therefore important to understand the implications of marking the form-bean scope to request, session, or application. However, the following confusions usually arise:

● If the scope of the form beans is marked request, how will the information available in one JSP page/screen also be available to another one?

● If the scope is session, how will the server know that the client is no longer interested in that form bean and consequently destroy it?

Struts best practice State can be maintained in HttpSession (Web tier), in stateful session beans (EJB tier), or in a database. The appropriate choice should be based on what kind availability needs persist or whether the application is supposed to provide multichannel access. However, the rule of thumb is to maintain the state close to the tier that requires it most. Therefore, the best practice is to keep the state in HttpSession; i.e., mark the form-bean scope to session.

The important question is when and how to destroy the form beans stored in the HttpSession. The session beans should be destroyed in an extended RequestProcessor based on the least-recently-accessed, to-be-removed logic. Another approach is to destroy all the form beans as soon as the user invokes a new business operation, possibly from the application menu.

Data transfer object implementation Problem Usually a data transfer object (DTO) is used for shuttling data between the Web and EJB tiers. It is not a good idea to pass the view-helper class (form bean, in the case of Struts), to the EJB tier, primarily because all of the form bean's fields are of type string. A separate class should be used as the data transfer object. The problem is how to transfer the data from the form bean to the DTO.

Struts best practice Two options are available for populating the data transfer object:

● Create the transfer object and copy the data from the form bean to the transfer object. Data-type conversions must be handled before data copying. Similarly, on the way back, you must repeat the same exercise of copying the data from the DTO to the form bean.

● Use Commons' BeanUtils class, which uses the Reflection API to achieve its objectives.

http://crowe.wowdns.com:8000/archives/dev_note/ (37 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Developers frequently use the first option, which is not problematic except that the mundane exercise is repeated every time, resulting in bulky and ugly code. While copying data, suitable data-type conversion must be hand-coded in the DTO's setter methods, so that when data is copied from the form bean to the DTO, it converts to the appropriate business type. All the attributes of type string in the form beans are the primary reasons for data-type conversions.

You can avoid all this coding by using BeanUtils's copyProperties() method. The method copies the data from one bean to another, provided the variable names of the business attributes in the value/transfer object are the same as the ones in the form bean. The method also relieves you from data-type conversion by transparently converting source- bean attribute types to destination-bean attribute types.

Exceptions Problem As a generic principle, errors should be caught right where they occur to display meaningful error messages. To implement this principle, programmers must write their own custom exception classes, wrap the actual exception into these custom classes, and throw them back to the place where they are handled. In a Struts-based application, the exceptions thrown from the EJB/Web tier are handled either in the Action class or the form bean. The usual practice involves writing a try-catch block to catch these exceptions in the Action class or the form bean, and creating an ActionError object with user-friendly messages derived from the application property file. The decision to direct these error messages to the screen is coded in the action classes, using the ActionMapping.findForward() method.

You must code all this exception-handling code in the Action class as well as the decision to direct the message to the screen. If the error-handling strategy changes, then every Action or form bean requires changes.

Struts best practice Struts deals with the issue of exception handling in a competent way, using declarative exception handling. This, as opposed to programmatic exception handling (as explained above) handles issues using the RequestProcessor class's processException() method and a Struts configuration file. To use declarative exception handling, you must do the following:

1. Create custom application exception classes. You can design them so they hold more than one exception

2. In the EJB/Web tier, trap application errors and wrap them into a custom exception class and throw them back

3. In the struts-config.xml file, add the localized exception against an action-mapping or define a global exception (as shown below):

key="your.error.property.key" path="yourException.jsp" type="your.application.custom.exception"

Action chaining Problem A clear strategy for the relationship between JSP pages and Action classes in an application should be defined; i.e., how many JSP pages should be associated with an Action class.

For clarity and easy maintenance, the strategy for large applications should be to have one-to-one mapping between JSP pages and Action classes. With such a guideline in place, a prospective problem of duplicated code in the Action classes could result. To avoid logic duplication, you need some way to call an Action class's method from another Action class. Moreover for criss-cross page flow, you often need action chaining, or, to summarize, an implementation of the Chain of Responsibility design pattern.

Struts best practice Possible solutions follow below:

● Let ActionForwards point to a fresh request in the same application to invoke the method of another Action class.

● RequestProcessor maintains a HashMap that stores all the instances of Action classes for that module. Extend the RequestProcessor class and provide getter methods for this HashMap. Provide a utility method in the application's parent Action class, which can invoke the methods of other Action classes within the same module. This utility method will use the Reflection API to achieve this.

The second approach is the best practice for large applications because if some change is required in exception handling, no code change will be required.

Conclusion Large applications built using the above recommendations have been benchmarked for excellent performance.

As indicated by the Struts roadmap (featured on the Struts Website), features and extensions developed on sourceforge. net and those commonly used by the Struts community are candidates for inclusion into Struts per se. All these are worth reading about before you make critical decisions about your application.

About the author Puneet Agarwal works as a technical architect in the open architecture lab of Tata Consultancy Services Limited (TCS). In his six-year software career with TCS, he has designed technical architectures for various large- and medium-sized J2EE- based applications. Agarwal is an engineering graduate from the National Institute of Technology in Trichy, India.

http://crowe.wowdns.com:8000/archives/dev_note/ (39 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Resources

● For Struts examples, extensions, and utilities: http://struts.sourceforge.net

● Open Tranquera: http://opentranquera.sourceforge.net

● Struts basic FAQs: http://struts.apache.org/faqs/index.html

● Struts documentation: http://struts.apache.org/userGuide/index.html

● Struts users' mailing list archives: http://www.mail-archive.com/[email protected]/

● Struts developers' mailing list archives: http://www.mail-archive.com/[email protected]/

● Example applications on Struts: http://sourceforge.net/projects/struts

● For information on the Apache Beehive project: http://incubator.apache.org/beehive/

● More tips on developing with Struts: "Jump the Hurdles of Struts Development," Michael Coen and Amarnath Nanduri (JavaWorld, April 2003): http://www.javaworld.com/javaworld/jw-04-2003/jw-0418-struts.html

● For more articles on JSP, browse the JSP (JavaServer Pages) section of JavaWorld's Topical Index: http://www.javaworld.com/channel_content/jw-jsp-index.shtml?

● For more articles on Java development tools, browse the Development Tools section of JavaWorld's Topical Index: http://www.javaworld.com/channel_content/jw-tools-index.shtml

● For more articles on J2EE development, browse the J2EE (Java 2 Platform, Enterprise Edition) section of JavaWorld's Topical Index: http://www.javaworld.com/channel_content/jw-j2ee-index.shtml?

Posted by Crowe Lee at 11:09 AM | Comments (0) | TrackBack

January 24, 2005

A JSTL Primer, Part 2 : Getting Down To The Core

View: A JSTL Primer, Part 2 : Getting Down To The Core | IBM DeveloperWorks

커스텀 태그를 이용한 플로우 제어와 URL 관리

Level: Intermediate

Mark A. Kolb 소프트웨어 엔지니어 2003년 3월 18일

http://crowe.wowdns.com:8000/archives/dev_note/ (40 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

JSP Standard Tag Library (JSTL) core 라이브러리는 이름이 말해주듯이, 범위(scoped) 변수를 관리하 고 URL과 인터랙팅하는 등의 기본 기능과, 반복과 조건화 같은 근본적인 작동에 필요한 커스텀 태그를 제공 한다. 이러한 태그들은 페이지 작성자가 직접 사용하기도 하지만 다른 JSTL 라이브러리와 함께 복잡한 표 현 로직에 대한 토대를 제공한다.

이 시리즈의 첫 번째 글에서 JSTL을 처음 보았을 것이다. 거기에서 데이터에 액세스 하고 작동하기 위해 expression language (EL)의 사 용법을 설명했다. 여러분이 배운 것 처럼 EL은 JSTL 커스텀 태그의 애트리뷰트에 동적 값을 할당하는데 사용된다. 따라서 빌트인 액션과 기타 커스텀 태그 라이브러리용 요청 시간 애트리뷰트 값을 지정하는 JSP 식과 같은 역할을 한다.

EL의 사용법을 설명하기 위해, core 라이브러리에서 세 개의 태그 (, , )를 소개했었다. 는 범위 변수를 관리하는데 사용된다. 은 특별히 EL을 사용하여 계산된 값인 데이터 디스플레이에 사용된다. 기초 학습 을 토대로 이제는 core 라이브러리의 나머지 태그를 볼 것이다. 두 가지 주요 카테고리로 나뉜다: 플로우 제어(플로우 제어)와 URL 관리.

예제 애플리케이션 JSTL 태그를 설명하기 위해서 실제 애플리케이션 예제를 사용할 것이다. 대중성과 인지도가 높아졌기 때문에 간단한 자바 기반의 Weblog 를 사용할 것이다. (참고자료에서 JSP 페이지와 소스 코드를 다운로드 한다.) Weblog (blog)은 웹 기반 저널로서 Weblog의 작성자가 흥 미를 가질만한 주제들에 대한 짧은 주석이다. 일반적으로 웹 상의 아티클이나 토의가 있는 곳 어디든 연결된다. 그림 1은 실행 중인 애플리 케이션의 모습이다.

그림 1. Weblog 애플리케이션

http://crowe.wowdns.com:8000/archives/dev_note/ (41 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Screenshot of the Weblog 예제 애플리케이션

스무 개 정도의 자바 클래스가 전체 구현에 필요하지만 Weblog 애플리케이션 클래스에서는 단 두개(Entry와 UserBean)만이 프리젠테 이션 레이어에 사용된다. JSTL 예제를 이해하려면 이들 두 개의 클래스가 필요하다. 그림 2는 Entry와 UserBean의 클래스 다이어그램이 다.

그림 2. Weblog 애플리케이션(클래스 다이어그램)

http://crowe.wowdns.com:8000/archives/dev_note/ (42 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Class diagram for Weblog 예제 애플리케이션

Entry 클래스는 Weblog 내의 날짜가 나와있는 엔트리를 나타낸다. 이것의 id 애트리뷰트는 데이터베이스 내의 엔트리를 저장하고 검색 하는데 사용된다. 반면 title과 text 애트리뷰트는 엔트리의 실제 콘텐트를 나타낸다. 자바 Date 클래스 중 두 개의 인스턴스는 created와 lastModified 애트리뷰트에 의해 레퍼런스되며 엔트리가 처음으로 만들어지고 마지막으로 편집 될 때 나타난다. author 애 트리뷰트는 UserBean 인스턴스를 참조하면서 엔트리를 만든 사람을 나타낸다.

The UserBean 클래스는 애플리케이션의 권한이 있는 사용자 정보(사용자 이름, 성명, 이메일 주소)를 저장한다. 이 클래스에는 관련 데이 터베이스와 인터랙팅하기 위한 id 애트리뷰트도 포함되어 있다. 마지막 애트리뷰트인 roles는 String 값 리스트를 참조하면서 애플리케 이션 역할과 이에 상응하는 사용자를 구분한다. Weblog 애플리케이션의 경우 일반적인 역할은 "User" (모든 일반적인 애플리케이션 사용 자 역할)와 "Author" (Weblog 엔트리를 만들고 편집할 수 있는 사용자를 지정하는 역할) 이다.

플로우 제어(Flow control) 본 시리즈의 다른 동적 애트리뷰트 값을 지정하는데 JSP 식 대용으로 EL이 사용될 수 있기 때문에 스크립팅 엘리먼트를 사용할 필요 주제들 가 줄어들었다. 스크립팅 엘리먼트는 JSP 페이지에서 중요한 소스가 될 수 있기 때문에 간단한 대안을 제공한다는 것은 JSTL에 있어서 큰 이점이다. Part 1, "expression EL은 JSP 컨테이너에서 데이터를 검색하고 객체 계층을 오가며 간단한 작동을 수행한다. 데이터에 접근하여 조작하 language" (2003 는 것 외에도 JSP 스크립팅 엘리먼트의 또 다른 사용 측면은 플로우 제어이다. 특히, 페이지 작성자가 반복되거나 년 2월) 조건적인 콘텐트를 구현하기 위해서 스크립틀릿을 의존한다는 것은 일반적인 일이다. 하지만 그와 같은 작동은 EL 의 기능을 넘어서기 때문에, core 라이브러리는 반복, 조건화, 예외 처리 등의 형태로 플로우 제어를 관리 할 다양 Part 3, "프리젠테 한 사용자 액션을 제공한다. 이션이 모든 것이 다!" (2003년 4 반복 월) 웹 애플리케이션의 측면에서, 반복(iteration)은 데이터의 모음을 가져다가 디스플레이 하는데 주로 사용된다. 주 로 테이블에 리스트나 열(row) 시퀀스의 형태로 나타난다. 반복 콘텐트를 구현하는 JSTL의 기본 액션은 커스텀 태그이다. 이 태그는 두 개의 다른 유형의 반복을 지원한다: 정수 범위내의 반복(이를 테면, 자바 XML 콘텐트에 접 의 for 문)과 컬렉션 내의 반복(자바의 Iterator와 Enumeration 클래스). 근하기" (2003년 5월) 정수 범위 내에서 반복하려면 (Listing 1)의 커스텀 태그의 신택스가 사용된다. begin과 end 애트리 뷰트는 정적 정수 값 또는 정수 값을 계산하는 수식이 되어야한다. 이들은 각각 반복을 위한 인덱스의 초기 값과 반복이 멈추는 지점의 인 덱스 값을 지정한다. 를 사용하여 정수 범위에서 반복할 때, 이 두개의 애트리뷰트가 필요하며 다른 모든 것들은 선택사항이 다.

Listing 1. 액션을 통한 반복 신택스

http://crowe.wowdns.com:8000/archives/dev_note/ (43 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

body content

step 애트리뷰트 또한 정수 값을 갖고 있어야한다. 매번 반복한 후에 인덱스에 추가될 양(amount)을 정한다. 따라서 반복 인덱스는 begin 애트리뷰트 값에서 시작하고 step 애트리뷰트의 값에 의해 증가하며 end 애트리뷰트의 값을 초과할 때 정지한다. step 애트리뷰 트가 생략되면 step 크기는 1로 초기화된다.

var 애트리뷰트가 지정되면 지정된 이름을 가진 범위 변수가 만들어지고 인덱스의 현재 값으로 할당된다. 이 범위 변수는 태그의 바디 내에서 액세스 될 수 있다. Listing 2는 액션의 예제이다.

Listing 2. 태그

Value Square

이 예제 코드는 다섯 개 짝수의 제곱을 테이블로 만들었다. 그림 3이 그 결과이다 .

그림 3. Listing 2의 결과

Output of Listing 2

컬렉션의 멤버들 사이를 반복할 때 태그의 추가 애트리뷰트인 items 애트리뷰트가 사용된다. (Listing 3). 이러한 형식의

http://crowe.wowdns.com:8000/archives/dev_note/ (44 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

태그를 사용할 때, items 애트리뷰트는 유일하게 필요한 애트리뷰트이다.

Listing 3. 액션을 통한 반복 신택스

body content

자바 플랫폼에서 제공되는 표준 컬렉션 타입은 태그에 의해 지원된다. 어레이 엘리먼트를 통해 반복할 때 이 액션을 사용할 수 있다. 표 1은 items 애트리뷰트에 의해 지원되는 값들의 리스트이다. 테이블의 마지막 열이 표시될 때, JSTL은 이것의 인터페이스 (javax.servlet.jsp.jstl.sql.Result)를 정의한다.

표 1. 태그의 items 애트리뷰트에서 지원되는 컬렉션

items item 값의 결과

java.util.Collection 호출에서 iterator()까지의 엘리먼트

java.util.Map java.util.Map.Entry의 인스턴스

java.util.Iterator Iterator 엘리먼트

java.util.Enumeration Enumeration 엘리먼트

Object 인스턴스 어레이 Array 엘리먼트

초기 값들의 어레이 래핑된 어레이 엘리먼트

콤마로 나뉘어진 String 서브스트링

javax.servlet.jsp.jstl.sql.Result SQL 쿼리의 열(row)

Listing 4는 컬렉션을 통한 반복에 사용되는 태그이다. entryList 라는 범위 변수가 Entry 객체의 리스트로 설정되었다. 태그가 이 리스트의 각 엘리먼트를 처리한다. blogEntry 라는 범위 변수로 이것을 할당하고 두 개의 테이블 열을 만든다. 하나는 Weblog 엔트리의 title 이고 다른 하나는 이것의 text이다. 이 속성들은 한 쌍의 액션과 이에 상응하는 EL 식을 통해 blogEntry 변수에서 검색된다. Weblog 엔트리의 타이틀과 텍스트에 HTML이 포함되어있기 때문에 의 escapeXml 애트리뷰트 는 false로 설정된다. (그림 4).

Listing 4. 태그를 사용하여 Weblog 엔트리 디스플레이 하기

http://crowe.wowdns.com:8000/archives/dev_note/ (45 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

그림 4. Listing 4의 결과

Output of Listing 4

남아있는 애트리뷰트인 varStatus는 정수 범위의 반복이나 컬렉션 범위의 반복에서 똑같은 역할을 한다. var 애트리뷰트 와 마찬가지로, varStatus는 범위 변수를 만드는데 사용된다. 현재 인덱스 값이나 현재 엘리먼트를 저장하는 대신에 이 변수는 javax. servlet.jsp.jstl.core.LoopTagStatus 의 인스턴스로 할당된다. 이 클래스는 일련의 속성을 정의한다. (표 2).

표 2. LoopTagStatus 객체의 속성

속성 Getter Description

current getCurrent() 현재 반복 라운드 아이템

index getIndex() 현재 반복 라운드의 제로 기반(zero-based) 인덱스

count getCount() 현재 반복 라운드의 1 기반(one-based) 인덱스

first isFirst() 현재 라운드가 반복을 통한 첫 번째 패스임을 나타내는 플래그

http://crowe.wowdns.com:8000/archives/dev_note/ (46 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

last isLast() 반복현재 라운드가 반복을 통한 마지막 패스임을 나타내는 플래그

begin getBegin() begin 애트리뷰트의 값

end getEnd() end 애트리뷰트의 값

step getStep() step 애트리뷰트의 값

Listing 5는 varStatus 애트리뷰트가 사용되는 방법을 나타낸 예제이다. Listing 4의 코드를 수정하여 Weblog 엔트리의 숫자세기를 타 이틀을 디스플레이하는 테이블 열에 추가한다. 이것은 varStatus 애트리뷰트의 값을 지정하고 결과 범위 변수의 카운트 속성에 액세스 하면 된다. 결과는 그림 5 이다.

Listing 5. varStatus 애트리뷰트를 사용하여 Weblog 엔트리의 카운트 디스플레이하기

.

그림 5. Listing 5의 결과

Output of Listing 5

http://crowe.wowdns.com:8000/archives/dev_note/ (47 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

이외에도, core 라이브러리는 두 번째 반복 태그인 를 제공한다. 이것의 액션은 자바의 StringTokenizer 클래스의 JSTL 이다. 태그(Listing 6)는 컬렉션 지향 버전의 와 같은 애트리뷰트를 갖고 있다. 의 경우 토큰화 될 스트링은 items 애트리뷰트를 통해 지정되는 반면 토큰을 만드는데 사용되는 지정자 (deliniter)는 delims 애트리뷰트를 통해 제공된다. 경우와 마찬가지로, begin, end, step애트리뷰트를 사용하여 토큰 이 상응하는 인덱스 값들과 매칭되는 것에 프로세스 되도록 제한 할 수 있다.

Listing 6. 액션

body content

조건화 동적 콘텐트를 포함하고 있는 웹 페이지라면 다양한 형식의 콘텐트를 볼 수 있는 다양한 사용자 카테고리가 필요할 것이다. Weblog에서 방문자들은 엔트리를 읽고 피드백을 제출 할 뿐만 아니라 권한을 받은 사용자는 새로운 엔트리를 게시하거나 기존 콘텐트를 편집할 수 있어 야 한다.

JSP 페이지 내에 그러한 기능을 구현하고 리퀘스트 기반으로 디스플레이 하고자하는 것을 제어하도록 조건 로직을 사용함으로서 가용성 과 소프트웨어 관리는 향상된다. core 라이브러리는 두 개의 다른 조건화 태그인 를 제공하는데 다음의 기능들을 구현한다.

이 두 가지 액션 중 좀더 단순한 는 간단한 테스트 식을 계산한 다음 식이 true로 되었을 때만 바디 콘텐트를 처리한다. 그렇지 않 다면 태그의 바디 콘텐트는 무시된다. Listing 7에서 보듯, 는 테스트의 결과를 var와 scope 애트리뷰트를 통해 범위 변수로 할당 할 수 있다. 이 기능은 테스트 비용이 비쌀 경우 유용하다. 결과는 범위 변수에 캐시되고 나 다른 JSTL 태그로의 연속 호출시에 검 색된다.

Listing 7. 조건 액션 신택스

body content

Listing 8은 태그의 LoopTagStatus 객체의 first 속성으로 사용된 를 보여준다. 이 경우, 그림 6 에서 보듯, Weblog 엔트리의 구현 날짜는 첫 번째 엔트리 위에 디스플레이 된다. 하지만 다른 엔트리 앞에 반복되지 않는다.

Listing 8. 를 사용하여 Weblog 엔트리 날짜 디스플레이 하기

http://crowe.wowdns.com:8000/archives/dev_note/ (48 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

그림 6. Listing 8의 결과

Output of Listing 8

Listing 8 처럼, 태그는 조건화된 콘텐트에 대해 매우 간략한 노트를 제공한다. 디스플레이 되어야하는 콘텐트가 무엇인지를 결정 해야하는 중립적인 테스트가 필요할 경우, JSTL core 라이브러리는 액션을 제공한다. 신택스는 Listing 9와 같다.

Listing 9. 액션 신택스

http://crowe.wowdns.com:8000/archives/dev_note/ (49 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

body content ... body content

테스트 되는 각 조건은 상응하는 태그에 의해 나타난다. test가 true로 평가된 첫 번째 태그의 콘텐트만 프로세스 된다. 어떤 테스트도 true로 리턴되지 않으면 태그의 바디 콘텐트가 프로세스 된다. 태그 가 선택적이라는 것을 주목하라. 태그는 최대 한 개의 중첩 태그를 가질 수 있다. 모든 테스트 가 false가 되고 어떤 액션도 나타나지 않으면 바디 콘텐트는 프로세스 되지 않는다.

Listing 10은 태그의 실행 예제이다. 여기에서 프로토콜 정보는 리퀘스트 객체에서 검색되고 간단한 스트링 비교를 사용하 여 테스트된다. 테스트 결과에 따라 상응하는 텍스트 메시지가 디스플레이된다.

Listing 10. 를 이용한 콘텐트 조건화

This is an insecure Web session. This is a secure Web session. You are using an unrecognized Web protocol. How did this happen?!

예외 처리 마지막 플로우 제어 태그는 이다. 이것은 JSP 페이지 내에서 기본적인 예외처리를 담당한다. 좀더 구체적으로 말하면 이 태그 의 바디 콘텐트 내에서 발생하는 모든 예외가 잡히면 무시된다. 하지만 예외가 발생하고 태그의 선택적인 var 애트리뷰트가 지정되면 예외는 지정된 변수로 할당되어 페이지 자체 내에서 에러 처리를 할 수 있다. Listing 11은 의 신택스이다. (예제는 Listing 18이다).

Listing 11. 실행 신택스

body content

http://crowe.wowdns.com:8000/archives/dev_note/ (50 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

URL 작동 JSTL core 라이브러리의 나머지 태그는 URL에 초점을 맞춘다. 이 중 첫 번째는 태그인데 URL 생성에 사용된다. 특히, 은 J2EE 웹 애플리케이션용 URL을 구현할 때 중요하게 쓰이는 세 가지 엘리먼트를 제공한다:

● 현재 서블릿 콘텍스트 이름이 됨

● 세션 관리를 위한 URL 재작성

● 요청 매개변수 이름과 값의 URL 인코딩

value 애트리뷰트가 사용되었다. 기본 URL을 지정하기 위해서. 태그는 필요할 경우 변형한다. 이 기본 URL이 포워드 슬래시로 시작하면 서블릿 콘텍스트 이름이 만들어진다. 구체적인 콘텍스트 이름은 context 애트리뷰트를 사용하여 제공될 수 있다. 이 애트리뷰트가 생략되 면 현재 서블릿 콘텍스트 이름이 사용된다. 서블릿 콘텍스트 이름이 개발 보다는 전개 시에 결정될 때 유용하다.

Listing 12. 작동 신택스

...

URL 재작성은 작동에 의해 자동적으로 수행된다. JSP 컨테이너가 사용자의 현재 세션 아이디를 저장하고 있는 쿠키를 검사하면 재작성은 필요없다. 쿠키가 존재하지 않으면 로 만들어진 모든 URL은 재작성되어 세션 아이디를 인코딩한다. 계속되는 요청에도 적절한 쿠키가 존재하지 않으면 은 이 아이디를 포함하기 위한 URL 재작성을 멈춘다.

var 애트리뷰트를 위해 값이 제공되면 생성된 URL은 특정 범위 변수의 값으로 할당된다. 그렇지 않다면 결과 URL은 현제 JspWriter를 사용하여 아웃풋이 된다. 결과를 직접 산출하는 기능은 태그가 값으로서 나타날 수 있도록 한다. 예를들어 HTML의 태그의 href 애트리뷰트와 같다.

Listing 13. HTML 태그용 애트리뷰트 값으로 URL 생성하기

View sitemap

마지막으로 모든 요청 매개변수가 중첩된 태그를 통해 지정되면 그들의 이름과 값은 HTTP GET 요청용 표준 표기법을 사용하 여 생성된 URL에 붙여진다. 또한 URL 인코딩이 수행된다. 유효 URL을 만들어내기 위해 변형되어야하는 매개변수의 이름 또는 값에 나타 나는 모든 문자는 적절히 변환된다.

Listing 14. 요청 매개변수를 가진 URL 만들기

http://crowe.wowdns.com:8000/archives/dev_note/ (51 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Listing 14의 JSP 코드는 blog이라는 이름의 서블릿 콘텍스트에 전개되었다. 범위 변수 searchTerm의 값은 "core library"로 설정 되었다. 세션 쿠키가 탐지되면 Listing 14에서 만들어진 URL은 Listing 15와 같다.

Listing 15. 세션 쿠키가 있는 상태에서 만들어진 URL

/blog/content/search.jsp?keyword=foo+bar&month=02%2F2003

어떤 세션 쿠키도 없으면 Listing 16의 URL이 그 결과이다. 서블릿 콘텍스트가 프리펜드 되었고 URL 인코딩이된 요청 매개변수가 붙었 다.

Listing 16. 세션 쿠키 없이 만들어진 URL

/blog/content/search.jsp;jsessionid=233379C7CD2D0ED2E9F3963906DB4290 ?keyword=foo+bar&month=02%2F2003

중요한 콘텐트 JSP는 두 개의 빌트인 메커니즘을 갖고 있어 다른 URL에서 온 콘텐트를 JSP 페이지로 만든다. 그것이 바로 include 지시문과 작동이다. 두 경우 모두, 포함되어야 하는 콘텐트가 페이지로서 같은 웹 애플리케이션의 부분이 되어야 한다. 두 가지 태그의 주 요 차이점은 include 지시문은 페이지가 컴파일하는 동안 포함된 콘텐트를 결합하고 액션은 JSP 페이지의 요청 프로세 스 동안 작동한다는 것이다.

core 라이브러리의 액션은 더욱 일반적이면서 강력한 버전의 라 할 수 있다. 처럼, 는 요청 시간 작동이고 기본 태스크는 다른 웹 리소스의 콘텐트를 JSP 페이지에 삽입하는 것이다.

Listing 17. 작동 신택스

...

임포트 되어야하는 콘텐트용 URL은 url 애트리뷰트를 통해 지정된다. 관련 URL이 허용되고 현재 페이지의 URL에 대비하여 분해된다. url 애트리뷰트의 값은 포워드 슬래시로 시작한다. 하지만 로컬 JSP 컨테이너 내에서는 절대 URL로서 인터프리팅된다. context 애트리 뷰트 값 없이는 그와 같은 절대 URL은 현재 서블릿 콘텍스트에서 리소스를 참조하는 것으로 간주된다. 명확한 콘텍스트가 context 애트 리뷰트를 통해 지정되면 절대(로컬) URL은 이름을 가진 서블릿 콘텍스트에 대해 분해된다.

액션은 로컬 콘텐트 접근에만 제한되지 않는다. 프로토콜과 호스트 이름을 포함한 전체 URI는 url 애트리뷰트의 값으로 지 정될 수 있다. 사실 프로토콜은 HTTP로 제한되지 않는다. java.net.URL 클래스로 지원되는 모든 프로토콜은 의 url 애트 리뷰트용 값에서 사용된다. (Listing 18).

http://crowe.wowdns.com:8000/archives/dev_note/ (52 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

액션은 FTP 프로토콜을 통해 액세스 되는 문서의 콘텐트를 포함하는데 사용된다. 액션은 FTP 파일 전송 동안 발 생하는 모든 에러를 처리하기 위해 사용된다. 의 var 변수를 사용하여 예외용 범위 변수를 지정하고 를 사용하여 값을 검사하면 된다. 예외가 발생하면 범위 변수로의 할당이 발생한다.

Listing 18. 의 결합 예제

Sorry, the remote content is not currently available.

액션의 마지막 두 개의 애트리뷰트는 var와 scope이다. var 애트리뷰트는 지정된 유알엘에서 가져온 콘텐트가 현재의 JSP 페이지에 포함되도록 하는 것이 아니라 변수에 저장되도록 한다. scope 애트리뷰트는 이 변수의 범위 지정을 제어하고 페이지 범위를 초 기화한다.

요청 리다이렉션(redirection) 마지막 core 라이브러리 태그는 이다. 이 액션은 HTTP 리다이렉트 응답을 사용자 브라우저로 보내는데 사용되며, JSTL 의 javax.servlet.http.HttpServletResponse의 sendRedirect() 메소드와 같다. 이 태그의 url과 context 애트리뷰트 (Listing 19) 작동은 의 url과 context 애트리뷰트와 같다.

Listing 19. action

...

Listing 20은 작동 모습이다. Listing 18의 에러 메시지를 지정된 에러 페이지 리다이렉트로 대체한다. 이 예제에서 태그는 표준 작동과 비슷한 방식으로 사용된다. 요청 디스패쳐를 통한 포워딩은 서버쪽에서 구현되지만 리 다이렉트는 브라우저에서 수행된다. 개발자의 관점에서 보면 포워딩은 리다이렉팅보다 효율적이다. 하지만 액션이 좀더 유연하다. 는 현재 서블릿 콘텍스트 내에서 다른 JSP 페이지로만 디스패치 할 수 있기 때문이다.

Listing 20. 예외에 대한 응답으로 리다이렉팅

http://crowe.wowdns.com:8000/archives/dev_note/ (53 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

사용자 관점과의 주요 차이점은 리다이렉트가 브라우저로 디스플레이된 URL을 업데이트하고 북마크 설정에 영향을 미친다는 것이다. 반 면 포워딩은 엔드유저에게 투명하다. 중의 선택은 사용자 경험에 따라 달라진다.

참고자료

● source code 다운로드.

● Sun product page for the JSP Standard Tag Library.

● JSTL 1.0 Specification.

● Jakarta Taglibs 프로젝트.

● JSTL in Action: Shawn Bayern (Manning, 2002).

● Core JSTL.

● JSPTags.com.

● Sun Java Web Services Tutorial.

● "Using JSPs and custom tags within VisualAge for Java and WebSphere Studio" (WebSphere Developer Domain).

● "Take control of your JSP pages with custom tags" (developerWorks, 2002년 1월).

● "JSP taglibs: Better usability by design" (developerWorks, 2001년 12월).

● developerWorks Java technology zone.

Posted by Crowe Lee at 06:18 PM | Comments (0) | TrackBack

A JSTL Primer, Part 1 : The Expression Language

View: A JSTL Primer, Part 1 : The Expression Language | IBM DeveloperWorks

JSP 애플리케이션용 MA 단순화하기

Level: Intermediate

Mark A. Kolb 소프트웨어 엔지니어 2003년 2월 11일

JSP Standard Tag Library (JSTL)은 일반적인 웹 애플리케이션 기능(반복(iteration)과 조건, 데이터 관 리 포맷, XML 조작, 데이터베이스 액세스)을 구현하는 커스텀 태그 라이브러리 모음이다. 소프트웨어 엔지 니어인 Mark Kolb은 JSTL 태그의 사용방법을 설명한다. 표현층(presentation layer)에서 소스 코드를 제

http://crowe.wowdns.com:8000/archives/dev_note/ (54 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

거하여 소프트웨어 관리를 단순화시키는 방법도 설명한다. 이외에도 JSTL의 단순화된 Expression Language에 대한 설명도 포함되어 있다.

JavaServer Pages (JSP)는 J2EE 플랫폼을 위한 표준 표현 레이어(presentation-layer) 이다. JSP는 페이지 콘텐트를 동적으로 생성할 수 있는 전산을 수행 할 수 있는 스크립팅 엘리먼트와 액션을 제공한다. 스크립팅 엘리먼트는 프로그램 소스 코드가 JSP 코드에 포함될 수 있도록 한다. 페이지가 사용자 요청에 대한 응답으로 렌더링 될 때 실행할 목적이다. 액션(actions)은 전산 작동을 JSP 페이지의 템플릿 텍 스트를 구성하고 있는 HTML 이나 XML과 거의 흡사하게하는 태그로 인캡슐한다. JSP 스팩에 표준으로 정의된 몇 가지의 액션들이 있다. 하지만 JSP 1.1 부터 개발자들은 커스텀 태그 라이브러리 형태로 자신만의 액션들을 만들 수 있다.

JSP Standard Tag Library (JSTL)는 JSP 1.2 커스텀 태그 라이브러리 모음으로서 광범위한 서버측 자바 애플리케이션에 일반적으로 쓰 이는 기본 기능들을 구현한다. JSTL은 데이터 포맷, 반복 콘텐트 또는 조건 콘텐트 같은 전형적인 표현 레이어를 위한 표준 구현을 제공하 기 때문에, JSP 작성자들이 애플리케이션 개발에 집중하는데 도움이 된다.

물론, 스크립틀릿, 익스프레션, 선언 같은 JSP 스크립팅 엘리먼트를 사용하는 태스크를 구현할 수 있다. 예를 들어 조건 콘텐트 (conditional content)는 세 개의 스크립틀릿(Listing 1의 하이라이트 부분)을 사용하여 구현될 수 있다. 페이지 내에 프로그램 소스 코드 를 임베딩하는 것에 의존하기 때문에 스크립팅 엘리먼트가 소프트웨어 관리 태스크를 매우 복잡하게 하는 경향이있더라도 JSP 페이지는 그들을 사용한다. Listing 1의 스크립틀릿 예제는 브레이스들의 적절한 매칭에 매우 의존한다. 조건화된 콘텐트 내에 추가 스크립틀릿을 중 첩하는 것은 신택스 에러가 갑자기 일어났다면 페이지가 JSP 콘테이너에 의해 컴파일 될 때 결과 에러 메시지를 합리화하는 것은 도전이 될 수 있다.

Listing 1. 스크립틀릿을 통해 조건 콘텐트 구현하기

<% if (user.getRole() == "member")) { %>

Welcome, member!

<% } else { %>

Welcome, guest!

<% } %>

그와 같은 프로그램을 해결하는데에는 프로그래밍 경험이 많이 필요하다. JSP 페이지의 마크업이 페이지 레이아웃과 그래픽 디자인에 익 숙한 디자이너에 의해 개발 및 관리되는데 반해 그와 같은 페이지 내의 스크립팅 엘리먼트는 문제가 생길 때 프로그래머가 개입해야한다. 하나의 파일안에 있는 코드에 대한 책임을 공유하는 것은 JSP 페이지의 개발, 디버깅, 향상을 성가신일로 만든다. JSTL은 일반적인 기능 을 커스텀 태그 라이브러리의 표준 세트로 패키징했기 때문에 JSP 작성자들이 스크립팅 엘리먼트에 대한 필요를 줄이고 관련된 관리 비용 을 피할 수 있도록 한다.

JSTL 1.0 JSTL 시리즈 2002년 6월에 릴리스된 JSTL 1.0은 네 개의 커스텀 태그 라이브러리(core, format, xml, sql)와 두 개의 범 용 태그 라이브러리 밸리데이터(ScriptFreeTLV & PermittedTaglibsTLV)로 구성되어 있다. core 태그 라이 Part 2, "JSTL 기 브러리는 커스텀 액션을 제공하여 범위 변수를 통해 데이터를 관리할 수 있도록하며 페이지 콘텐트의 반복과 조건 초, Part 2 : 핵심에 화를 수행할 수 있도록 한다. 또한 URL에서 생성 및 작동할 수 있는 태그도 제공한다. format 태그 라이브러리 접근하기" (2003년 는 이름이 시사하는 바와 같이 데이터 특히 숫자와 날짜를 포맷하는 액션을 정의한다. 국지화 된 리소스 번들을 사 월 용하여 JSP 페이지의 국제화도 지원한다. xml 라이브러리에는 XML을 통해 표현된 데이터를 조작할 수 있는 테그 3 )

가 포함되어 있다 sql 라이브러리는 관계형 데이터베이스를 쿼리하는 액션을 정의한다 . . Part 3, "Presentation is 두 개의 JSTL 태그 라이브러리 밸리데이터는 개발자들이 JSP 애플리케이션 내에서 표준을 코딩하도록 한다. everything" (2003 ScriptFreeTLV 밸리데이터를 설정하여 JSP 페이지 내에 있는 다양한 스크립팅 엘리먼트(스크립틀릿, 표현, 선 년 4월) 언)의 다양한 유형을 사용하는 것을 막는다. 이와 비슷하게 PermittedTaglibsTLV 밸리데이터는 애플리케이션 의 JSP 페이지들에 의해 액세스된 커스텀 태그 라이브러리(JSTL 태그 라이브러리 포함)을 제한한다. Part 4,

http://crowe.wowdns.com:8000/archives/dev_note/ (55 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

"Accessing SQL JSTL은 J2EE 플랫폼에 필요한 컴포넌트가 될 것이지만 적은 수의 애플리케이션 서버들만이 이를 포함하고 있는 and XML 것이 현실이다. JSTL 1.0의 레퍼런스 구현은 Apache Software Foundation의 Jakarta Taglibs 프로젝트의 일 content" (2003년 부로서 사용할 수 있다. (참고자료). 레퍼런스 구현에 있는 커스텀 태그 라이브러리는 JSTL 지원을 추가하기 위해 5월) JSP 1.2와 Servlet 2.3 이상 스팩을 지원하는 모든 애플리케이션 서버에 통합될 수 있다.

Expression language JSP 1.2에서 JSP 액션의 애트리뷰트는 정적 캐릭터 스트링이나 익스프레션을 사용하여 지정된다. 예를 들어 Listing 2의 경우 정적 값들 은 액션의 name과 property 애트리뷰트를 위해 지정된다. 반면 익스프레션은 이것의 값 애트리뷰트를 지정하는 데 사용된다. 이 액션은 요청 매개변수의 현재 값을 이름이 붙여진 빈 속성으로 할당하는 효과를 갖고 있다. 이러한 방식으로 사용된 익스 프레션은 request-time attribute values이라 일컬어지며 애트리뷰트 값을 동적으로 지정하기위한 JSP 스팩에 내장된 유일한 메커니즘 이다.

Listing 2. request-time attribute value을 결합하는 JSP 액션

request-time attribute values가 익스프레션을 사용하여 지정되기 때문에 다른 스크립팅 엘리먼트와 같은 소프트웨어 관리 문제가 일어 날 수 있다. 이런 이유로 인해 JSTL 커스텀 태그는 동적 애트리뷰트 값을 지정하기 위한 대안 메커니즘을 지원한다. JSP 익스프레션을 사 용하는 것 보다 JSTL 액션용 애트리뷰트 값이 단순화 된 expression language (EL)를 사용하여 지정될 수 있다. EL은 JSP 컨테이너에 있는 데이터를 검색 및 조작할 식별자, 접근자, 연산자를 제공한다. EL은 EcmaScript(참고자료)와 XML Path Language (XPath)에 약 간 의존하기 때문에 신택스는 페이지 디자이너와 프로그래머 모두 에게 익숙하다. EL은 객체와 속성들을 검색하면서 간단한 작동을 수행한 다. 이것은 프로그래밍 언어도 스크립팅 언어도 아니다. JSTL 태그와 결합하면 간단하고 편리한 표기를 사용하여 복잡한 작동이 표현될 수 있다. EL 익스프레션은 달러 표시($)와 중괄호 ({})를 앞에 붙여 사용하여 범위를 정한다.(Listing 3)

Listing 3. JSTL 액션: EL 익스프레션 범위 지정

여러개의 익스프레션들과 정적 텍스트를 결합하여 스트링 연결을 통해 동적 애트리뷰트 값을 만들 수 있다.(Listing 4). 개별 익스프레션들 은 식별자, 접근자, 리터럴, 연산자로 구성되어 있다. 식별자는 데이터 센터에 저장된 데이터 객체를 참조하는데 사용된다. EL은 11 개의 식별자를 보유하고 있다. 11 개의 EL 내장 객체에 상응하는 것들이다. 다른 모든 식별자들은 범위 변수를 참조하는 것으로 간주된다. 접근 자는 객체의 속성 또는 컬렉션의 엘리먼트를 검색하는데 사용된다. 리터럴은 고정된 값들(숫자, 문자, 스트링, 부울, null)을 나타낸다. 연 산자는 데이터와 리터럴이 결합 및 비교될 수 있도록 한다.

Listing 4. 정적 텍스트와 여러 EL 익스프레션을 결합하여 동적 애트리뷰트 값 지정하기

범위 변수(Scoped variables) 액션을 통한 JSP 에이피아이는 데이터가 저장될 수 있도록 하며 JSP 컨테이너 내에 네 개의 다른 범위에서 데이터가 검 색될 수 있도록 한다. JSTL은 이러한 범위 내에 객체를 할당하고 제거할 추가 액션을 제공한다. 더욱이, EL은 범위 변수 같은 객체들을 검

http://crowe.wowdns.com:8000/archives/dev_note/ (56 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

색하는 빌트인 지원을 제공한다. 특히 EL의 내장 객체 중 하나라도 상응하지 않는 EL 익스프레션에 있는 식별자는 네 개의 JSP 스콥 중 하 나에 저장된 객체를 참조하는 것으로 자동 간주된다:

● 페이지 범위

● 요청 범위

● 세션 범위

● 애플리케이션 범위

페이지 범위에 저장된 객체들은 특정 요청에 대한 페이지가 프로세스 되는 동안 검색될 수 있다. 요청 범위 내에 저장된 객체들은 요청 프 로세스에 참여한 모든 페이지들이 프로세스 하는 동안 검색될 수 있다. 객체가 세션 범위에 저장되어있다면 웹 애플리케이션과의 단일 인 터랙트브 세션 동안 사용자가 액세스 한 페이지로 검색될 수 있다. 웹 애플리케이션이 언로드(unload) 될 때 까지 애플리케이션 범위에 저 장된 객체는 모든 페이지에서 접근가능하며 모든 사용자들이 접근할 수 있다.

캐릭터 스트링을 희망하는 범위에 있는 객체로 매핑하여 범위안에 객체를 저장할 수 있다. 이러한 경우에는 같은 캐릭터 스트링을 제공하 여 범위에서 객체를 검색할 수도 있다. 스트링은 범위 매핑 중 검색되고 매핑된 객체는 리턴된다. Servlet API 내에서 그와 같은 객체들은 상응하는 범위의 애트리뷰트로서 언급된다. EL의 경우 애트리뷰트와 관련된 캐릭터 스트링은 변수 이름으로 간주될 수도 있다.

EL에서 내장 객체들과 관련이 없는 식별자들은 JSP 범위에 저장된 객체들을 명명하는 것으로 간주된다. 그와 같은 식별자는 페이지 범위 를 검사하고 그 다음에는 요청 범위, 세션 범위, 애플리케이션 범위 순으로 검사한다. 식별자의 이름이 그 범위에 저장된 객체 이름과 매칭 되는지의 여부가 테스트된다. 첫 번째 매치는 EL 식별자의 값으로 리턴된다. EL 식별자는 범위 변수를 참조하는 것으로 간주될 수 있다.

기술적인 관점에서 보면 내장 객체로 매핑하지 않는 식별자는 PageContext 인스턴스의 findAttribute() 메소드를 사용하여 평가되면 서 현재 핸들되는 요청에 대해 익스프레션이 발생하는 페이지의 프로세싱을 나타낸다. 식별자의 이름은 이 메소드에 대한 인자로서 전달된 다. 이것은 같은 이름을 가진 애트리뷰트에 대한 네 개의 범위를 검색한다. 발견된 첫 번째 매치는 findAttribute() 메소드 값으로 리턴 된다. 그와 같은 애트리뷰트가 네 개의 범위 중에 없으면 null이 리턴된다.

궁극적으로 범위 변수는 네 개의 EL 식별자로서 사용될 수 있는 이름을 가진 JSP 범위의 에트리뷰트라고 할 수 있다. 영숫자 이름으로 할당 되는 한 범위 변수는 JSP 에 존재하는 모든 메커니즘으로 만들어져 애트리뷰트를 설정할 수 있다. 여기에는 빌트인 액션 은 물론 setAttribute() 메소드가 포함된다. 게다가 네 개의 JSTL 라이브러리에서 정의된 많은 커스텀 태그들은 스스로 범위 변수로서 애트리뷰트 값을 설정할 수 있다.

내장 객체(Implicit objects) 11 개의 EL 내장 객체용 식별자는 표 1과 같다. JSP 내장 객체와 혼동하지 말것!

표 1. EL 내장 객체

Category 식별자 설명

JSP pageContext 현재 페이지의 프로세싱과 상응하는 PageContext 인스턴스

범위 pageScope 페이지 범위 애트리뷰트 이름과 값과 관련된 Map

requestScope 요청 범위 애트리뷰트 이름과 값과 관련된 Map

sessionScope 세션 범위 애트리뷰트 이름과 값과 관련된 Map

applicationScope 애플리케이션 범위 애트리뷰트 이름과 값과 관련된 Map

요청 매개변수 param 요청 매개변수의 기본 값을 이름으로 저장하는 Map

http://crowe.wowdns.com:8000/archives/dev_note/ (57 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

paramValues 요청 매개변수의 모든 값을 String 어레이로서 저장하는 Map

요청 헤더 header 요청 헤더의 기본 값을 이름으로 저장하는 Map

headerValues 요청 헤더의 모든 값을 String 어레이로서 저장하는 Map

쿠키 cookie 요청에 수반되는 쿠키들을 이름으로 저장하는 Map

초기화 매개변 initParam 웹 애플리케이션의 콘텍스트 초기화 매개변수를 이릉으로 저장하는 Map 수

JSP와 EL 내장 객체가 일반적인 하나의 객체를 갖는 반면(pageContext) 다른 JSP 내장 객체는 EL에서 접근 가능하다. 페이지콘텍스트 가 다른 8 개의 JSP 내장 객체 모두에 액세스 할 수 있는 속성을 갖고 있기 때문이다.

남아있는 모든 EL 내장 객체들은 맵(map)이다. 이름에 상응하는 객체들을 탐색한다. 첫 번째 네 개의 맵은 이전에 언급한 다양한 애트리 뷰트 범위를 나타낸다. 특정 범위 내의 식별자들을 검색하는데 사용될 수 있다. EL이 기본적으로 사용하는 순차적인 탐색 프로세스에 의존 하지 않는다.

다음 네 개의 맵은 요청 매개변수와 헤더의 값을 반입하는 용도이다. HPPT 프로토콜이 요청 매개변수와 헤더가 다중 값을 가질 수 있도록 하기 때문에 각각 한 쌍의 맵이 있다. 각 쌍 중에서 첫 번째 맵은 요청 매개변수 또는 헤더에 대한 기본 값을 리턴한다. 실제 요청 시 첫 번 째로 지정된 값이 무엇이든 상관없다. 두 번째 맵은 매개변수나 헤더의 값 모두 검색될 수 있도록 한다. 이 맵의 핵심은 매개변수 또는 헤더 의 이름이다. 값들은 String 객체의 어레이이다.

쿠키 내장 객체는 요청으로 설정된 쿠키에 대한 접근을 제공한다. 이 객체는 요청과 관련된 모든 쿠키들의 이름을 Cookie 객체들로 매핑하 면서 쿠키들의 속성을 나타낸다.

마지막 EL 내장 객체인 initParam은 웹 애플리케이션과 관련된 모든 콘텍스트 초기와 매개변수의 이름과 값을 저장하는 맵이다. 초기화 매개변수들은애플리케이션의 WEB-INF 디렉토리에 있는 web.xml 전개 디스크립터 파일을 통해 정의된다.

접근자(Accessors) EL 식별자는 내장 객체 또는 범위 변수로서 설명될 수 있기 때문에 자바 객체로 평가해야한다. EL은 상응하는 자바 클래스에서 프리머티브 를 래핑/언래핑한다. 하지만 대부분의 경우 식별자들은 자바 객체에 대한 포인터가 된다.

결과적으로 이러한 객체들의 속성이나, 어레이와 컬렉션의 경우 그들의 엘리먼트에 액세스하는 것이 바람직하다. 이를 위해 EL은 두 개의 다른 접근자를 제공한다. 닷(dot) 오퍼레이터(.)와 브래킷 오퍼레이터([])이다. 이들은 속성과 엘리먼트들이 EL을 통해 연산될 수 있도 록 한다.

닷 연산자는 객체의 프로퍼티에 접근하는데 사용된다. ${user.firstName} 익스프레션에서 닷 연산자는 user 식별자에 의해 참조된 객 체 중 firstName이라는 이름을 가진 속성에 액세스 한다. EL은 자바 빈 규정을 사용하여 객체 속성에 접근하기 때문에 이 속성에 대한 게 터(일반적으로 getFirstName())는 이 익스프레션이 정확히 계산하기 위해서 반드시 정의되어야 한다. 액세스되는 속성이 객체일 때 닷 연산자는 재귀적으로 적용될 수 있다. 예를 들어 가상의 user 객체가 자바 객체로서 구현된 address 속성을 갖고 있다면 닷 연산자는 이 객체의 속성에 액세스 하기 위해 사용될 수도 있다. ${user.address.city} 익스프레션은 이 address 객체 중 중첩된 city 속성을 리 턴한다.

브래킷 연산자는 어레이와 컬렉션의 엘리먼트를 검색하는데 사용된다. 어레이와 컬렉션(java.util.List를 구현하는 컬렉션)의 경우 검 색될 엘리먼트 인덱스는 브래킷 안에 나타난다. 예를 들어 ${urls[3]} 익스프레션은 이 urls 식별자에 의해 참조된 어레이 또는 컬렉션 의 네 번째 엘리먼트를 리턴한다.

java.util.Map 인터페이스를 구현하는 컬렉션의 경우 브래킷 연산자는 관련 키를 사용하여 맵에 저장된 값을 찾는다. 이 키는 브래킷 내 http://crowe.wowdns.com:8000/archives/dev_note/ (58 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

에서 지정되고 상응하는 값은 익스프레션 값으로 리턴된다. 예를 들어 ${commands["dir"]} 익스프레션은 commands 식별자에 의해 참 조된 Map의 "dir" 키와 관련된 값을 리턴한다.

익스프레션이 브래킷안에 나타날 수 있다. 중첩된 익스프레션의 계산 결과는 컬렉션이나 어레이의 적절한 엘리먼트를 검색하는 인덱스 또 는 키로 작용한다. 닷 연산자가 true라면, 브래킷 연산자도 재귀적으로 적용될 수 있다. 이는 EL이 다차원 어레이, 중첩 컬렉션, 또는 둘의 결합에서 엘리먼트를 검색 할 수 있도록 한다. 더욱이 닷 연산자와 브래킷 연산자는 상호운용성이 있다. 예를들어 한 어레이의 엘리먼트가 객체라면 브래킷 연산자는 그 어레이의 엘리먼트를 검색하는데 사용될 수 있고 닷 연산자와 결합하여 엘리먼트 속성 중 하나를 검색할 수 있다. (예를 들어 ${urls[3].protocol}).

EL이 동적 애트리뷰트 값을 정의하는 간한한 언어로서 작용한다고 볼 때, 자바 접근자와는 다른 EL 접근자의 재미있는 특성 중 하나는 null에 적용될 때 예외를 던지지 않는다는 점이다. EL 접근자가 적용되는 객체(예를 들어 ${foo.bar}와 ${foo["bar"]}의 foo 식별 자)가 null이면 접근자 적용 결과 역시 null이다. 이는 대부분의 경우, 도움이 되는 일이다.

마지막으로 닷 연산자와 브래킷 연산자는 상호 교환될 수 있다. 예를 들어 ${user["firstName"]}은 user 객체의 firstName 속성을 검색하는데 사용될 수 있다. ${commands.dir}가 commands 맵에서 "dir" 키와 관련된 값을 반입하는데 사용될 수 있는것과 같은 이치 이다.

연산자(Operators) 식별자와 접근자를 사용하여 EL은 애플리케이션 데이터(범위 변수를 통해 노출) 또는 환경 관련 정보(EL 내장 객체를 통해 노출)를 포함하 고 있는 객체 계층을 트래버스 할 수 있다. 그와 같은 데이터에 간단히 접근하는 것은 많은 JSP 애플리케이션에 필요한 표현 로직을 구현하 는데 종종 부적합하다.

EL에는 EL 익스프레션으로 접근된 데이터를 조작 및 비교할 여러 연산자를 포함하고 있다. 이러한 연산자들을 표 2에 요약했다.

표 2. EL 연산자

Category 연산자

산술 +, -, *, / (or div), % (or mod)

관계형 == (or eq), != (or ne), < (or lt), > (or gt), <= (or le), >= (or ge)

논리 && (or and), || (or or), ! (or not)

타당성검사 empty

산술 연산자는 더하기, 빼기, 나누기를 지원한다. 다른 연산자들도 제공된다. 나누기와 나머지 연산자들은 비 상징 이름들이라는 대안을 갖 고 있다. 산술 연산자의 사용법을 설명하는 예제 익스프레션은 Listing 5에 설명되어 있다. 산술 연산자를 한 쌍의 EL 익스프레션에 적용 한 결과는 그러한 익스프레션에 의해 리턴된 숫자 값에 대한 연산자에 적용한 결과이다.

Listing 5. 산술 연산자를 사용하는 EL 익스프레션

${item.price * (1 + taxRate[user.address.zipcode])}

관계형 연산자는 숫자 또는 텍스트 데이터를 비교할 수 있도록 한다. 비교 결과는 부울 값으로서 리턴된다. 논리적 연산자는 부울 값이 결 합될 수 있도록 하며 새로운 부울 값을 리턴한다. EL 논리적 연산자는 중첩된 관계형 연산자 또는 논리적 연산자의 결과에 적용될 수 있다. (Listing 6).

http://crowe.wowdns.com:8000/archives/dev_note/ (59 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Listing 6. 관계형 연산자 및 논리적 연산자를 사용하는 EL 익스프레션

${(x >= min) && (x <= max)}

EL 연산자는 empty 이다. 데이터의 타당성 검사에 특히 유용하다. empty 연산자는 하나의 익스프레션을 인자로 취한다.(${empty input}). 그리고 익스프레션이 empty 값으로 계산했는지의 여부를 나타내는 부울 값을 리턴한다. null로 계산한 익스프레션은 empty 로 간주된다. 어떤 엘리먼트도 없는 컬렉션이나 어레이와 같다. empty 연산자는 인자가 길이가 0인 String으로 계산했다면 true로 리턴 한다.

EL 연산자의 우선순위는 표 3에 정리되어 있다. Listing 5와 6에 제안된 것 처럼 괄호는 그룹 익스프레션에 사용되고 일반적인 우선순위 를 따른다.

표 3. EL 연산자 우선순위 (위->아래, 왼쪽->오른쪽)

[], .

()

unary -, not, !, empty

*, /, div, %, mod

+, binary -

() <, >, <=, >=, lt, gt, le, ge

==, !=, eq, ne

&&, and

||, or

리터럴(Literals) 숫자, 캐릭터 스트링, 부울, null은 EL 익스프레션에서 리터럴 값으로 지정될 수 있다. 캐릭터 스트링은 싱글 쿼트 또는 더블 쿼트로 범위 가 지정된다. 부울 값은 true와 false로 계산된다.

Taglib 지시문 앞서 언급했지만 JSTL 1.0에는 네 개의 커스텀 태그 라이브러리가 포함되어 있다. 익스프레션 언어로 JSTL 태그의 인터랙션을 설명하기 위해 JSTL core 라이브러리에서 여러 태그들을 검토할 것이다. 모든 JSP 커스텀 태그 라이브러리로 true가 된다면 taglib 지시문은 이 라이브러리 태그를 사용할 수 있는 페이지에 포함되어야한다. 이 특정 라이브러리에 대한 지시문은 Listing 7에 나타나있다.

Listing 7. JSTL core 라이브러리의 EL 버전용 테그립 지시문

<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>

실제로 JSTL core 라이브러리에 상응하는 두 개의 Taglib 지시문이 있다. JSTL텐에서 EL은 옵션이기 때문이다. JSTL 1.0 의 네 개의 커 스텀 태그 라이브러리들은 동적 애트리뷰트 값을 지정할 때 EL 대신 JSP 익스프레션을 사용하는 대안 버전을 갖고있다. 이러한 대안 라이 브러리는 JSP의 전통적인 요청시간 애트리뷰트 값에 의존하기 때문에 RT 라이브러리로 일컬어진다. 반면 익스프레션 언어를 사용하는 것 http://crowe.wowdns.com:8000/archives/dev_note/ (60 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

은 EL 라이브러리라고 한다. 개발자들은 대안 Taglib 지시문을 사용하는 각각의 라이브러리의 버전들을 구별한다. RT 버전의 코어 라이 브러리를 사용하기 위한 지시문은 Listing 8에 나와있다. 하지만 지금은 EL에 집중해야 하기 때문에 지금 필요한 것은 이 지시문들 중 첫 번째 것이다.

Listing 8. RT 버전의 JSTL core 라이브러리용 태그립 지시문

<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c_rt" %>

변수 태그 첫 번째 JSTL 커스텀 태그는 액션이다. 이미 언급했듯이 범위 변수는 JSTL에서 핵심적인 역할을 하고 액션은 태그 기 반의 매커니즘을 제공하여 범위 변수의 생성 및 설정에 쓰인다. 이 액션의 신택스는 Listing 9와 같다. var 애트리뷰트는 범위 변수 이름 을 정하고 scope 애트리뷰트는 변수가 머물게 될 범위를 나타내고, value 애트리뷰트는 변수가 될 값을 지정한다. 지정된 변수가 이미 존 재하면 지시된 값으로 할당된다. 그렇지 않다면 새로운 범위 변수가 만들어지고 그 값으로 초기화된다.

Listing 9. 액션 신택스

scope 애트리뷰트는 선택적이며 page로 기본 설정되어 있다.

의 두 예제는 Lisitng 10에 설명되어 있다. 첫 번째 예제에서 세션 범위 변수는 String 값으로 설정된다. 두 번째에서는 익스프 레션은 숫자 값을 설정하는데 사용된다. square라는 페이지 범위 변수는 x 라는 요청 매개변수 값을 배가시킨 결과로 할당된다.

Listing 10. 액션 예제

애트리뷰트를 사용하는 대신 범위 변수용 값을 액션의 바디 콘텐트로 설정할 수 있다. 이러한 접근방식을 사용하여 Listing 10 의 첫 번째 예제를 Listing 11과 같이 재작성할 수 있다. 더욱이 태그의 바디 콘텐트가 커스텀 태그를 적용하는 것도 가능하다. 의 바디 안에서 만들어진 모든 콘텐트는 String 값 같이 지정된 변수에 할당된다..

Listing 11. 바디 콘텐트를 통해 액션용 값 지정하기

CST

JSTL core 라이브러리에는 범위 변수를 관리하는 두 번째 태그()가 포함되어 있다. 이름에서 시사되는 바와 같이 액션은 범위 변수를 지우는데 사용되고 두 개의 애트리뷰트를 취한다. var 애트리뷰트는 제거될 변수를 명명하고 선택적인 scope 애트리뷰트는 제거되어야 할 범위를 나타낸다. (Listing 12).

http://crowe.wowdns.com:8000/archives/dev_note/ (61 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Listing 12. 액션 예제

아웃풋 액션은 익스프레션의 결과가 범위 변수로 할당될 수 있도록 하는 반면 개발자들은 익스프레션 값을 저장하는 대신 간단히 디스플 레이하기를 원한다. 이는 JSTL의 커스텀 태그의 몫이다. (Listing 13). 이 태그는 value 애트리뷰트에서 지정된 익스프레션을 계산한다. 그런다음 결과를 프린트한다. 선택적 default 애트리뷰트가 지정되면 value 애트리뷰트의 익스프레션이 null 또는 비어있는 String으로 계산될 때 액션은 값을 프린트한다.

Listing 13. 액션 신택스

escapeXml 애트리뷰트 또한 선택사항이다. "<", ">", "&" 같은 캐릭터가 태그에 의해 아웃풋 될 때 종료되는지의 여부를 제어 한다. escapeXml이 true로 설정되어 있다면 이 캐릭터들은 상응하는 XML 인터티(<, >, &)로 바뀐다.

예를 들어, user라는 세션 범위 변수가 있다고 가정해보자. 이것은 사용자에 대한 username과 company라는 두 개의 속성들을 정의하는 클래스의 인스턴스이다. 이 객체는 사용자가 사이트에 접근할 때마다 세션에 할당된다. 하지만 이 두 개의 속성들은 사용자가 실제로 로그 인하기 전까지 설정되지 않는다. (Listing 14). 일단 사용자가 로그인하면 "Hello"가 디스플레이 되고 뒤따라서 사용자 이름과 감탄부호가 나온다. 사용자가 로그인하기 전에 여기에서 생긴 콘텐트는 "Hello Guest!" 라는 구(phrase)가 된다. 이 경우 username 속성이 초기화되 지 않았기 때문에 태그는 default 애트리뷰트 값을 프린트한다.

Listing 14. 액션 예제 (디폴트 콘텐트)

Hello !

태그의 escapeXml 애트리뷰트를 사용하는 Listing 15를 보자. company 속성이 자바 String 값인 "Flynn & Sons"으로 설 정되었다면 이 액션에서 생긴 콘텐트는 Flynn & Sons이 된다. 이 액션이 HTML 또는 XML 콘텐트를 만드는 JSP 페이지의 일부라면 이 캐릭터의 스트링 중간에 있는 앰퍼샌트 부호는 HTML 또는 XML이 문자를 제어하고 이 콘텐트의 렌더링 또는 파싱을 방해하는것으로 해석 하고 끝난다. escapeXml 애트리뷰트의 값이 true로 설정되면 생성된 콘텐트는 Flynn & Sons이 된다. 이 콘텐트를 만나는 브라우 저 또는 파서는 인터프리테이션에 아무 문제가 없다. HTML과 XML이 JSP 애플리케이션에서 가장 일반적인 콘텐트 유형이라면 escapeXml 애트리뷰트의 디폴트 값이 true라는 것은 놀라운 일이 아니다.

Listing 15. 액션 예제

디폴트 값으로 변수 설정하기 동적 데이터를 단순하게 하는 것 외에도 디폴트 값을 지정하는 의 기능은 을 통해 변수 값을 설정할 때에도 유용하다. 범

http://crowe.wowdns.com:8000/archives/dev_note/ (62 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

위 변수에 할당된 값이 태그의 바디 콘텐트로 지정될수 있고 value 애트리뷰트로서도 가능하다. 액션을 태그 의 바디 콘텐트에 중첩하여 변수 할당은 이것의 디폴트 값을 이용할 수 있다. (Listing 11).

이러한 접근 방식은 Listing 16에도 설명되어 있다. 외부 태그의 작동은 단순하다.

Listing 16. 결합: 디폴트 변수 값 제공

요청에 제공된 tzPref 라는 이름의 쿠키가 없다. 내장 객체를 사용한 검색은 null이 된다는 것을 의미한다. 익스프레션은 전체적으로 null을 리턴한다. value 애트리뷰트를 계산한 값이 null 이기 때문에 태그는 default 애트리뷰트를 계산한 결과를 아웃풋한 다.

참고자료

● Sun JSP Standard Tag Library page.

● JSTL 1.0 Specification .

● Jakarta Taglibs .

● JSTL in Action : Shawn Bayern (Manning Publications Co., 2002).

● David Geary, Core JSTL.

● JSPTags.com.

● Sun Java Web Services Tutorial.

● "Using JSPs and custom tags within VisualAge for Java and WebSphere Studio" (WebSphere Developer Domain).

● " 커스텀 태그로 JSP 페이지 제어하기 " (developerWorks, 2002년 1월).

● Noel Bergman, "JSP taglibs: Better usability by design" (developerWorks, 2001년 12월).

● Sing Li "Quick-and-dirty Java programming" (developerWorks, 2001년 7월).

● developerWorks Java technology zone.

Posted by Crowe Lee at 06:16 PM | Comments (0) | TrackBack

January 07, 2005

http://crowe.wowdns.com:8000/archives/dev_note/ (63 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

comp.lang.javascript FAQ - Quick Answers 8.0

View: comp.lang.javascript

1 meta-FAQ meta-questions 2 comp.lang.javascript tips 2.1 Which newsgroup deals with javascript? 2.2 What questions are off-topic for clj? 2.3 What do I have to do before posting to clj? 2.4 Why was my post not answered? 2.5 How do I direct someone to this FAQ? 2.6 What is ECMAScript? 2.7 What is JScript? 2.8 What are object models? 2.9 What is the document object model? 2.10 Internationalisation in javascript. 2.11 I have a question that is not answered in here or in any of the resources mentioned here but I'm sure it has been answered in clj. Where are the clj archives located? 2.12 What does the future hold for ECMAScript? 3 javascript resources 3.1 What books cover javascript? 3.2 What online resources are available? 4 Quick Answers 4.1 How do I protect my javascript code? 4.2 How can I disable the back button in a web browser? 4.3 How can I access the client-side filesystem? 4.4 How can I see in javascript if a web browser accepts cookies? 4.5 How can I protect a webpage in javascript? 4.6 How do I convert a Number into a String with exactly 2 decimal places? 4.7 Why does 5 * 1.015 != 5.075 or 0.05+0.01 != 0.06? 4.8 How do I communicate between frames in a web browser? 4.9 How do I find the size of a browser window? 4.10 How do I check to see if a childwindow is open, before opening another? 4.11 Why does framename.print() not print the correct frame in IE? 4.12 Why does parseInt('09') give an error? 4.13 How do I get the value of a form control? 4.14 How do I close a window and why does it not work on the first one? 4.15 How do I modify the current page in a browser? 4.16 How do I trim whitespace - LTRIM/RTRIM/TRIM? 4.17 How do I force a reload from the server/prevent caching? 4.18 How do I get a perl/asp/php variable into client-side js? 4.19 Why do I get permission denied when accessing a frame/window? 4.20 How do I make a 10 second delay? 4.21 Why does 1+1 equal 11? or How do I convert a string to a number? 4.22 How do I generate a random integer in [1..N]? 4.23 How do I change print settings with window.print()? 4.24 I have what ... ? 4.25 My element is named myselect[] , how do I access it?

http://crowe.wowdns.com:8000/archives/dev_note/ (64 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

4.26 How do I detect Opera/Netscape/IE? 4.27 How do I disable the right mouse button? 4.28 How do I change the confirm box to say yes/no or default to cancel? 4.29 How do I log-out a user when they leave my site? 4.30 How do I format Last Modified date with javascript 4.31 Why are my Rollovers so slow? 4.32 How do I change the text in the url/location bar? 4.33 How do I prompt a "Save As" dialog for an accepted mime type? 4.34 How do I run a server side script? 4.35 I have window.status="Moomin"; why doesn't the statusbar change? 4.36 How do I modify the current browser window? 4.37 How do I POST a form to a new window? 4.38 How do I download a page to a variable? 4.39 How do I access a property of an object using a string? 4.40 When should I use eval? 4.41 Why doesn't the global variable "divId" always refer to the element with id="divId"? 4.42 How do I open a new window with javascript? 4.43 How do I get my browser to report javascript errors? 5 Comments and Suggestions 5.1 Why do some posts have in them ? 5.2 How do I make a suggestion?

1 meta-FAQ meta-questions

You are reading the _comp.lang.javascript_ meta-FAQ, version 8.0 it is available on the web at in html form.

This is the official _comp.lang.javascript_ (clj) FAQ. Or, more accurately, the meta-FAQ as it mainly provides URLs to further information about javascript and some hints and tips to make your stay in comp.lang.javascript more enjoyable.

This document was created to help reduce the high levels of noise on clj and to provide a resource for people new to javascript or who want to know locations of valuable javascript resources.

The FAQ will currently be posted in two parts, one part (the Quick Answers) on Mondays and Fridays, with the remainder posted on wednesdays.

To cope with a desire for additional explanation and detail relating to some aspects of the FAQ without increasing its size beyond what would be reasonable to post to the group an additional resource consisting of official notes on the FAQ is available at:

2 comp.lang.javascript tips

http://crowe.wowdns.com:8000/archives/dev_note/ (65 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

see Or Wednesdays FAQ posting.

3 javascript resources

see Or Wednesdays FAQ posting.

4 Quick Answers

4.1 How do I protect my javascript code?

In the main you don't, as the language is distributed in source form, you need to deliver the source code, with JScript, there is the Script Encoder (see MSDN) but this is nothing more than obfuscation in effect, disabling the Right Mouse button, also achieves nothing to protect your script in a web browser.

Your code is likely protected under copyright laws. See:

4.2 How can I disable the back button in a web browser?

You can't. The browser's history cannot be modified. You can however use location.replace(url) in some browsers to replace the current page in the history.

4.3 How can I access the client-side filesystem?

Security means that by default you can't. In a more restricted enviroment, there are options (e.g. live connect to Java in NN, and using FileSystemObject in IE) check for previous posts on the subject.

4.4 How can I see in javascript if a web browser accepts cookies?

Writing a cookie, reading it back and checking if it's the same.

Additional Notes:

4.5 How can I protect a webpage in javascript?

http://crowe.wowdns.com:8000/archives/dev_note/ (66 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

In practice you can't, Whilst you could create a suitable encryption system with a password in the page, the level of support you need to do this means it's always simpler to do it serverside. Anything that "protects" a page other than the current one is definitely flawed

4.6 How do I convert a Number into a String with exactly 2 decimal places?

When formatting money for example, to format 6.57634 to 6.58, 6.5 to 6.50, and 6 to 6.00?

Rounding of x.xx5 is uncertain, as such numbers are not represented exactly.

N = Math.round(N*100)/100 only converts N to a Number of value close to a multiple of 0.01; but document.write(N) does not give trailing zeroes.

ECMAScript Ed. 3.0 (JScript 5.5 (but buggy) and JavaScript 1.5) introduced N.toFixed, the main problem with this is the bugs in JScripts implementation.

Much code for trailing zeros fails for some numbers (e.g. 0.07). The following works successfully :

function Stretch(Q, L, c) { var S = Q if (c.length>0) while (S.length

function StrU(X, M, N) { // X>=0.0 var T, S=new String(Math.round(X*Number("1e"+N))) if (S.search && S.search(/\D/)!=-1) { return ''+X } with (new String(Stretch(S, M+N, '0'))) return substring(0, T=(length-N)) + '.' + substring(T) }

function Sign(X) { return X<0 ? '-' : ''; }

function StrS(X, M, N) { return Sign(X)+StrU(Math.abs(X), M, N) }

Number.prototype.toFixed= new Function('n','return StrS(this,1,n)')

4.7 Why does 5 * 1.015 != 5.075 or 0.05+0.01 != 0.06?

Javascript numbers are represented in binary as IEEE-754 Doubles, with a resolution of 53 bits, giving an accuracy of 15- 16 decimal digits; integers up to about 9e15 are precise, but few decimal fractions are. Given this, arithmetic is as exact as possible, but no more. Operations on integers are exact if the true result and all intermediates are integers within that range.

In particular, non-integer results should not normally be compared for equality; and non-integer computed results generally need rounding; see 4.6.

http://crowe.wowdns.com:8000/archives/dev_note/ (67 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

4.8 How do I communicate between frames in a web browser?

To reference another frame, you simply need to step through the frame hierarchy, parent is the page the frame is defined in, parent.framename is another frame in the same frameset as the frame you are in. So to access a variable called Moomin in a frame called Snork that is in the same frameset you're in you would use parent.Snork.Moomin, to call the function Snufkin in that frame you would use parent.Snork.Snufkin() .

4.9 How do I find the size of a browser window?

Where supported in NN: (>NN4.0)

var winWidth = window.innerWidth; var winHeight = window.innerHeight;

Where supported in IE: (>IE4.0)

var winWidth = document.body.clientWidth; var winHeight = document.body.clientHeight;

When using IE6 with in CSS1Compat mode (with a Formal DOCTYPE):

var winWidth = document.documentElement.clientWidth var winHeight = document.documentElement.clientHeight

Combined:

d=document if (typeof window.innerWidth!='undefined') { var winWidth = window.innerWidth; var winHeight = window.innerHeight; } else { if (d.documentElement && typeof d.documentElement.clientWidth!='undefined' && d.documentElement.clientWidth!=0) { var winWidth = d.documentElement.clientWidth var winHeight = d.documentElement.clientHeight } else { if (d.body && typeof d.body.clientWidth!='undefined') { var winWidth = d.body.clientWidth var winHeight = d.body.clientHeight } } }

http://crowe.wowdns.com:8000/archives/dev_note/ (68 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

4.10 How do I check to see if a childwindow is open, before opening another?

var myWin=null; function openWin(aURL) { if (!myWin || myWin.closed ) { myWin=window.open(aURL,'myWin'); } else{ myWin.location=aURL; myWin.focus(); } }

4.11 Why does framename.print() not print the correct frame in IE?

IE prints the frame that has focus when you call the print method

frameref.focus(); frameref.print();

4.12 Why does parseInt('09') give an error?

parseInt decides what base the number is by looking at the number. By convention it assumes any number beginning with 0 is Octal, and any number beginning with 0x Hexadecimal. To force use of base 10 add a second parameter parseInt ("09",10)

4.13 How do I get the value of a form control?

Named forms may be referred to as named properties of the document.forms collection and named form controls as named properties of the form's elements collection in HTML documents:

var frm = document.forms["formname"]; var contrl = frm.elements["elementname"];

http://crowe.wowdns.com:8000/archives/dev_note/ (69 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

The (string) value property of such controls can be read directly from the element:-

var value = contrl.value;

Except when the control is a SELECT element where it is necessary to read the value property from the currently selected OPTION element whenever compatibility with older browsers (like NN 4) may be necessary:-

var value = contrl.options[contrl.selectedIndex].value;

Or multiple like-named controls such as radio buttons, which are made available as collections and need additional handling, For more information see:-

4.14 How do I close a window and why does it not work on the first one?

The scriptwindowref.close(); windowref is window, or self, top, parent, or a reference obtained when opening the window, you can only close windows opened by script, no others.

4.15 How do I modify the current page in a browser?

At its simplest in current DOM2 (with innerHTML extension) (IE5+ NS6 ) then having HTML of

with script of document.getElementById("aID").innerHTML="Some new Content"; works.

The below adds document.all capable browsers to the mix, NS4 could also be done but had serious issues, so is not listed here. HTML:

and script DynWrite('aID',"Some new Content") With the below code also in the page:

DocDom = (document.getElementById?true:false); DocAll = (document.all?true:false); DocStr='' if (DocAll) DocStr="return document.all[id]" if (DocDom) DocStr="return document.getElementById(id)" GetRef=new Function("id", DocStr) if (DocStr=='') { DynWrite=new Function("return false") } else { DynWrite=new Function("id", "S", "GetRef(id).innerHTML=S; return true") }

An alternative DynWrite function:

http://crowe.wowdns.com:8000/archives/dev_note/ (70 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

4.16 How do I trim whitespace - LTRIM/RTRIM/TRIM?

Using Regular Expressions (JavaScript 1.2/JScript 4+) :

String.prototype.LTrim=new Function("return this.replace(/^\\s+/,'')") String.prototype.RTrim=new Function("return this.replace(/\\s+$/,'')") String.prototype.Trim= new Function("return this.replace(/^\\s+|\\s+$/g,'')")

or for all versions (trims characters ASCII<32 not true "whitespace"):

function LTrim(str) { for (var k=0; k

function RTrim(str) { for (var j=str.length-1; j>=0 && str.charAt(j)<=" " ; j--) ; return str.substring(0,j+1); }

function Trim(str) { return LTrim(RTrim(str)); }

4.17 How do I force a reload from the server/prevent caching?

To reload a page, location.reload() works, however this does depend on the cache headers that your server sends, to change this you need to change your server - a quick fix to this on the client side is to change the URI of the page so it contains a unique element such as the current date. location.replace(location.href+'?d='+new Date().valueOf()) of if the location.href already contains a Query String location. replace(location.href+'&d='+new Date().valueOf())

4.18 How do I get a perl/asp/php variable into client-side js?

You have the serverside language generate the javascript:

jsvar="<%= aspvar %>"; jsvar="";

etc. For communication between client and server, after a page has loaded, there are a number of "Remote Scripting" http://crowe.wowdns.com:8000/archives/dev_note/ (71 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

options available:

4.19 Why do I get permission denied when accessing a frame/window?

In the normal browser security it is impossible for a script from one domain to access properties of pages served from another domain or by a different protocol, and any attempt by your script to access a property of a window in a different domain will result in a permission denied error. Signed scripts, or trusted ActiveX's can overcome this in limited situations.

4.20 How do I make a 10 second delay?

There is no ability to cause a delay in javascript, host Object Models usually provide something. In the browser, there is window.setTimeout which can be used to create a delay.

To call the function Snork(), approx 10 seconds after the function Moomin(), you would do this:

Moomin() setTimeout('Snork()',10000)

Script execution is not stopped, and adding Snufkin() after the setTimeout line would immediately execute the function Snufkin() before Snork() Other hosts have different wait functions such as Windows Script Hosts WScript.Sleep()

4.21 Why does 1+1 equal 11? or How do I convert a string to a number?

Javascript variables are loosely typed, and conversion between Strings and Numbers happen freely, since + is also used for String concatenation, "1"+1 is equal to 11 - the String deciding what + does, to overcome this convert the String in varname to a number: Number(varname) or varname*1 or varname-0 or parseInt(varname, 10) or parseFloat(varname) or +varname. Prompt and Form element values are all Strings, so convert them to numbers before performing addition.

Additional Notes:

4.22 How do I generate a random integer in [1..N]?

function Random(x) { return Math.floor(x*Math.random()) }

gives a random number between 0..(x-1);

http://crowe.wowdns.com:8000/archives/dev_note/ (72 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Random(N)+1 for [1..N]

Dealing and Shuffling:

4.23 How do I change print settings with window.print()?

In a normal security enviroment, you can't change anything. The page stylesheet rules provide some options, but are not supported in browsers yet. If you can, use an ActiveX or Plugin ScriptX and Neptune from Meadroid to give you more control for Windows versions of Internet Explorer and Netscape 4,6 and Opera 5.

4.24 I have what ... ?

Whatever the rest of your question, this is generally a very bad use of the javascript pseudo protocol. It was designed to be a method that a javascript function could return the new page e.g. javascript:"

Hello

" To use it simply to call a function, which you then return false from, causes user agents which do not understand javascript, or have it disabled, to get nothing other than an error.

where something.html is meaningful to the non-javascript capable, is nearly always preferable. Or use onclick on another element so users without javascript aren't even led to believe it does anything.

4.25 My element is named myselect[] , how do I access it?

Form elements with any "illegal" characters can be accessed with formref.elements["myselect[]"] - These characters are illegal within ID attributes in the standard (x)HTML doctypes and javascript Identifiers, so you should try to avoid them as browsers may handle them incorrectly.

4.26 How do I detect Opera/Netscape/IE?

The navigator object contains strings which specify the browser and version; however, this is in general not very genuine. Mozilla (and therefore Netscape 6) allows this to be freely set, and Opera and IE allow it to be modified. There are also at

http://crowe.wowdns.com:8000/archives/dev_note/ (73 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

least 25 other javascript capable browsers with their own strings here.

Generally though, you don't need to identify which browser is being used. There are alternative techniques which depend on why you want to redirect browsers - if it's to offer different CSS stylesheets, then shows many techniques. For Scripting, _Object_ or _Feature_ detection is a better method to use.

Object Detection means checking that the object you wish to use is supported by the browser before using it. This means that you don't need to know what browsers support what methods, and your code will automatically be usable on any browser that can execute it.

if (document.getElementById && document.getElementById('el') && document.getElementById('el').style ) { // We know that this browser supports getElementByID and has // a style object, so we can set a style property. document.getElementById('el').style.color="red"; }

Browser bugs can often be detected and overcome in similar ways.

4.27 How do I disable the right mouse button?

The event oncontextmenu of HTML attributes in IE and Mozilla, is the only reliable and safe method of blocking the context menu, the other approaches often presented, rarely work, and rely on alert boxes coming up and even then are limited in their scope:

4.28 How do I change the confirm box to say yes/no or default to cancel?

The buttons on a confirm box cannot be changed. But, why not just change the question around such that the default answer is OK. e.g. change "We will now buy ourselves a porsche with your credit card, Do you want to continue with this transaction _OK_ Cancel" to "We will now buy ourselves a porsche with your credit card, Would you like to abandon this transaction? _OK_ Cancel"

4.29 How do I log-out a user when they leave my site?

This cannot be done reliably. Here 's why:

A user may disable javascript, or may not have a javascript capability, so the log-out script will never execute.

http://crowe.wowdns.com:8000/archives/dev_note/ (74 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

A user may not be connected to the Internet/Intranet when they close your web page.

Javascript errors elsewhere in the page may prevent the script executing.

The URL below has more information.

4.30 How do I format Last Modified date with javascript

The format of the document.lastModified string is browser-dependent and sometimes misleading to users. Apparently, new Date() appears to read it correctly, apart from possible century error. In particular, time zone, field order and separators may vary. It is also reliant on the server's clock having been correctly set at the time of upload. See the URL below.

4.31 Why are my Rollovers so slow?

Images are cached by the browser depending on the headers sent by the server, if the server does not send sufficient information for the browser to decide the image is cacheable, it will check if the image has been updated everytime you change the src of an image (in some user settings). To overcome this you must send suitable headers.

4.32 How do I change the text in the url/location bar?

This text cannot be changed without changing the url of the page, the normal solution is to use frames, although this does introduce other problems.

4.33 How do I prompt a "Save As" dialog for an accepted mime type?

In IE and some browsers you can use the Content-Disposition header added on the server it has the format

Content-Disposition: attachment; filename=filename.ext

This can not be done with client-side javascript.

4.34 How do I run a server side script?

You trigger a server-side script by setting any object's URL. For example a frame, window, or an Image. An image will also"swallow" the data sent back by the server so that they will not be visible anywhere.

http://crowe.wowdns.com:8000/archives/dev_note/ (75 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

var dummyImage = new Image();dummyImage.src ="scriptURL.asp?param=" + varName;

In win32 IE5 and Mozilla, there is the XMLHTTPRequest object, which allows passing back of information from the server easy.

4.35 I have window.status="Moomin"; why doesn't the statusbar change?

When changing the status in an event (e.g. onmouseover) you should return true from the event. Also a number of browsers require a short delay before setting the status to overcome their default behaviour with the statusbar.

onevent="setTimeout('window.status=\'Moomin\'',15);"

Some browsers may be configured to disallow scripts from setting the status.

4.36 How do I modify the current browser window?

In a default security enviroment you are very limited in how much you can modify the current browser window. You can use window.resizeTo or window.moveTo to resize or move a window respectively, but that is it. Normally you can only suggest chrome changes in a window.open

4.37 How do I POST a form to a new window?

You use the target attribute on the form, opening a window with that name and your featuresting in the onsubmit handler of the FORM.

4.38 How do I download a page to a variable?

Within a webpage, you need to either use java, or the xml http request object, see:

4.39 How do I access a property of an object using a string?

There are two equivalent ways to access properties: the dot notation and the square bracket notation. What you are looking for is the square bracket notation, in which the dot and the identifier to the right of the dot are replaced with a set of square brackets containing a string that represents the identifier that was to the right of the dot.

http://crowe.wowdns.com:8000/archives/dev_note/ (76 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

//dot notation var bodyElement = document.body;

//square bracket notation, using an expression var bodyElement = document["bo"+"dy"];

4.40 When should I use eval?

The eval() function should _only_ be used when it is necessary to evaluate a string supplied or composed at run-time; the string can be anything from a simple (but unpredictable) expression such as 12*2.54 to a substantial piece of javascript code.

The eval() function should not be used as a crutch in composing member expressions; see section 4.39.

4.41 Why doesn't the global variable "divId" always refer to the element with id="divId"?

A common shortcut (introduced by IE and reproduced in some other browsers) in accessing DOM elements that have ID attributes is to use a provided global variable with the same name as the element's ID string. However, the best approach is the document.getElementById method, which is part of the W3C DOM standard and implemented in modern browsers (including IE from version 5.0). So an element with id="foo" can be referenced with:-

var el = document.getElementById("foo");

4.42 How do I open a new window with javascript?

New windows can be opened on browsers that support the window.open function and are not subject to the action of any pop-up blocking mechanism with code such as:-

if(window.open){ wRef = window.open("http://example.com/page.html","windowName"); }

4.43 How do I get my browser to report javascript errors?

Various browsers include mechanisms for reporting javascript errors in more or less detail but often they need to be enabled or actively viewed. The quick way of activating window IE error messages is to wait until a little yellow triangle appears at the left end of the status bar, double click on it and, when the error dialog box appears, check the "always show errors" checkbox it contains. It is also possible to enable/disable error reporting from the "Internet Options" dialog available through the menus. Mac IE error reporting is enabled through the preferences dialog.

http://crowe.wowdns.com:8000/archives/dev_note/ (77 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Netscape, Mozilla and other Gecko-based browsers have a javascript console that displays errors. It can be viewed by typing javascript: into the address bar, and it is sometimes also available as a menu item.

Opera's error reporting is activated by checking the "Open JavaScript console on error" checkbox in its preferences.

For Safari see:

5 Comments and Suggestions

5.1 Why do some posts have in them ?

If a poster feels that the question they are answering should be covered in the FAQ, placing in your post lets the FAQ robot collect the messages for easy review and inclusion.

5.2 How do I make a suggestion?

To Make a suggestion to the FAQ, either use the FAQENTRY method above, or email [email protected] (current FAQ editor) or [email protected] directly, all comments, suggestions, and especially corrections are welcome.

Posted by Crowe Lee at 05:43 PM | Comments (0) | TrackBack

December 23, 2004

Servlet에서 JSP의 useBean의 scope 공유하기

View: What servlet code corresponds to the various scope values? | jGuru

The jsp:useBean tag is very powerful. It allows you to declare a variable using a single syntax, but produces code that (a) either initializes the variable, or locates it if it's already been initialized, and (b) stores it in one of a number of locations, depending on its scope.

In order to share variables between JSPs and Servlets, you need to know how to create and to access variables from inside your servlet code. Following are examples which should help you understand how to do this.

In this example, when the Counter bean is instantiated, it is placed within the servlet context, and can be accessed by any JSP or servlet that belongs to the application (i.e. belongs to the same servlet context).

The servlet equivalent of the above useBean action is:

foo.Counter counter = (foo.Counter)getServletContext().getAttribute("counter"); if (counter == null) { counter = new foo.Counter(); http://crowe.wowdns.com:8000/archives/dev_note/ (78 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

getServletContext().setAttribute("counter", counter); }

In this example, when the Counter bean is instantiated, it is placed within the current session, and can be accessed by any JSP or servlet during a subsequent request by the current user.

The servlet equivalent of the above useBean action is:

HttpSession session = request.getSession(true); foo.Counter counter = (foo.Counter)session.getValue("counter"); if (counter == null) { counter = new foo.Counter(); session.putValue("counter", counter); }

In this example, when the Counter bean is instantiated, it is placed within the current request object, and can be accessed by any JSP or servlet during a the same request; e.g., if a RequestDispatcher is used, or if or sends the request to another servlet or JSP.

The servlet equivalent of the above useBean action is:

foo.Counter counter = (foo.Counter)request.getAttribute("counter"); if (counter == null) { counter = new foo.Counter(); request.setAttribute("counter", counter); }

In this example the Counter bean is instantiated as a local variable inside the JSP method. It is impossible to share a page scope variable with another JSP or Servlet.

The servlet equivalent of the above useBean action is:

http://crowe.wowdns.com:8000/archives/dev_note/ (79 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

foo.Counter counter = new foo.Counter();

Posted by Crowe Lee at 03:02 AM | Comments (0) | TrackBack

Tomcat 4.1.29 및 5.0.x 버전에서의 한글 처리 (Filtering 이용시)

View: 4.1.29 및 5.0.x 버전에서의 request.setCharacterEncoding 문제.. | OK JSP

파라미터의 한글처리를 위해 request.setCharacterEncoding 을 사용하거나 Characterset Encoding Filter를 사용할텐데요.. Servlet 2.3 스펙에 의하면 이것이 Request Body 에만 적용이 됩니다. 톰캣 4.1.29 및 5.0.x 버전에서는 이를 적용하여 request. setCharacterEncoding에 의해 설정된 인코딩이 POST방식의 파라미터에만 적용됩니다. GET방식의 파라미터에는 적용이 되지 않네요.

GET방식의 파라미터가 들어있는 QueryString은 URIEncoding 에 설정된 캐릭터셋을 이용하여 처리합니다. 따라서, 한글이 깨지지 않게 하기 위해서는 URIEncoding 을 별도로 설정해 주어야합니다. 이는 %CATALINA_HOME%/conf/server.xml 내에 있는 HTTP 호출을 처 리하는 Connector 에서 설정할 수 있습니다. 톰캣의 기본 HTTP Connector를 예로 들면 다음과 같이됩니다.

마지막 속성인 URIEncoding="KSC5601" 이 URIEncoding의 캐릭터셋을 명시적으로 지정하는 속성이 되겠습니다.

혹시 톰캣 업글후 한글 파라미터때문에 고생하셨던 분이 계시다면 도움이 되길 바랍니다.

Posted by Crowe Lee at 12:24 AM | Comments (0) | TrackBack

October 08, 2004

Oracle Optimizer Hints

/*+ ALL_ROWS */ explicitly chooses the cost-based approach to optimize a statement block with a goal of best throughput (that is, minimum total resource consumption) 가장 좋은 단위 처리량의 목표로 문 블록을 최적화하기 위해 cost-based 접근 방법을 선택합니다. (즉, 전체적인 최소의 자원 소비) /*+ CHOOSE */ causes the optimizer to choose between the rule-based approach and the cost-based approach for a SQL statement based on the presence of statistics for the tables accessed by the statement 최적자(optimizer)가 그 문에 의해 접근된 테이블을 위해 통계의 존재에 근거를 두는 SQL 문을 위해 rule-based 접근 방법과 cot-based 접근 방법 사이에 선택하게 합니다. /*+ FIRST_ROWS */ explicitly chooses the cost-based approach to optimize a statement block with a goal of best response time (minimum http://crowe.wowdns.com:8000/archives/dev_note/ (80 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

resource usage to return first row) 가장 좋은 응답 시간의 목표로 문 블록을 최적화하기 위해 cost-based 접근 방법을 선택합니다. (첫번째 행을 되돌려 주는 최소의 자원 사용) /*+ RULE */ explicitly chooses rule-based optimization for a statement block 문 블록을 위하여, rule-based 최적화를 고르는

/*+ AND_EQUAL(table index) */ explicitly chooses an execution plan that uses an access path that merges the scans on several single-column indexes 그만큼 실행 계획을 선택합니다. 그리고 여럿의 single-column 색인에 그 scan을 합병하는 접근 경로를 사용합니다. /*+ CLUSTER(table) */ explicitly chooses a cluster scan to access the specified table 선택합니다. 그리고, 클러스터는 그 명시된 테이블을 접근하기 위해 살핍니다. /*+ FULL(table) */ explicitly chooses a full table scan for the specified table 그 명시된 테이블을 위하여, 전체 테이블 scan을 고르는 /*+ HASH(table) */ explicitly chooses a hash scan to access the specified table 선택합니다. 그리고, 해쉬는 그 명시된 테이블을 접근하기 위해 운율을 살핍니다. /*+ HASH_AJ(table) */ transforms a NOT IN subquery into a hash antijoin to access the specified table 변환, 그 명시된 테이블을 접근하는 해쉬 antijoin으로의 NOT IN 부속 조회 /*+ HASH_SJ (table) */ transforms a NOT IN subquery into a hash anti-join to access the specified table 변환, 그 명시된 테이블을 접근하는 해쉬 anti-join으로의 NOT IN 부속 조회 /*+ INDEX(table index) */ explicitly chooses an index scan for the specified table 그 명시된 테이블을 위하여, 색인 scan을 고르는 /*+ INDEX_ASC(table index) */ explicitly chooses an ascending-range index scan for the specified table 그 명시된 테이블을 위하여, ascending-range 색인 scan을 고르는 /*+ INDEX_COMBINE(table index) */ If no indexes are given as arguments for the INDEX_COMBINE hint, the optimizer uses whatever Boolean combination of bitmap indexes has the best cost estimate. If particular indexes are given as arguments, the optimizer tries to use some Boolean combination of those particular bitmap indexes. 어떤 색인도 INDEX_COMBINE 암시를 위해 인수로서 주어지지 않는다면, bitmap 색인의 결합이 어떤 부울의를 가장 좋은 수행 난이도 평가를 가지고 있든지 최적자는 이용합니다. 특별한 색인이 인수로서 주어진다면, 최적자는 그 특별한 bitmap 색인의 몇몇의 부울의 결합을 사용하려고 노력합니다. /*+ INDEX_DESC(table index) */ explicitly chooses a descending-range index scan for the specified table 그 명시된 테이블을 위하여, descending-range 색인 scan을 고르는 /*+ INDEX_FFS(table index) */ causes a fast full index scan to be performed rather than a full table scan 빠른 전체 색인 scan이 전체 테이블 scan이라기보다는 수행되게 합니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (81 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

/*+ MERGE_AJ (table) */ transforms a NOT IN subquery into a merge anti-join to access the specified table 변환, NOT IN 부속 조회, 그 명시된 테이블을 접근하기 위해 anti-join을 합병합니다. /*+ MERGE_SJ (table) */ transforms a correlated EXISTS subquery into a merge semi-join to access the specified table 변환, 관련된 EXISTS 부속 조회, 접근으로 semi-join을 합병합니다, 그 명시된 테이블 /*+ ROWID(table) */ explicitly chooses a table scan by ROWID for the specified table 그 명시된 테이블을 위하여, ROWID에 의해 테이블 scan을 고르는 /*+ USE_CONCAT */ forces combined OR conditions in the WHERE clause of a query to be transformed into a compound query using the UNION ALL set operator 힘은 질의의 WHERE 문절에 있는 UNION ALL 집합 연산자를 사용하는 합성의 질의로 변형되는 OR 조건을 합쳤습니다.

/*+ ORDERED */ causes Oracle to join tables in the order in which they appear in the FROM clause 오라클이 어느 것에 순서로 테이블을 결합시키게 합니다. /*+ STAR */ forces the large table to be joined last using a nested-loops join on the index 큰 있는 테이블이 최종 사용/회전율에 nested-loops를 결합시킨 힘은 그 색인에 결합합니다.

/*+ DRIVING_SITE (table) */ forces query execution to be done at a different site from that selected by Oracle 힘은 그것과 다른 오라클에 의해 선택된 사이트에 되는 실행을 질의합니다. /*+ USE_HASH (table) */ causes Oracle to join each specified table with another row source with a hash join 오라클이 테이블이 다른 행 자원으로 해쉬 접합으로 명시되면서 각자와 합치게 합니다. /*+ USE_MERGE (table) */ causes Oracle to join each specified table with another row source with a sort-merge join 오라클이 테이블이 다른 행 자원으로 sort-merge 접합으로 명시되면서 각자와 합치게 합니다.

/*+ USE_NL (table) */ causes Oracle to join each specified table to another row source with a nested-loops join using the specified table as the inner table 오라클이 그 명시된 테이블을 그 안의 테이블로 사용하는 nested-loops 접합과 각자와 다른 행 자원에 대한 명시된 테이블을 합치게 합니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (82 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

/*+ APPEND */ , /*+ NOAPPEND */ specifies that data is simply appended (or not) to a table; existing free space is not used. Use these hints only following the INSERT keyword. 데이타가 테이블로 단순히 덧붙여진다는 (or not)것 명시합니다; 무료인 현존하는 영역은 사용되지 않습니다. 단지 그 삽입 키 핵심어를 따르는 이 암시를 사용하시오. /*+ NOPARALLEL(table) */ disables parallel scanning of a table, even if the table was created with a PARALLEL clause 그 테이블이 PARALLEL 문절로 새로 만들어졌다면 테이블의 평행의 순차 검색을 무능하게 만듭니다. /*+ PARALLEL(table, instances) */ allows you to specify the desired number of concurrent slave processes that can be used for the operation. DELETE, INSERT, and UPDATE operations are considered for parallelization only if the session is in a PARALLEL DML enabled mode. (Use ALTER SESSION PARALLEL DML to enter this mode.) 당신이 그 연산을 위해 사용될 수 있는 동시의 슬레이브(slave) 프로세스의 요구된 수를 명시하는 것을 허락합니다. 그 세션이 가능하게 된 PARALLEL DML에 모드를 있다면, DELETE, INSERT, UPDATE 연산은 단지 parallelization에 대해 고려됩니다. (사용은 이 모드에 들어가기 위해 평행의 세션 DML을 변경합니다.) /*+ PARALLEL_INDEX allows you to parallelize fast full index scan for partitioned and nonpartitioned indexes that have the PARALLEL attribute parallelize에 당신에게 빠른 가득한 색인 scan을 허락합니다. 그런데, 그것은 PARALLEL 속성을 가지고 있는 색인을 분할했고 nonpartitioned했습니다. /*+ NOPARALLEL_INDEX */ overrides a PARALLEL attribute setting on an index 병렬이 색인을 나아가는 것을 속하게 하는 대체

/*+ CACHE */ specifies that the blocks retrieved for the table in the hint are placed at the most recently used end of the LRU list in the buffer cache when a full table scan is performed 그 블록이 찾아서 가져왔다는 것을 명시합니다. 그리고 그 테이블을 위해 그 암시에 놓여집니다. 그런데, 그것은 가장 요즈음 사용된 언제 그 버퍼 캐쉬, 가득한 테이블 scan에 있는 LRU 리스트의 끝입니다. 수행됩니다. /*+ NOCACHE */ specifies that the blocks retrieved for this table are placed at the least recently used end of the LRU list in the buffer cache when a full table scan is performed 그 명시합니다. 그리고, 그 블록은 이 테이블을 위해 검색되면서 요즈음 사용된 언제 그 버퍼 캐쉬, 가득한 테이블 scan에 있는 LRU 리스트의 가장 작은 끝에 놓여집니다. 수행됩니다. /*+ MERGE (table) */ causes Oracle to evaluate complex views or subqueries before the surrounding query 오라클이 그 둘러싸는 질의 전에 복잡한 뷰나 부속 조회를 평가하게 합니다. /*+ NO_MERGE (table) */ causes Oracle not to merge mergeable views 오라클이 mergeable 뷰를 합병하지 않게 하지 않습니다 /*+ PUSH_JOIN_PRED (table) */ causes the optimizer to evaluate, on a cost basis, whether or not to push individual join predicates into the view http://crowe.wowdns.com:8000/archives/dev_note/ (83 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

개개 접합을 미는 것이 그 뷰 안으로 단정 하든 간에 비용 방식으로 최적자가 평가하게 합니다. /*+ NO_PUSH_JOIN_PRED (table) */ Prevents pushing of a join predicate into the view 접합 술부 중에서 그 뷰로 밀면서, 막는 /*+ PUSH_SUBQ */ causes nonmerged subqueries to be evaluated at the earliest possible place in the execution plan 원인은 그 실행 계획에서의 가장 이른 가능한 장소에 평가되는 부속 조회를 nonmerged했습니다. /*+ STAR_TRANSFORMATION */ makes the optimizer use the best plan in which the transformation has been used. 최적자가 그 변형이 사용된 가장 좋은 계획을 사용하는 제작

Posted by Crowe Lee at 09:37 PM | Comments (0) | TrackBack

September 21, 2004

CVS 사용자를 위한 10가지 Subversion 팁

View: CVS 사용자를 위한 Subversion 팁

저자: Brian W. Fitzpatrick, 역 한동훈(traxacun at unitel.co.kr) 원문: http://www.onlamp.com/pub/a/onlamp/2004/08/19/subversiontips.html

Subversion 프로젝트의 목표는 "CVS를 대체할 강력한 소스 관리 도구를 제공하는 것"이며, 부수적으로 CVS 사용자가 쉽게 Subversion 으로 변경할 수 있도록 CVS와 유사한 사용자 인터페이스를 제공하는 것이다.

Subversion의 새로운 특징들을 배웠다면, 직접 사용할 차례가 맞지 않을까?

인터페이스가 유사하더라도, 중요한 차이점은 몇가지 있다. Subversion은 CVS가 제공하지 않거나, 다른 형태로 제공하던 몇가지 기능들 을 제공할 뿐만 아니라, CVS를 설치하고 익히게 된 나쁜 버릇들도 날려버려야 하는 기능들을 제공하고 있다.

이를 염두에 두고 CVS 사용자를 위한 10가지 subversion 팁을 살펴보도록하자. 먼저 소개하는 6가지 팁은 CVS의 나쁜 버릇에 관한 것이 며, 나머지 4가지는 Subversion 이용에 있어 좋은 버릇을 위한 것이다.

1. 자신의 상태를 알기 위해 status를 사용하라

CVS에서는 작업 사본(working copy)에 변경사항이 있는지 살펴보고 싶을 때 cvs update를 실행해야 했으며, 이는 분명 이상한 일이다. 이 명령은 작업 사본에 있는 파일을 보여줄 뿐만 아니라 저장소(repository)에 있는 최신 버전을 작업 사본으로 복사해온다. 이것은 서버와 의 라운드 트립을 유발할 뿐만 아니라, 작업 사본도 변경시킨다. 작업 사본에서 변경된 것과 저장소에서 변경된 것을 찾아내는 것은 어려 운 일이며, CVS는 이 두가지를 섞어버린다.

Subversion에서는 변경 사항을 알기 위해 svn status를 사용한다. 이 명령은 작업 사본에 있는 것과 Subversion 관리자 영역(.svn 디렉 터리)에 있는 것을 비교하기 때문에 네트워크 라운드 트립이 발생하지 않는다.

$ svn status

D fish.c

A shrimp.c

http://crowe.wowdns.com:8000/archives/dev_note/ (84 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

M anemone.c

fish.c는 삭제될 것이며, shrimp.c는 추가될 것이며, anemore.c는 변경되었다는 것을 의미한다.

이제, 기본적으로 svn status는 추가, 수정, 삭제되는 것과 같이 원하는 파일만 보여줄 수 있다. 작업 사본에 있는 모든 파일에 대한 정보 를 보고 싶다면 --verbose 스위치를 사용한다.

$ svn status --verbose

44 23 sally README

44 30 sally INSTALL

44 35 harry trout.c

D 44 19 ira fish.c

A 0 ? ? shrimp.c

M 0 ? ? anemone.c

44 36 harry things/rocks.txt

첫번째 열은 동일하지만, 두번째는 작업 사본의 개정번호를 보여준다. 세번째와 네번째 열은 각 항목이 마지막으로 변경된 개정번호와 누 가 변경했는지를 보여준다.

svn update를 실행했을 때 어떤 파일이 업데이트되는지 알고 싶다면 --show-updates 스위치를 사용한다.

$ svn status --show-updates --verbose

* 44 23 sally README

44 30 sally INSTALL

* 44 35 harry trout.c

D 44 19 ira fish.c

A 0 ? ? shrimp.c

M * 44 32 sally anemone.c

44 36 harry things/rocks.txt

업데이트될 파일은 *로 표시되는 것을 알 수 있다.

2. 옮길 때 알아야 할 것들

http://crowe.wowdns.com:8000/archives/dev_note/ (85 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

CVS 저장소를 위해 디렉터리를 정리하고, 파일을 옮기느라 시간을 보내는 사람들을 본적 있다. 이런 작업을 하는 이유는 CVS가 알고 있 는 파일과 디렉터리에 대해서는 이동을 허용하지 않기 때문이다. Sunversion에서는 파일과 디렉터리를 이동할 수 있다.

cf. 물론, CVS에서도 저장소에 있는 파일을 옮기기 위해 파일을 이동시키고, 저장소에 삭제와 추가 작업을 수작업으로 할 수는 있다. 하지 만, 이런 작업은 기존 파일을 삭제하고, 새 파일을 추가하는 것이 되기 때문에 버전 관리의 히스토리가 완전히 무용지물이 되고 만다.

$ svn move foo.c bar.c

A bar.c

D foo.c

이제 bar.c는 저장소에 추가될 것이고, foo.c는 삭제될 것이다.(Subversion은 이동에 대해 추가와 삭제로 보여주며, svn commit을 수행 할 때 서버에 변경사항이 반영된다)

URL을 사용해서 파일과 디렉터리를 이동시킬 수도 있다.

$ svn move -m "Move a file" http://svn.red-bean.com/repos/foo.c \

http://svn.red-bean.com/repos/bar.c

위와 같은 명령은 서버에 있는 foo.c를 bar.c로 바로 변경한다.

3. 복사에 의한 태그 부여와 트리확장(branch)

CVS에서는 태그를 부여하거나 트리를 확장하기 위해 cvs tag, cvs tag ?b, cvs rtag, cvs rtag -b를 사용하지만, Subversion에서는 copy를 사용한다.

$ svn copy -m "Tag rc1 rel." http://svn.red-bean.com/repos/trunk \

http://svn.red-bean.com/repos/tags/1.0rc1

위 명령을 사용하여 주요 개발 버전에 대한 태그를 부여한 것이다.(Subversion 용어로는 이를 trunk라 한다.) 트리 확장을 하고 싶다면, trunk를 트리 확장 디렉터리로 복사하면 된다. Subversion에서 태그 부여와 트리 확장은 매우 빠르게 수행된다.

Subversion에서 태그와 확장(branch)는 저장소 트리에서 복제된 경로에 불과하다. 보통 태그는 /tags 디렉터리 밑에, 확장(branches) 은 /branches 디렉터리 밑에 있다.

CVS에서는 저장소에 태그가 부여된 각각의 파일을 수정해야 한다. 이 과정은 저장소 크기에 따라 다르며, 매우 긴 시간이 걸린다. 반면에 Subversion에서는 디렉터리 노드 하나만 복사하기 때문에 속도도 빠르며, 저장소의 공간도 거의 사용하지 않는다. 태그나 확장에 관련된 파일 수가 아무리 많아도 문제되지 않는다. Subversion에서는 이를 "저렴한 복제(cheap copies)"라고 부른다.

Subversion에서는 같은 개정 번호에 속한 모든 파일들에 태그를 부여하는 것을 제한하지 않는다. "섞여버린 개정 번호(mixed- revision)"을 갖는 태그나 확장(branch)을 만들 필요가 있다면 작업 복사본을 URL로 복사하면 된다.

$ svn copy -m "Mixed branch." . http://svn.red-bean.com/repos/branch/1.2-mixed http://crowe.wowdns.com:8000/archives/dev_note/ (86 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

태그와 확장에 대한 보다 자세한 설명은 Branching and Merging을 참고하기 바란다.

4. "삭제 및 업데이트" 대신 "복원(revert)"

CVS 작업 사본에서 작업하던 파일을 원본으로 복원하고 싶은 경우 다음과 같은 작업을 할 것이다.

$ rm I-made-a-boo-boo.txt

$ cvs up I-made-a-boo-boo.txt

U I-made-a-boo-boo.txt

두 번의 절차를 거쳐야 하며, 변경되지 않은 파일을 가져오기 위해 서버와 데이터를 주고 받아야 한다. 그러나 Subversion에서는 각 파일 의 원본을 .svn 디렉터리에 보관하고 있으므로 다음과 같이 하면 된다.

$ svn revert I-made-a-boo-boo.txt

Reverted 'I-made-a-boo-boo.txt'

이와 같은 방법은 지금 네트워크에 연결되어 있지 않아도 사용할 수 있으며 사용법도 간단하다.

5. 버전 관리 시스템을 두려워말라

CVS는 각 라인의 끝을 변환하고, 파일에 $Id$와 같은 키워드를 붙여나간다.(UNIX는 각 라인의 마지막을 CR로 표기하며, 윈도우는 CRLF 로 표기하며, CVS는 이러한 변환을 수행한다) 이런 방법은 매우 편리하지만 CVS 저장소에 이진 파일(binary file)을 저장하게 되면 푸딩 처럼 엉망이 되어 버린다. Subversion은 사용자가 요청하기 전까지는 데이터에 어떤 변경도 가하지 않는다.

Subversion 저장소에 이진 파일을 저장할 수 있으며, 파일이 손상되지 않도록 별도의 작업을 할 필요가 없다. 그러나 .java, .c와 같은 텍 스트 파일을 추가할 때 Subversion에서 자동으로 각 라인의 끝을 변환하게 할 수도 있다. 이에 대해서는 Subversion properties을 참고 하기 바란다.

이와 같은 경우에는 svn:eol-style 속성을 native로 설정하면 된다.

$ svn propset svn:eol-style native halibut.c

파일에 특정 속성을 추가하기 위해 Subversion 클라이언트를 설정할 수도 있으며, 이에 대해서는 Automatic Properties와 configuration을 참고하기 바란다.

6. 로그

Subversion의 log 명령은 CVS log 명령보다 훨씬 더 강력하다.

Subversion의 log 명령이 강력한 이유로는 CVS log 명령이 동일한 커밋인지, 아닌지 알 수 없는 파일 목록을 보여주는 것과 달리 최소 단 위 커밋(atomic Subversion commit)에 따라 데이터를 보여주는 데 있다.(CVS에는 커밋 그룹이라는 개념이 없다) Subversion은 저장 소의 로그 데이터를 보다 간결하게 보여줄 수 있다.

$ svn log http://crowe.wowdns.com:8000/archives/dev_note/ (87 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

------

r3 | sally | Mon, 15 Jul 2002 18:03:46 -0500 | 1 line

Added include lines and corrected # of cheese slices.

------

r2 | harry | Mon, 15 Jul 2002 17:47:57 -0500 | 1 line

Outline sandwich fixins.

------

r1 | sally | Mon, 15 Jul 2002 17:40:08 -0500 | 1 line

Initial import

------

각 로그 항목은 항목, 저자, 날짜, 로그에 기록된 라인 수, 로그 메시지를 볼 수 있다. 로그에 변경된 경로까지 보고 싶다면 --verbose 플래 그를 사용할 수 있다.

$ svn log --verbose

------

r3 | sally | Mon, 15 Jul 2002 18:03:46 -0500 | 1 line

Changed paths:

M /trunk/sandwich.txt

Added include lines and corrected # of cheese slices.

------

r2 | harry | Mon, 15 Jul 2002 17:47:57 -0500 | 1 line

Changed paths:

M /trunk/sandwich.txt

Outline sandwich fixins.

------

r1 | sally | Mon, 15 Jul 2002 17:40:08 -0500 | 1 line http://crowe.wowdns.com:8000/archives/dev_note/ (88 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Changed paths:

A /trunk/sandwich.txt

Initial import

------

위 예제에서, log 명령에 특정 파일이나 디렉터리에 대한 정보를 제공하지 않는다는 것을 알 수 있다. 어떤 것도 지정하지 않고 svn log 명 령을 사용하면 현재 작업 디렉터리를 참조한다. Subversion은 초기 개정 번호로 1을 사용하며, 최종 개정 번호로 현재 작업 디렉터리의 작 업 개정번호로 사용한다.(작업 개정 번호를 알기 위해서는 svn status -v를 사용한다)

여기서 잠깐 알아둘 것이 있다. 파일의 변경 사항을 커밋하고 즉시 svn log를 수행하면 작업 디렉터리의 "작업 개정번호"가 업데이트되지 않았기 때문에 커밋에 대한 로그 메시지를 볼 수 없다. 파일을 커밋하는 것이 작업 디렉터리나 다른 파일을 자동으로 업데이트하지 않는 다. svn update를 수행하고 svn log를 수행하면 "실종된(missing)" 로그 메시지를 볼 수 있을 것이다.

svn log 사용에 대한 것은 http://svnbook.red-bean.com/svnbook/ch03s06.html#svn-ch-3-sect-5.1 과 http://svnbook.red- bean.com/svnbook/re15.html를 참고하기 바란다.

7. 잘못된 커밋을 복원하기

/trunk에 작업 복사본을 갖고 있으며, 개정 번호가 303으로 변경된 oyster.c 파일이 엉뚱한 파일이라는 것을 알았다고 하자. - 이 경우엔 커밋을 해서는 안되는 경우다. 이런 경우에는 작업 사본의 변경사항을 "복원"하기 위해 svn merge를 사용할 수 있다. 복원한 다음에 저장 소에 변경 사항을 적용하기 위해 커밋할 수 있다.

$ svn merge -r 303:302 http://svn.example.com/repos/calc/trunk U oyster.c

변경사항이 올바른지 검증하기 위해 svn diff를 사용한 다음에 저장소에 commit 할 수 있다.

보다 자세한 사항은 Undoing Changes를 참고하기 바란다.

8. 삭제된 항목 복원하기

Subversion 저장소에서 삭제했던 파일을 최근 저장소에 다시 "복원"하는 가장 쉬운 방법은 작업 복제본에서 삭제될 때의 개정번호에서 svn copy를 사용하여 복사하는 것이다. 파일이 삭제될 때의 개정 번호를 알기 위해 svn log -v를 사용한다.

$ svn copy --revision 807 \

http://svn.red-bean.com/repos/trunk/perch.c ./perch.c

보다 자세한 것은 Resurrecting deleted items를 참고한다.

9. 새 작업 복사본 없이 branch 변경하기

CVS에서는 작업 사본이 있으면서 branch에 대해 작업 하려면 개정 번호로 branch 이름을 사용할 수 있다. 그러나 Subversion에서는 태 그와 branch를 저장소의 경로로 인식하기 때문에 svn update시 작업사본에 branch 이름을 사용할 수 없다. 대신에 svn switch 명령을 사용해야 한다.

svn switch는 저장소의 새 트리를 반영하기 위해 작업 사본을 업데이트 한다. ? 여기서 tree는 trunk 트리가 아니라 branch 트리를 의미 http://crowe.wowdns.com:8000/archives/dev_note/ (89 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

한다. Subversion은 switch를 사용하여 작업 사본을 새 branch로 변경시킨다.

$ svn switch http://svn.red-bean.com/repos/branches/vendors-with-fix .

U myproj/foo.txt

U myproj/bar.txt

U myproj/baz.c

U myproj/qux.c

Updated to revision 31.

보다 자세한 것은 Switchting a working copy를 참고한다.

10. 저장소 검색하기, 마운트하기!!

Apache HTTP 서버를 사용하여 Subversion 저장소를 이용할 수 있다면 Subversion은 다양한 편의를 제공한다.

첫번째로 Subversion 저장소를 보기 위해 웹 브라우저를 사용할 수 있으며 저장소에 있는 최근 버전을 이용할 수 있다.

두번째로 DAV 공유를 할수 있는 운영체제를 사용하고 있다면 Subversion 저장소를 데스크탑에 읽기전용으로 마운트 할 수 있다.

http://crowe.wowdns.com:8000/archives/dev_note/ (90 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

이러한 방법은 저장소의 내용을 편리하게 살펴볼 수 있으며, Subversion을 이용하지 않는 사용자와 파일도 공유할 수 있다.

Posted by Crowe Lee at 02:08 PM | Comments (0) | TrackBack

September 20, 2004

Win2X/XP의 IIS에서 연결수 최대 설정 팁

한 웹당 2개씩, 전체 10개까지밖에 되지 않던 최대 접속자수를 40까지 늘리는 방법입니다. 40은 하드코딩 되어있어 더이상 늘릴수는 없 고 서버버젼을 깔아야 합니다. 간단히 설명하면 c:/inetpub/adminscripts/adsutil.vbs 스크립트 파일을 이용하여 40까지 증가시키고, 웹 사이트 설정에서 연결을 유지하지 않도록 설정하는 것이 포인트 입니다.

View: No More 403.9's in IIS | PermaLink

Alot of people were pinging me today about getting 403.9's when trying to access my website (Access Forbidden: Too many users are connected Internet Information Services). It turns out that the IIS on Windows XP comes configured out of the box for a maximum of 10 HTTP connections. What's worse, IE (actually URLMON) seems to use up 2 connections when hitting a website. I tried figuring out how to raise the connection limit, but there didn't seem to be a documented way of doing this on a non-server version of Windows. Fortunately, my good friend Geoff is the Architect of IIS and was able to help me out. Here's his tip:

This can be bumped up to 40 but not higher (40 is the hardcoded limit). http://crowe.wowdns.com:8000/archives/dev_note/ (91 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

To do this, find the adsutil.vbs script (should be in c:\inetpub\AdminScripts or similar) and run the following command: adsutil set w3svc/MaxConnections 40

You can also try to prevent individuals from hogging a bunch of connections and thereby blocking out other users. (IE will normally use 2 connections to a web site.) To do this, launch the IIS admin tool (under control panel / administrative tools), right click on your ‘default web site’ (under ‘web sites’ in the outline control), and mess with the settings in the “connections” part of the “web site” tab. Lowering the timeout (default is 5 min) will cause idle connections to get dropped quicker. Disabling keep alives will cause connections to disconnect immediately after the request is handled instead of waiting for new requests on the same connection. This is a little extreme but you probably don’t care that much about responsiveness, so you might want to just try that and see if it solves the problem.

Thanks Geoff! I hope I didn't cost the company too many W2K3 licenses by posting this :-)

Posted by Crowe Lee at 01:41 PM | Comments (1) | TrackBack

September 17, 2004

Java Script에서 Regular Expression (정규식) 사용하기

View: RegExp 사용하기

Regular Expressions

Regular Expressions은 JavaScript1.2에서 사용할 수 있는 객체입니다. 특정 문자나 문자열로 문자열을 다루는 것에 문자뿐 만이 아니 고 특수 문자로 다룰 수 있고 이들을 조합하여 다룰 수 있는 조건식(pattern)을 제공하여 더 세밀한 방법으로 문자들을 검색할 수 있게 합 니다. Regular Expressions는 Perl, PHP등의 Server Side 언어에서도 있고 비슷한 동작을 합니다. 많은 부분을 Perl에서 가져온 것입니 다.

Regular Expressions은 문자들에게 조건을 지정하여 조건식을 만들고 이것을 이용하여 사용합니다. 조건식을 만드는 방법은 2가지가 있 습니다.

var re = /abc/

var re = new RegExp("abc")

위의 2가지 식 중에서 하나를 사용할 수 있고 위의 변수 "re"가 Regular Expression 객체가 됩니다. 문자열 "abc"는 검색어로 사용할 pattern이 됩니다.

이 Regular Expression객체는 'source', 'lastIndex'의 2 개의 요소와 3개의 메소드 compile(), exec(), test()를 가지고 있습니다. (nn4에서는 요소로 'global', 'ignoreCase'의 2개가 더 있지만 Netscape 공식 문서에는 들어 있지 않습니다.)

http://crowe.wowdns.com:8000/archives/dev_note/ (92 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

Regular Expression Switch

var re = /abc/[g,i,gi]

var re = new RegExp("abc", ["g", "i", "gi"])

Regular Expression 사용식에 "g", "i", "gi" 로 3가지 중에서 하나의 switch를 지정한다. 지정하지 않을 수도 있다.

● "g" : pattern에 맞는 문자들을 전부 검색한다.

● "i" : 검색시 영어 대소문자를 구분하지 않는다.

● "g" : "gi" : "g"와 "i"를 같이 지정한다.

Regular Expression 요소값

lastIndex : pattern에 지정한 문자가 검색된 마지막 위치. nn4에서는 switch로 "g"를 지정해야 참조할 수 있고 ie5에서는 exec() 메소 드로 만든 배열 객체의 요소이다. 이 경우는 정수 3이 나온다.

var myRe= /bb/g;

var arr = myRe.exec("abbc");

if(document.layers) alert(myRe.lastIndex)

else if(document.all) alert(arr.lastIndex)

source : pattern에 지정한 문자열. 스위치는 포함되지 않는다. 이 경우는 "bb"가 나온다.

alert(myRe.source)

Regular Expression 메소드

compile("pattern", "switch") : 지정한 pattern을 재지정한다. 그래서 source와 global, ignoreCase 스위치를 변경할 수 있다. 이것으 로 지정식에 변화하는 pattern, switch를 적용할 수 있다. 아래는 "cc"가 된다. 처음 source "bb"에서 다시 "cc"로 재지정하였고 스위치 도 "g"에서 "i"로 바꾸었다.

var myRe = /bb/g

myRe.exec()

myRe.compile("cc", "i")

alert(myRe.source)

exec("문자열") : 문자열을 검색한다. 실행후에 RegExp 객체와 검색결과를 가지고 있는 배열을 만든다. 아래는 "abb"로 나온다. arr[0] 도 "abb"가 나온다.

http://crowe.wowdns.com:8000/archives/dev_note/ (93 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

var myRe = /\w+bb/g

var arr = myRe.exec("abbc")

alert(RegExp.$1)

test("문자열") : 지정한 pattern이 문자열에 있는지 없는지를 검사한다. 있으면 true를 없으면 false를 반환한다. 특정 pattern이 있는지 없는지만 확인할 필요가 있으면 이것을 사용하는 것이 exec()보다 속도가 빠르다.

var myRe = /\w+bb/g

var arr = myRe.test("abbc")

if(arr) {

alert('문자열 "abbc" 안에 pattern인 /\w+bb/가 발견됨.')

}

Regular Expression 특수문자

특수문자 의미

다음 글자가 일반적인 글자가 아니고 특수문자로 사용된다는 지정이다. 특수문자로 사용할 문자앞에 backslash(\)를 붙 여준다. \w는 w가 특수문자로 사용됨을 나타낸다. \w의 의미는 모든 문자를 가리킨다.

\ 또는 반대로 특수문자를 일반적인 문자로 지정하게 한다. \\는 backslash(\)문자를 나타낸다. \\\/는 slash(/) 문자를 나타낸다.

^ input이나 line의 시작문자. /^A/는 "Amatch"와는 맞지만 "an Amatch"와는 맞지 않는다.

$ input이나 line의 끝문자. /A$/는 "abA"와는 맞지만 "aAb"와는 맞지 않는다.

앞 문자가 0번 이상 있으면 맞다. 없어도 되고 여러개 있어도 된다. /ab*c/는 "abbbc"와 맞다. "ac"와도 맞다. 하지만, * "ax"와는 맞지 않는다.

앞 문자가 1번 이상 있으면 맞다. 1개 이상 있어야 하고 여러개 있어도 된다. /ab+c/는 "abbbc"와 맞다. 하지만, + "ac"와는 맞지 않는다.

? 앞 문자가 0번이나 1번은 있어야 맞다. /ab?c/는 "abc"와 맞고 "ac"와도 맞다. 하지만, "abbc"와는 맞지 않는다.

개행문자(\n)외에 모든 문자이면 맞다. /.c/는 "Xc", "1c"와 맞다. 하지만, "ca", "\\nc"와는 맞지 않는다. (\\n은 \n의 . 뜻이다. 특수문자로 사용되는 \를 문자로 표현하기 위해서 앞에 \를 붙여준다. )

문자 'x'를 검색하고 맞으면 'x'를 기억한다. /a(bc)/는 "abcd"에서 검색되고 "bc"를 기억한다. 이 기억된 것은 RegExp (x) 객체의 $1에서 $9까지로 또는 배열 [1]에서 기억된 숫자 [n]개 까지로 호출될 수 있다.

x|y 'x'나 'y'가 있으면 맞다. /abc|xx/는 "abc ZZZ"나 "xx ZZZ"와 맞다.

앞 문자가 n개 있으면 맞다. n은 양수 정수이다. /X{2}/는 "abcXX"와 맞다. "abcXXX"와도 맞고 처음 2개의 "XX"가 {n} 검색된다.

앞 문자가 최소한 n개 있으면 맞다. n은 양수 정수이다. /X{2,}/는 "abcXX"와 맞다. "abcXXX"와도 맞고 3개의 {n,} "XXX"가 검색된다.

{n,m} 앞 문자가 최소한 n번에서 m번까지 있다면 맞다. /a{1,3}/은 "ab", "aaab"와 맞다.

http://crowe.wowdns.com:8000/archives/dev_note/ (94 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

[xyz] xyz 중에서 아무런 문자가 있어도 맞다. 이것은 [x-z]과 같은 지정이다.

[^xyz] xyz 중에서 하나의 문자라도 없으면 맞다. 이것은 [^x-z]과 같은 지정이다.

[\b] backspace

\b 공백같은 것으로 단어의 경계되는 부분에 있는 문자이다. /\bX/는 "Xxx"와 맞다. /X\b/는 "xxX"와 맞다.

\B \b와 반대이다. 단어 경계에 있지 않은 문자만 맞다. /X\Bz/는 "XXz Xzz"에서 두번째의 "Xz"가 검색된다.

\cX control-X 문자와 맞다. /\cM/은 control-M 문자이다.

\d 숫자는 맞다. [0-9]와 같은 지정이다. /\d/는 "hi5"에서 "5"를 검색한다.

\D 숫자가 아닌 것은 맞다. [^0-9]와 같은 지정이다. /\D/는 "hi5"에서 "h"를 검색한다.

\f form-feed(\f) 문자와 맞다.

\n linefeed(\n) 문자와 맞다.

\r carriage return(\c) 문자와 맞다.

space, tab, form feed, line feed를 가지고 있는 white space 문자와 맞다.[ \f\n\r\t\v]와 같은 지정이다. /\s\w*/ \s 는 "abc Xz"에서 "Xz"를 검색한다.

white space 문자가 아닌 하나의 문자이다. [^ \f\n\r\t\v]와 같은 지정이다. /\S\w*/는 "abc Xz"에서 "abc"를 검색 \S 한다.

\t tab 문자

\v vertical tab 문자

\w underscore(_)문자를 포함하는 알파벳과 숫자 0에서 9까지의 문자. [A-Za-z0-9_]와 같은 지정이다.

\W \w의 반대이다. [^A-Za-z0-9_]와 같은 지정이다. /\W/는 "50%"에서 "%"를 검색한다.

n은 양수 정수이다. ()로 묵은 것을 지정한다. \1은 RegExp.$1, \2는 RegExp.$2의 값을 사용한다. /ab(c)de\1/은 \n "abcdec"와 맞다.

\o숫자, \x숫자 escape문자로 ASCII codes 값을 넣을 수 있게한다. 8진, 16진 10진수를 넣을 수 있다.

RegExp 객체

RegExp 객체는 Regular Expression객체의 메소드 exec(), test()와 String 메소드인 match(), replace() 메소드의 실행 후에 생기는 객체입니다. 이 객체는 요소는 있지만 메소드는 없습니다.

var myRe = /b(b)/g

var arr = myRe.exec("abbc")

위에서 myRe가 regular expression 객체이고 arr은 exec()의 결과 값을 가지고 있는 배열입니다. RegExp 객체는 자동으로 만들어 집니 다. exec()를 실행한 후 만들어 집니다. 그래서 아무데서나 사용할 수 있습니다. 아래는 위의 구문을 사용하여 나오는 RegExp 객체의 요 소값입니다.

nn4 RegExp 요소값 ie4, ie5 RegExp 요소값

http://crowe.wowdns.com:8000/archives/dev_note/ (95 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

input = index = 1 multiline = false input = abbc lastMatch = bb lastIndex = 3 lastParen = b $1 = b leftContext = a $2 = rightContext = c $3 = $1 = b $4 = $2 = $5 = $3 = $6 = $4 = $7 = $5 = $8 = $6 = $9 = $7 = $8 = $9 =

위의 각 요소의 값이 RegExp 객체의 값입니다. 브라우저에 따라서 나오는 결과값이 서로 다릅니다. index는 검색된 문자열이 시작하는 위 치이고 lastIndex는 검색된 문자열의 다음의 글자 위치입니다. lastMatch는 검색된 문자열, lastParen은 ()로 기억한 마지막 값, leftContext은 검색된 문자열의 왼쪽의 문자, rightContext은 검색된 문자열의 오른쪽 문자입니다.

변수로 지정한 nn의 input 값이 나오지 않는데 이벤트로 작동시키면 나옵니다. 사용할 수 있는 객체는 TEXT, TEXTAREA, SELECT, LINK 입니다. multiline은 TEXTAREA, LINK로 동작 시키면 'true'로 지정됩니다.

abbc

위의 구문에서 nn4에서 "abbc"링크를 누르면 abbc를 보여줍니다. nn4에서는 직접 값을 보내줄 필요가 없습니다. 폼의 내용은 value가 가고 링크는 링크의 내용 글자가 자동으로 보내집니다. 아래는 이와 같은 동작을 하는 ie용 구문입니다. 아래와 같은 코드로 직접 링크의 내 용 글자를 보내주어야 합니다.

abbc

그리고 $1에서 $9의 값은 /b(b)/g 의 괄호를 지정한 문자열을 저장하는 것입니다. 즉, (b)입니다. 검색된 문자열 중에서 "b"를 기억합니 다. $1은 첫번째로 검색된 것이고 $2는 두번째, $9는 아홉번째 검색된 문자를 저장합니다. 9개가 저장할 수 있는 최대값입니다.

배열 값

var myRe = /b(b)/g

var arr = myRe.exec("abbc")

아래는 위의 구문을 사용하여 자동으로 만들어지는 배열 객체 "arr"의 값입니다.

nn4 배열 요소값 ie4, ie5 배열 요소값

0 = bb input = abbc 1 = b index = 1 index = 1 lastIndex = 3 input = abbc 0 = bb 1 = b

input는 검색했는 문자열이고 0은 최종 검색된 문자열입니다. 즉, arr[0]입니다. 1 = b는 $1과 같은 값입니다. RegExp의 요소인 $1에 서 $9까지 9개의 값을 저장할 수 있지만 배열값은 갯수에 상관없습니다.

$ 사용하기

나이를 입력하세요..

http://crowe.wowdns.com:8000/archives/dev_note/ (97 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

나이를 입력하세요.

위의 폼에 나이를 입력하고 다른 곳을 누르면 입력한 나이를 보여줍니다. 숫자가 아닌 문자를 입력하면 잘못 입력했다는 안내말을 보여줍 니다.

RegExp 사용하기

문자열을 다루는 String 메소드와 함께 문자열을 다루는 방법이 더 강력합니다. Pattern을 만들어 사용할 수도 있고 더 쉽고 빠른 속도로 문자들을 다룰 수 있게 합니다.

1. 숫자인지 확인하기

function verifyDigit(input) {

if( input.search(/\D/) != -1 ) {

alert("숫자만 입력하세요..!!")

input = input.replace(/\D/g, "")

}

return input

}

input로 보내온 값 중에서 0에서 9사이의 숫자가 아닌 모든 문자를 보내왔을 때, 경고 창을 보여주고 그 문자를 없엔다.

2. 문자열 바꾸기

아마 Regular Expression의 사용함의 가장 적절한 곳이 문자열의 교체일것 입니다. 아래는 간단하게 문자열을 다른 문자열로 바꿀 수 있 게 합니다. String 함수를 사용하는 것보다 훨씬 간단합니다. "안녕하세요.. 보아서 반갑습니다.. 안녕하세요.. 보아서 반갑습니다.. "로 나 옵니다. "만나서"를 "보아서"로 바꿉니다. /만나서/g의 g 스위치는 문자열 중에서 2번 이상 같은 것이 있으면 전부 다 바꾸라는 지정입니 다.

var xx = "안녕하세요.. 만나서 반갑습니다.. 안녕하세요.. 만나서 반갑습니다.. "

xx = xx.replace(/만나서/g, "보아서")

alert(xx)

Posted by Crowe Lee at 06:35 PM | Comments (0) | TrackBack

September 15, 2004

정규식 몇가지...

http://crowe.wowdns.com:8000/archives/dev_note/ (98 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

View: Building a regexp

At this point, we have all the basic regexp concepts covered, so let's give a more involved example of a regular expression. We will build a regexp that matches numbers.

The first task in building a regexp is to decide what we want to match and what we want to exclude. In our case, we want to match both integers and floating point numbers and we want to reject any string that isn't a number.

The next task is to break the problem down into smaller problems that are easily converted into a regexp.

The simplest case is integers. These consist of a sequence of digits, with an optional sign in front. The digits we can represent with \d+ and the sign can be matched with [+-]. Thus the integer regexp is

/[+-]?\d+/; # matches integers

A floating point number potentially has a sign, an integral part, a decimal point, a fractional part, and an exponent. One or more of these parts is optional, so we need to check out the different possibilities. Floating point numbers which are in proper form include 123., 0.345, .34, -1e6, and 25.4E-72. As with integers, the sign out front is completely optional and can be matched by [+-]?. We can see that if there is no exponent, floating point numbers must have a decimal point, otherwise they are integers. We might be tempted to model these with \d*\.\d*, but this would also match just a single decimal point, which is not a number. So the three cases of floating point number sans exponent are

/[+-]?\d+\./; # 1., 321., etc.

/[+-]?\.\d+/; # .1, .234, etc.

/[+-]?\d+\.\d+/; # 1.0, 30.56, etc.

These can be combined into a single regexp with a three-way alternation:

/[+-]?(\d+\.\d+|\d+\.|\.\d+)/; # floating point, no exponent

In this alternation, it is important to put '\d+\.\d+' before '\d+\.'. If '\d+\.' were first, the regexp would happily match that and ignore the fractional part of the number.

Now consider floating point numbers with exponents. The key observation here is that both integers and numbers with decimal points are allowed in front of an exponent. Then exponents, like the overall sign, are independent of whether we are matching numbers with or without decimal points, and can be 'decoupled' from the mantissa. The overall form of the regexp now becomes clear:

/^(optional sign)(integer | f.p. mantissa)(optional exponent)$/;

The exponent is an e or E, followed by an integer. So the exponent regexp is

http://crowe.wowdns.com:8000/archives/dev_note/ (99 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

/[eE][+-]?\d+/; # exponent

Putting all the parts together, we get a regexp that matches numbers:

/^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/; # Ta da!

Long regexps like this may impress your friends, but can be hard to decipher. In complex situations like this, the //x modifier for a match is invaluable. It allows one to put nearly arbitrary whitespace and comments into a regexp without affecting their meaning. Using it, we can rewrite our 'extended' regexp in the more pleasing form

/^

[+-]? # first, match an optional sign

( # then match integers or f.p. mantissas:

\d+\.\d+ # mantissa of the form a.b

|\d+\. # mantissa of the form a.

|\.\d+ # mantissa of the form .b

|\d+ # integer of the form a

)

([eE][+-]?\d+)? # finally, optionally match an exponent

$/x;

If whitespace is mostly irrelevant, how does one include space characters in an extended regexp? The answer is to backslash it '\ ' or put it in a character class [ ] . The same thing goes for pound signs, use \# or [#]. For instance, Perl allows a space between the sign and the mantissa/integer, and we could add this to our regexp as follows:

/^

[+-]?\ * # first, match an optional sign *and space*

( # then match integers or f.p. mantissas:

\d+\.\d+ # mantissa of the form a.b

|\d+\. # mantissa of the form a.

|\.\d+ # mantissa of the form .b

|\d+ # integer of the form a

)

([eE][+-]?\d+)? # finally, optionally match an exponent

$/x;

In this form, it is easier to see a way to simplify the alternation. Alternatives 1, 2, and 4 all start with \d+, so it could be factored out:

http://crowe.wowdns.com:8000/archives/dev_note/ (100 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

/^

[+-]?\ * # first, match an optional sign

( # then match integers or f.p. mantissas:

\d+ # start out with a ...

(

\.\d* # mantissa of the form a.b or a.

)? # ? takes care of integers of the form a

|\.\d+ # mantissa of the form .b

)

([eE][+-]?\d+)? # finally, optionally match an exponent

$/x;

or written in the compact form,

/^[+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;

This is our final regexp. To recap, we built a regexp by

● specifying the task in detail,

● breaking down the problem into smaller parts,

● translating the small parts into regexps,

● combining the regexps,

● and optimizing the final combined regexp.

These are also the typical steps involved in writing a computer program. This makes perfect sense, because regular expressions are essentially programs written a little computer language that specifies patterns.

Posted by Crowe Lee at 04:54 PM | Comments (0) | TrackBack

August 22, 2004

JK2를 이용한 Apache2 웹서버와 Tomcat5 의 연동 (Tomcat Web Server Connector)

Apache 2.0.50과 Jakarta Tomcat 5.0.27을 Windows XP Professional 환경에서 JK2로 연결할때 참고한 문서입니다. 무지하게 뻘짓 을... -_-;;; 어디서 참고했늦지는 기억이 가물가물... 원작자님께 감사드립니다.

레드햇 9.0에서 Apache2, MySQL4, PHP4, Tomcat5 연동하기

The Apache 사이트에서 JK2 connector를 다운로드 받습니다. JK2의 binary 배포본은 Solaris와 WIN32용 만이 배포 되고 있으므로 소스 형태의 배포본을 다운로드 받아야 합니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (101 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

http://jakarta.apache.org/site/sourceindex.cgi에서 JK 2.0.2 Source Release tar.gz를 다운로드 받습니다. 배포파일 : jakarta-tomcat-connectors-jk2-src-current.tar.gz

JK2 connector 소스를 컴파일 하면 얻을 수 있는 것이 mod_jk2.so 모듈입니다. Apache 웹서버와 Tomcat을 연동할 mod_jk2.so 모듈 을 얻는 것이 컴파일의 목적입니다.

적당한 위치에서 압축을 풀어줍니다. 소스들을 컴파일하고 필요한 파일들을 이동시키면 더 이상 사용하지 않기 때문에 /usr/local/src에서 압축을 풀어주겠습니다.

shell> mv jakarta-tomcat-connectors-jk2-src-current.tar.gz /usr/local/src

shell> cd /usr/local/src

shell> tar xvfz jakarta-tomcat-connectors-jk2-src-current.tar.gz

압축이 풀리면 해당 디렉토리로 이동합니다. JK2 모듈을 얻기 위해서는 jakarta-tomcat-connectors-jk2-2.0.2-src/jk/native2로 이동 합니다.

shell> cd /usr/local/src/jakarta-tomcat-connectors-jk2-2.0.2-src/jk/native2

다음과 같이 configure를 실행하고 make로 컴파일을 합니다.

shell> ./configure --with-apxs2=/usr/sbin/apxs

shell> make

configure 옵션(autoconf 출력옵션) --with-apxs2[=FILE] Apache 2.0 에서 공유할 DSO 모듈을 build하기 위해 사용합니다. FILE은 Apache apxs tool이 있는 경로를 나타냅니다.

make 시 에러가 발생할 수 있습니다. 이것은 autoconf 실행 중 설정되는 Apache 홈디렉토리가 RedHat에서는 /usr이므로, 컴파일 중에 필요로 하는 libtool 실행파일을 /usr/build/ 경로에서 찾는데 RedHat에서는 /var/www/build/ 경로에 존재하기 때문입니다. 따라서 문제 가 발생할 경우에는 /var/www/build 디렉토리를 /usr 디렉토리 밑으로 복사하기 바랍니다.

문제없이 완료되었다면, 현재 디렉토리를 기준으로 ../build/jk2/apache2/의 경로에 mod_jk2.so 파일이 만들어졌을 것입니다. 이 파일 을 아파치 모듈이 있는 디렉토리로 복사합니다.

shell> pwd

/usr/local/src/jakarta-tomcat-connectors-jk2-2.0.2-src/jk/native2

shell> cd ../build/jk2/apache2

# mod_jk2.so 파일이 존재하는지 확인합니다.

shell> ls

# 레드햇에서는 아파치 모듈이 /usr/lib/apache 디렉토리에 위치합니다.

shell> cp mod_jk2.so /usr/lib/apache/

/usr/local/src/jakarta-tomcat-connectors-jk2-2.0.2-src/jk/conf/workers2.properties 파일을 /etc/httpd/conf/ 위치로 복사합니 http://crowe.wowdns.com:8000/archives/dev_note/ (102 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

다.

shell> cd /usr/local/src/jakarta-tomcat-connectors-jk2-2.0.2-src/jk/conf

shell> cp workers2.properties /etc/httpd/conf

이 파일은 Apache 웹서버에서 JK2 모듈을 사용할 때 필요한 여러가지 설정을 저장한 파일입니다. 이 파일 이외에 jk2.properties 파일이 중요한데, workers2.properties 파일이 Apache 쪽에서 JK2 모듈의 설정을 담당한다면, jk2.properties 파일은 Tomcat 쪽에서 JK2 모 듈을 통한 웹서버와의 연결 설정을 담당합니다. Tomcat 5.0 버전에서는 JK2 connector를 처리할 수 있는 coyote connector가 기본설 치 되어 있기 때문에 jk2.properties 파일을 Tomcat의 conf 디렉토리로 복사할 필요는 없습니다.

/etc/httpd/conf/httpd.conf 파일을 vi와 같은 편집기로 열어 LoadModule 부분을 찾아서 다음을 추가합니다.

LoadModule jk2_module lib/apache/mod_jk2.so

DirectoryIndex 부분을 찾아서 index.jsp를 추가합니다.

DirectoryIndex index.html index.html.var index.php index.phtml index.jsp

저장하고 편집기를 끝냅니다. 다음으로 /etc/httpd/conf 디렉토리에 복사해놓은 workers2.properties를 수정합니다. 아래의 설정 내용으로 구성합니다.

shell> cd /etc/httpd/conf

shell> vi worker2.properties

# Shared memory handling. Needs to be set.

[shm]

file=/var/log/httpd/shm.file

size=1048576

# Example socket channel, explicitly set port and host.

[channel.socket:localhost:8009]

port=8009

host=127.0.0.1

# define the worker

[ajp13:localhost:8009]

channel=channel.socket:localhost:8009

# Announce a "status" worker

[status:status]

# Uri mapping http://crowe.wowdns.com:8000/archives/dev_note/ (103 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

[uri:/jsp-examples/*]

worker=ajp13:localhost:8009

[uri:/servlets-examples/*]

worker=ajp13:localhost:8009

[uri:/tomcat-docs/*]

worker=ajp13:localhost:8009

[uri:/*]

worker=ajp13:localhost:8009

[uri:/status/*]

worker=status:status

위 설정내용은 포스데이타㈜ BPM 사업추진반 장윤기 대리님이 작성한 <아파치와 톰캣 연동 및 로드 발란싱 구현>에서 발췌하여 수정한 것입니다.

/usr/local/share/jakarta-tomcat-5.0.16/conf/jk2.properties를 수정합니다.

shell> cd /usr/local/share/jakarta-tomcat-5.0.16/conf

shell> vi jk2.properties

## THIS FILE MAY BE OVERRIDEN AT RUNTIME. MAKE SURE TOMCAT IS STOPED

## WHEN YOU EDIT THE FILE.

## COMMENTS WILL BE _LOST_

## DOCUMENTATION OF THE FORMAT IN JkMain javadoc.

# Set the desired handler list

# handler.list=apr,request,channelJni

#

# Override the default port for the socketChannel

channelSocket.port=8009

# Default:

# channelUnix.file=${jkHome}/work/jk2.socket

# Just to check if the the config is working

# shm.file=${jkHome}/work/jk2.shm

shm.file=/var/log/httpd/jk2.shm

# In order to enable jni use any channelJni directive

# channelJni.disabled = 0 http://crowe.wowdns.com:8000/archives/dev_note/ (104 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

# And one of the following directives:

# apr.jniModeSo=/opt/apache2/modules/mod_jk2.so

# If set to inprocess the mod_jk2 will Register natives itself

# This will enable the starting of the Tomcat from mod_jk2

apr.jniModeSo=inprocess

위 설정내용은 포스데이타㈜ BPM 사업추진반 장윤기 대리님이 작성한 <아파치와 톰캣 연동 및 로드 발란싱 구현>에서 발췌하여 수정한 것입니다.

모든 설정이 끝났습니다. 실행시키기 전에 /usr 디렉토리에서 /etc/httpd/conf 디렉토리에 연결된 링크 파일을 만들어줍니다. 이것은 mod_jk2.so 모듈을 컴파일 할 때 Apache Home Directory를 /usr 로 인식했기 때문에 workers2.properties 파일을 /usr/conf에서 찾 기 때문입니다. 다음과 같이 실행합니다.

shell> cd /usr

shell> ln -s /etc/httpd/conf ./conf

모든 과정이 끝났으면 Tomcat과 Apache 웹서버를 실행시킵니다. ps 명령어로 현재 실행중인 process 목록을 확인해서 Tomcat과 Apache가 가동중인지 확인하고, 가동중이라면 실행을 중지시킵니다. 참고로 Tomcat의 Process CMD는 java입니다.

# 프로세스가 존재하는지 확인

shell> ps -el

# 프로세스가 존재하면 실행중지

shell> catalina.sh stop

shell> catalina.sh start

# Apache 프로세스가 존재하면 실행중지

shell> /etc/init.d/httpd stop

shell> /etc/init.d/httpd start

# 또는 Apache 프로세스가 존재할 경우

shell> /etc/init.d/httpd restart

클라이언트에서 http://serverURL 로 연결해 봅니다. 웹페이지가 보이면 정상적으로 연동되어 작동하는 것입니다.

jk2.properties의 설정을 변경하였을 경우 이를 적용시키기 위해서는 Tomcat을 재가동 시켜주고 Apache 또한 다시 실행시켜주어야 합니 다. workers2.properties의 설정을 변경한 경우에는 Apache 웹서버만 재가동시킵니다.

Posted by Crowe Lee at 05:54 AM | Comments (0) | TrackBack

March 25, 2004 http://crowe.wowdns.com:8000/archives/dev_note/ (105 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

FreeBSD에서 Oracle 인스톨

View : Installing Oracle

Oracle 설치 Marcel Moolenaar ([email protected])

1. 시작하며

이 문서에서는 Oracle 8.0.5과 Oracle 8.0.5.1 Enterprise Edition의 Linux 판을 FreeBSD에 설치하기 위한 순서를 설명합니다.

2. Linux 환경 세팅

우선은 Ports Collection으로부터 linux_base와 linux_devtools를 설치 하시기 바랍니다. 이 포트는 FreeBSD 3.2의 이후 버젼에서 추가 되었습니다. 만약 FreeBSD 3.2 혹은 그것보다 이전 버젼을 사용하고 있는 경우는 포트 콜렉션을 업데이트 합니다. 하는 김에 FreeBSD 를 업데이트 하는 것도 좋습니다. 만약 linux_base-6.1이나 linux_devtools-6.1로 잘 되지 않으면 5.2를 가지고 하시기 바랍니다.

만약 인텔리전트 에이전트 (intelligent agent)를 실행하고 싶으면 Red Hat TCL 패키지 tcl-8.0.3-20.i386.rpm 도 설치 할 필요가 있습 니다. 공식의 RPM 패키지를 설치 하려면 일반적으로 다음과 같이 합니다.

# rpm -i --ignoreos --root /compat/linux --dbpath /var/lib/rpm package

패키지의 설치시에 에러가 발생해서는 안됩니다.

3. Oracle 환경 구축

Oracle을 설치 하기 전에 , 적절한 환경을 설정할 필요가 있습니다. 이 문서에서는 , Oracle의 설치 가이드에 써 있는 것은 아니고 FreeBSD로 Linux 용 Oracle을 운영하기 위해서 특별히 필요한 작업만을 설명합니다.

3.1. 커널 튜닝

Oracle 설치 가이드에 있듯이 , 공유메모리의 최대 사이즈를 설정하지 않으면 안됩니다. FreeBSD에서는 SHMMAX를 사용하지 않는것에 주의합니다. SHMMAX 는 단지 SHMMAXPGS PGSIZE로부터 계산된 것입니다. 따라서 SHMMAXPGS를 사용하도록 합니다. 설치 가이드 에 기술되고 있는 다른 옵션은 사용할 수 있습니다. 예를 들면 아래와 같습니다.

options SHMMAXPGS=10000

options SHMMNI=100

options SHMSEG=10

options SEMMNS=200

options SEMMNI=70

options SEMMSL=61

이러한 옵션은 Oracle의 용도에 맞춰서 설정하시기 바랍니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (106 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

그리고 , 다음의 옵션이 커널의 배치 파일에 있는지도 확인합니다.

options SYSVSHM #SysV shared memory

options SYSVSEM #SysV semaphores

options SYSVMSG #SysV interprocess communication

3.2. Oracle 계정

다른 계정을 만드는 것과 같이 Oracle의 계정을 생성합니다. Oracle의 계정에 특별히 필요한 것은 Linux의 쉘을 할당하는 것입니다. /etc/ shells에 /compat/linux/bin/bash를 추가해 Oracle의 계정으로 설정합니다.

3.3. 환경 설정

ORACLE_HOME 이나 ORACLE_SID 라고 하는 통상의 Oracle 용 변수 외에 다음의 변수도 설정하여아 합니다.

Variable Value

LD_LIBRARY_PATH $ORACLE_HOME/lib

CLASSPATH $ORACLE_HOME/jdbc/lib/classes111.zip

/compat/linux/bin /compat/linux/sbin /compat/linux/usr/bin PATH /compat/linux/usr/sbin /bin /sbin /usr/bin /usr/sbin /usr/local/bin $ORACLE_HOME/bin

모든 환경 변수는 .profile에 설정합니다. 실제 파일 내용은 다음과 같습니다.

ORACLE_BASE=/oracle; export ORACLE_BASE

ORACLE_HOME=/oracle; export ORACLE_HOME

LD_LIBRARY_PATH=$ORACLE_HOME/lib

export LD_LIBRARY_PATH

ORACLE_SID=ORCL; export ORACLE_SID

ORACLE_TERM=386x; export ORACLE_TERM

CLASSPATH=$ORACLE_HOME/jdbc/lib/classes111.zip

export CLASSPATH

PATH=/compat/linux/bin:/compat/linux/sbin:/compat/linux/usr/bin: \

/compat/linux/usr/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin: \

$ORACLE_HOME/bin

export PATH

4. Oracle 설치

http://crowe.wowdns.com:8000/archives/dev_note/ (107 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

installer를 기동하기 전에 /var/tmp에 .oracle라는 이름의 디렉토리를 만들 필요가 있습니다. 이것은 Linux 에뮬레이터로 설치하면 생거 나는 약간의 문제가 때문입니다. 이 디렉토리는 누구라도 쓸 수 있도록, 또는 oracle 유저의 소유로 설정합니다. 이것으로 특별한 문제없 이 Oracle을 설치 할 수 있습니다. 만약 문제가 발생하면 우선 Oracle 설치 프로그램이나 설정을 검사하시기 바랍니다. Oracle의 설치가 끝나면 다음의 두 가지 부가 작업에서 문제가 발생할 수 있습니다.

자주 발생하는 문제는 TCP 프로토콜 어댑터가 올바르게 설치되어 있지 않아서 발생하는 것입니다. 그 때문에 TCP 리스너를 시작할 수 없 게됩니다. 다음의 설정은 이 문제를 해결하는데 도움이 됩니다.

# cd $ORACLE_HOME/network/lib

# make -f ins_network.mk ntcontab.o

# cd $ORACLE_HOME/lib

# ar r libnetwork.a ntcontab.o

# cd $ORACLE_HOME/network/lib

# make -f ins_network.mk install

한번 더 root.sh를 실행합니다.

4.1. root.sh 수정

Oracle을 설치 할 때 root의 권한으로 실행할 필요가 있는 몇개의 작업은 root.sh로 불리는 쉘 스크립트에 기록됩니다. root.sh 는 orainst 디렉토리에 있습니다. 다음의 수정을 root.sh에 적용하여 올바른 위치에 있는 chown 명령을 사용하도록 하던지, Linux 네이티브 인 쉘의 내에서 스크립트를 실행하도록 합니다.

*** orainst/root.sh.orig Tue Oct 6 21:57:33 1998

--- orainst/root.sh Mon Dec 28 15:58:53 1998

***************

*** 31,37 ****

# This is the default value for CHOWN

# It will redefined later in this script for those ports

# which have it conditionally defined in ss_install.h

! CHOWN=/bin/chown

#

# Define variables to be used in this script

--- 31,37 ----

# This is the default value for CHOWN

# It will redefined later in this script for those ports

# which have it conditionally defined in ss_install.h

! CHOWN=/usr/sbin/chown

#

# Define variables to be used in this script

http://crowe.wowdns.com:8000/archives/dev_note/ (108 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

CD-ROM으로부터 설치하지 않는 경우는 root.sh의 소스에 직접 수정하여도 좋을 것입니다. rthd.sh라는 파일 이름으로 소스 디렉토리의 orainst 내에 있습니다.

4.2. genclntsh 수정

genclntsh 스크립트는 하나의 공유 클라이언트 라이브러리를 생성하는데 사용됩니다. 이것은 데모를 만들 때에 사용됩니다. PATH를 변경 하기 위해서 다음을 수정합니다.

*** bin/genclntsh.orig Wed Sep 30 07:37:19 1998

--- bin/genclntsh Tue Dec 22 15:36:49 1998

***************

*** 32,38 ****

#

# Explicit path to ensure that we're using the correct commands

#PATH=/usr/bin:/usr/ccs/bin export PATH

! PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin export PATH

#

# each product MUST provide a $PRODUCT/admin/shrept.lst

--- 32,38 ----

#

# Explicit path to ensure that we're using the correct commands

#PATH=/usr/bin:/usr/ccs/bin export PATH

! #PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin export PATH

#

# each product MUST provide a $PRODUCT/admin/shrept.lst

5. Oracle 시작

이제 Linux에서와 같이 Oracle을 실행할 수 있습니다.

Posted by Crowe Lee at 02:21 AM | Comments (0) | TrackBack

February 07, 2004

ALTER SESSION KILL 에 대하여...

사용자는 다음과 같은 상황에서 session 을 kill 하려는 시도를 하게 된다. 1. os 에는 process 가 존재하지 않지만, v$session 에는 active 로 존재하고 있을 경우 2. shadow process 는 살아 있는데, client machine 을 rebooting 한 경우 3. session 이 걸고 있던 lock 을 release 해야 할 경우 4. OS 나 Oracle 의 자원을 지나치게 많이 사용하여 성능을 저하시키는 process

http://crowe.wowdns.com:8000/archives/dev_note/ (109 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

그런데, alter system kill session ('sid, serial#'); 후에 다음과 같은 에러가 발생할 경우가 있다.

ora-00030, 00000, "user session ID does not exist"

// *Cause: The user session id no longer exists, probably because the

// session was logged out.

// *Action: Use a valid session ID.

--- 원인과 대책

kill session을 할 수 없는 이유는 PMON이 이미 이 session을 delete하고 있는중이기 때문이다. 즉, PMON 이 dead session 을 clean- up 하고 있는 중에는 serial number의 값이 증가한다.

문제는 PMON이 process를 kill하는 시간인데, transaction의 크기에 따라, PMON의 rollback 시간이 결정된다. 먼저 PMON은 dead process를 찾아내어,이 process가 사용한 resource 를 release하는 시도를 한다. PMON은 계속 이 작업을 시도하다가 마침내, free buffer의 부족으로 더 이상resource를 free-up 하지 못하게 된다. 이 때, 이 process를 delete하고 있다는 message를 trace file에 출력하는데,이것은 process를 delete하는 데 필요한 resource(data cache 내의 free buffer)의 부족으로 위의 작업이 지연되고 있다는 의미이다.

PMON이 process 를 clean-up 할 때 걸리는 시간은, 5분에서 24 시간까지 소요될 수 있다. 문제는 이 process가 hold 하고 있는 lock으 로 인해 특정 작업이 수행되지 못하는 데 있다. MTS 를 사용할 때는 configuration MTS setting,sqlnet.expire_time 사용)에 따라 다르 지만, clean-up 작업을 하는데 72 시간이 소요된 경우도 있다.

아직까지는 PMON이 작업을 마칠 때까지 기다리는 방법 또는 db를 restartup하는 방법 밖에는 없다.

--- PMON 의 작업

PMON은 network failure 나 기타의 원인으로 생긴 old process connection을clean-up 하는 역할을 한다. 그런데, PMON 은 clean-up 해야 하는 connection 중에 정해진 개수 만큼의 transaction 을 rollback 할 수 있는데, 이 값은 initSID.ora 의 cleanup_rollback_entries(default = 20) 에 의해 결정된다. 예를 들어, 1000 개의 uncommitted update가 있다면, 일정한 시간마다 cleanup_rollback_entries 의 개수 만큼의 record 만 rollback 할 수 있으므로 이 작업 동안에 lock 은 그대로 유지된다.

PMON 은 위의 작업 이외에 DB maintenance 역할이 있으므로, 위의 rollback 이비교적 빠르게 처리되지 못할 수도 있다. 이러한 rollback을 빠르게 처리하기 위하여 cleanup_rollback_entries 를 늘릴 수도 있다. 그러나, 그 만큼 일정 시간 동안 PMON 의 작업이 많 아지게 되므로, 다른 사용자들의 작업 요청이 느려지게 되는 trade-off 가 있으므로, 신중히 고려한 후에 수정하는 것이 바람직하다.

alter system kill session 에 의해서도 위와 같이 rollback 이 이루어지는데, 이 session 이 완전히 clean-up 되기 전까지 v$session, v $process에 남아 있게 된다.

--- ALTER SYSTEM KILL SESSION 을 하기 전에 ...

kill session 을 원할 경우는 다음의 순서대로 작업하는 것이 좋다.

1. kill the user process first 2. wait for 3 - 4 minutes 3. query v$session http://crowe.wowdns.com:8000/archives/dev_note/ (110 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

4. if any information find in v$session, query v$lock like

select count(*) from v$lock where SID ='sid';

위의 count(*) 가 0 이 아니라면, 아직 PMON 이 rollback을 끝내지 못한 경우이므로 다시 얼마 후에 v$lock 을 조회하여 lock 의 개수가 감소하였는지 반복적으로 확인한다. 만약, 이 값이 전혀 변하지 않았다면, ALTER SYSTEM KILL SESSION 을 수행하고v$session, v$lock을 query 하여 변화가 있는지 확인 하여 변화가 있다면, 좀 더 기다린다. 그래도, v$lock 의 count(*) 가 0 이 되지 않을 경우, 마지막으로 수행할 수 있는 유일한 방법은 instance 를 restartup 하는 것이다.

Posted by Crowe Lee at 01:06 PM | Comments (0) | TrackBack

25가지 SQL작성법

1.데이터와 비즈니스 어플리케이션을 잘 알아야 한다.

동일한 정보는 다른 비즈니스 데이터 원천으로부터 검색될 수 있다. 이러한 원천에 익숙해야 한다. 당신은 당신의 데이터베이스 안의 데이 터의 크기와 분포를 반드시 알아야 한다. 또한 SQL을 작성하기 전에 비즈니스 개체 안의 관계와 같은 데이터 모델을 전체적으로 이해해야 한다. 이러한 이해는 당신이 여러 테이블에서 정보를 검색하는데 있어서 보다 좋은 쿼리를 작성할 수 있다. DESIGNER/2000과 같은 CASE TOOLS은 다른 비즈니스와 데이터베이스 객체사이의 관계를 문서화 하는데 좋은 역할을 한다.

2.실제 데이터를 가지고 당신의 쿼리를 검사하라.

대부분의 조직은 개발, 검사, 제품의 3가지 데이터베이스 환경을 가진다. 프로그래머는 어플리케이션을 만들고 검사하는데 개발 데이터베 이스 환경을 사용하는데, 이 어플리케이션이 제품 환경으로 전환되기 전에 프로그래머와 사용자에 의해 검사 환경하에서 보다 엄격하게 검 토되어야 한다. SQL이 검사 환경하에서 테스트될 때, 검사 데이터베이스가 가지고 있는 데이터는 제품 데이터베이스를 반영해야 한다. 비실제적인 데이터 를 가지고 테스트된 SQL문은 제품 안에서는 다르게 작동할 수 있다. 엄격한 테스트를 보장하기 위해서는, 검사 환경하에서의 데이터 분포 는 반드시 제품 환경에서의 분포와 밀접하게 닮아야 한다.

3.동일한 SQL을 사용하라.

가능한한 BIND VARIABLE, STORED PROCEDURE, PACKAGE의 이점을 활용하라. DENTICAL SQL문의 이점은 PARSING이 불필요하 기에 데이터베이스 서버안에서 메모리 사용의 축소와 빠른 수행을 포함한다. 예로서 아래의 SQL 문은 IDENTICAL하지 않다.

SELECT * FROM EMPLOYEE WHERE EMPID = 10;

SELECT * FROM EMPLOYEE WHERE EMPID = 10;

SELECT * FROM EMPLOYEE WHERE EMPID = 20;

그러나 I_EMPID라고 이름 주어진 BIND VARIABLE을 사용하면 SQL 문은 이렇게 된다.

SELECT * FROM EMPLOYEE WHERE EMPID = :I_EMPID;

http://crowe.wowdns.com:8000/archives/dev_note/ (111 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

4.주의 깊게 인덱스를 사용하라.

테이블상에 모든 필요한 인덱스는 생성되어야 한다. 하지만 너무 많은 인덱스는 성능을 떨어뜨릴 수 있다. 그러면 어떻게 인덱스를 만들 칼 럼을 선택해야 하는가?

*최종 사용자에 의해 사용되는 어플리케이션 SQL과 쿼리의 WHERE 절에서 빈번하게 사용되는 칼럼에 인덱스를 만들어야 한다.

*SQL 문에서 자주 테이블을 JOIN하는데 사용되는 칼럼은 인덱스되어야 한다.

*같은 값을 가지는 ROW가 적은 비율을 가지는 칼럼에 인덱스를 사용하라.

*쿼리의 WHERE 절에서 오직 함수와 OPERATOR로 사용되는 칼럼에는 인덱스를 만들면 안된다.

*자주 변경되거나 인덱스를 만들때 얻는 효율성보다 삽입, 갱신, 삭제로 인해 잃는 효율성이 더 큰 칼럼에는 인덱스를 만들면 안된다. 이러 한 OPERATION은 인덱스를 유지하기 위한 필요 때문에 느려진다.

*UNIQUE 인덱스는 더 나은 선택성 때문에 NONUNIQUE 인덱스보다 좋다. PRIMARY KEY 칼럼에 UNIQUE 인덱스를 사용한다. 그리고 FOREIGN KEY 칼럼과 WHERE 절에서 자주 사용되는 칼럼에는 NONUNIQUE 인덱스를 사용한다.

5.가용한 인덱스 PATH를 만들어라

인덱스를 사용하기 위해서는 기술한 SQL문을 이용할 수 있는 식으로 SQL을 작성하라. OPTIMIZER는 인덱스가 존재하기 때문에 인덱스 를 사용하는 ACESS PATH를 사용할 수 없다. 따라서 ACCESS PATH는 반드시 SQL이 사용할 수 있게 만들어 져야 한다. SQL HINT를 사 용하는 것은 인덱스 사용을 보증해주는 방법중 하나이다. 특정 ACCESS PATH를 선택하기 위한 다음의 힌트를 참고 하라.

6.가능하면 EXPLAIN과 TKPROF를 사용하라

만약 SQL문이 잘 다듬어지지 않았다면 비록 오라클 데이터베이스가 잘 짜여져 있어도 효율성이 떨어질 것이다. 이럴 경우 EXPLAIN TKPROF에 능숙해져야 한다. EXPALIN PLAN은 SQL이 사용하는 ACCESS PATH를 발견할 수 있게 해주고 TKPROF는 실제 PERFORMANEC의 통계치를 보여준다. 이 TOOL은 오라클 서버 소프트웨어에 포함되어 있고 SQL의 성능을 향상시켜 준다.

7.OPTIMIZER를 이해하라.

SQL은 RULE-BASED나 COST-BASED중 하나를 이용해서 기동된다.기존의 소프트웨어는 RULE BASED 방식을 채택하고 있다. 그리고 많은 오라클 소프트웨어가 이러한 방식을 오랫동안 사용해 왔다. 그러나 새로 출시된 소프트웨어에 대해서는 COST BASED 방식의 OPTIMIZER를 고려해야 한다. 오라클은 새로 출시되는 프로그램을 COST BASED방식으로 업그레이드 시켜왔으며 이러한 방식은 시스템 을 훨씬 더 안정적으로 만들었다. 만약 COST BASED방식의 OPTIMIZER를 사용한다면 반드시 ANALYZE 스키마를 정기적으로 사용해야 한다. ANALYZE스키마는 데이터베이스 통계를 데이터 사전 테이블에 기록하는 역할을 수행하며 그렇게 되면 COST BASED OPTIMIZER 가 그것을 사용하게 된다. SQL은 COST BASED OPTIMIZER를 사용할 때만 잘 조정될 수 있다. 만약 RULE BASED에서 COST BASED 로 바꾸고 싶다면 데이터베이스를 사용하는 모든 소프트웨어의 모든 SQL문의 성능을 평가해 보아야 한다.

8.지엽적으로 동작하더라도 전역적으로 생각하라

항상 주의할 것은 하나의 SQL문을 조정하기 위해 생긴 데이터베이스안의 변화는 다른 응용프로그램이나 다른 사용자가 이용하는 다른 명 령문에 영향을 미친다는 사실이다.

9.WHERE절은 매우 중요하다.

http://crowe.wowdns.com:8000/archives/dev_note/ (112 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

비록 인덱스가 가용하다고 해도 다음의 WHERE 절은 그 인덱스 ACCESS PATH를 사용하지 않는다.(즉 COL1 과 COL2는 같은 테이블에 있으며 인덱스는 COL1에 만들어진다.)

COL1 > COL2

COL1 < COL2

COL1 > = COL2

COL1 <= COL2

COL1 IS NULL

COL1 IS NOT NULL

인덱스는 NULL값을 갖는 칼럼에는 ROWID를 저장하지 않는다. 따라서 NULL값을 갖는 ROW를 검색할 때는 인덱스를 사용하지 못한다.

COL1 NOT IN (VALUE1, VALUE2 )

COL1 != EXPRESSION

COL1 LIKE ''%PATTERN''

이럴 경우 THE LEADING EDGE OF THE INDEX(?) 는 작동되지 않고 인덱스가 사용되지 못하게 한다. 한편 COL1 LIKE ''PATTERN %''이나 COL1 LIKE ''PATTERN %PATTERN%'' 는 한정된 인덱스 스캔을 수행하기 때문에 인덱스를 사용할 수 있다.

NOT EXISTS SUBQUERY

EXPRESSION1 = EXPRESSION2

인덱스된 컬럼을 포함하는 표현(EXPRESSION), 함수, 계산(CALCULATIONS)은 인덱스를 사용하지 못한다. 다음의 예에서 보면 UPPER SQL 함수를 사용하면 인덱스 스캔을 사용할 수 없고 FULL TABLE SCAN으로 끝나고 만다.

SELECT DEPT_NAME

FROM DEPARTMENT

WHERE UPPER(DEPT_NAME) LIKE ''SALES%'';

10.레코드 필터링을 위해서는 HAVING보다는 WHERE를 사용하라

인덱스가 걸려있는 칼럼에는 GROUP BY와 같이 HAVING절을 사용하지 마라. 이 경우 인덱스는 사용되지 않는다. 또한 WHERE절로 된 ROW를 사용하지 마라. 만약 EMP테이블이 DEPTID컬럼에 인덱스를 가지고 있다면 다음 질의는 HAVING 절을 이용하지 못한다.

SELECT DEPTID,

SUM(SALARY)

FROM EMP

GROUP BY DEPTID

HAVING DEPTID = 100;

http://crowe.wowdns.com:8000/archives/dev_note/ (113 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

그러나 같은 질의가 인덱스를 사용하기 위해 다시 씌여질 수 있다.

SELECT DEPTID,

SUM(SALARY)

FROM EMP

WHERE DEPTID = 100

GROUP BY DEPTID;

11. WHERE 절에 선행 INDEX 칼럼을 명시하라.

복합 인덱스의 경우, 선행 인덱스가 WHERE절에 명시되어 있다면 쿼리는 그 인덱스 를 사용할 것이다. 다음의 질의는 PART_NUM과 PRODUCT_ID 칼럼에 있는 PRIMARY KEY CONSTRAINT에 기초한 복합 인덱스를 이용할 것이다.

SELECT *

FROM PARTS

WHERE PART_NUM = 100;

반면, 다음의 쿼리는 복합인덱스를 사용하지 않는다.

SELECT *

FROM PARTS

WHERE PRODUCT_ID = 5555;

같은 요청(REQUEST)이 인덱스를 이용하기 위해 다시 씌어 질 수 있다. 다음 질의의 경우, PART_NUM컬럼은 항상 0 보다 큰 값을 가질것 이다.

SELECT *

FROM PARTS

WHERE PART_NUM > 0

AND PRODUCT_ID = 5555;

12.인덱스 SCAN과 FULL TABLE SCAN을 평가하라.

한 행(ROW)의 15% 이상을 검색하는 경우에는 FULL TABLE SCAN이 INDEX ACESS PATH보다 빠르다. 이런 경우, SQL이 FULL TABLE SCAN을 이용할 수 있도록 여러분 스스로 SQL을 작성하라. 다음의 명령문은 비록 인덱스가 SALARY COLUMN에 만들어져 있어 도 인덱스 SCAN을 사용하지 않을 것이다. 첫 번째 SQL에서, FULL HINT를 사용한다면 오라클은 FULL TABLE SCAN을 수행할 것이다. 인덱스의 사용이 나쁜 점이 더 많다면 아래의 기술을 이용해서 인덱스 수행을 막을 수 있다.

SELECT * --+FULL

FROM EMP

http://crowe.wowdns.com:8000/archives/dev_note/ (114 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

WHERE SALARY = 50000;

SELECT *

FROM EMP

WHERE SALARY+0 = 50000;

다음의 명령문은 비록 인덱스가 SS# COLUMN에 있어도 인덱스 SCAN을 사용하지 않을 것이다.

SELECT *

FROM EMP

WHERE SS# || '' '' = ''111-22-333'';

오라클이 불분명한 데이터 변환을 수행해야 하는 경우 인덱스가 항상 사용되지 않는 것은 아니다. 다음의 예를 보면, EMP 칼럼에 있는 SALARY는 숫자형 칼럼이고 문자형이 숫자값으로 변환된다.

SELECT *

FROM EMP

WHERE SALARY = ''50000'';

테이블의 행이 15%이거나 그보다 작을 경우 인덱스 스캔은 보다 잘 수행 될 것이다. 왜냐 하면 인덱스 스캔은 검색된 행(ROW)하나 하나 마다 다중의 논리적인 읽기 검색(READ)을 할 것이기 때문이다. 그러나 FULL TABLE SCAN은 하나의 논리적인 읽기 검색 영역 안의 BLOCK에 있는 모든 행들을 읽을 수 있다. 그래서 테이블의 많은 행들에 접근해야 하는 경우에는 FULL TABLE SCAN이 낫다. 예로 다음 의 경우를 보자. 만약 EMP TABLE과 그 테이블의 모든 인덱스에 대해 ANALYZE라는 명령어가 수행된다면, 오라클은 데이터 사전인 USER_TABLES와 USER_INDEXES에 다음과 같은 통계치를 산출해 낸다.

TABLE STATISTICS:

NUM_ROWS = 1000

BLOCKS = 100

INDEX STATISTICS:

BLEVEL = 2

AVG_LEAF_BLOCKS_PER_KEY = 1

AVG_DATA_BLOCKS_PER_KEY = 1

이러한 통계치 에 근거해서, 아래에 보이는 것이 각각의 다른 SCAN에 대한 논리적인 읽기(READ)-즉 ACESS된 BLOCK이 될 것이다.

USE OF INDEX TO RETURN ONE ROW = 3

(BLEVEL+(AVG_LEAF_BLOCKS_PER_KEY - 1) +

AVG_DATA_PER_KEY

http://crowe.wowdns.com:8000/archives/dev_note/ (115 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

FULL TABLE SCAN = 100

(BLOCKS)

USE OF INDEX TO RETURN ALL ROWS = 3000

(NUM_ROWS * BLOCKS ACCESSED TO RETURN ONE ROW USING INDEX)

13. 인덱스 스캔에 ORDER BY를 사용하라

오라클의 OPTIMIZER는 , 만약 ORDER BY라는 절이 인덱스된 칼럼에 있다면 인덱스 스캔을 사용할 것이다. 아래의 질의는 이러한 점을 보여 주는 것인데 이 질의는 비록 그 칼럼이 WHERE 절에 명시되어 있지 않다고 해도 EMPID컬럼에 있는 가용한 인덱스를 사용할 것이 다. 이 질의는 인덱스로부터 각각의 ROWID를 검색하고 그 ROWID를 사용하는 테이블에 접근한다.

SELECT SALARY

FROM EMP

ORDER BY EMPID;

만약 이 질의가 제대로 작동하지 않는다면, 당신은 위에서 명시되었던 FULL HINT를 사용하는 같은 질의를 다시 작성함으로써 다른 대안들 을 이용해 볼 수 있다.

14. 자신의 데이터를 알아라

내가 이미 설명한 것처럼, 당신은 당신의 데이터를 상세하게 알고 있어야 한다. 예를 들어 당신이 BOXER라는 테이블을 가지고 있고 그 테 이블이 유일하지 않은 인덱스를 가진 SEX라는 컬럼과 BOXER_NAME이라는 두 개의 테이블을 가지고 있다고 가정해 보자. 만약 그 테이 블에 같은 수의 남자, 여자 복서가 있다면 오라클이 FULL TABLE SCAN을 수행하는 경우 다음의 질의가 훨씬 빠를 것이다.

SELECT BOXER_NAME

FROM BOXER

WHERE SEX = ''F'';

당신은 다음과 같이 기술함으로써 질의가 FULL TABLE SCAN을 수행하는지를 확실하게 해 둘 수 있다.

SELECT BOXER_NAME --+ FULL

FROM BOXER

WHERE SEX = ''F'';

만약 테이블에 980 명의 남성 복서 데이터가 있다면, 질의는 인덱스 SCAN으로 끝나기 때문에 아래형식의 질의가 더 빠를 것이다.

SELECT BOXER_NAME --+ INDEX (BOXER BOXER_SEX)

FROM BOXER

WHERE SEX = ''F'';

http://crowe.wowdns.com:8000/archives/dev_note/ (116 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

이 예는 데이터의 분포에 대해 잘 알고 있는 것이 얼마나 중요한 가를 예시해 준다. 데이터가 많아지고(GROW) 데이터 분포가 변화하는 것 처럼 SQL 도 매우 다양할 것이다. 오라클은 OPTIMIZER 가 테이블에 있는 데이터의 분포를 잘 인식하고 적절한 실행 계획을 선택하도록 하기 위해 오라클 7.3 에 HISTOGRAMS라는 기능을 추가했다.

15. KNOW WHEN TO USE LARGE-TABLE SCANS.

작거나 큰 테이블에서 행들을 추출할 때, 전체 테이블의 검색은 인텍스를 사용한 검색보다 성능이 더 좋을 수도 있다. 매우 큰 테이블의 인 덱스 검색은 수많은 인덱스와 테이블 블록의 검색이 필요할수도 있다. 이러한 블록들이 데이터베이스 버퍼 캐쉬에 이동되면 가능한한 오래 도록 그곳에 머무른다. 그래서 이러한 블록들이 다른 질의등에 필요하지 않을 수도 있기 때문에, 데이터베이스 버퍼 히트 비율이 감소하며 다중 사용자 시스템의 성능도 저하되기도 한다. 그러나 전체 테이블 검색에 의해서 읽혀진 블록들은 데이터베이스 버퍼 캐쉬에서 일찍 제 거가 되므로 데이터베이스 버퍼 캐쉬 히트 비율은 영향을 받지 않게 된다.

16. MINIMIZE TABLE PASSES.

보통, SQL질의시 참조하는 테이블의 숫자를 줄임으로 성능을 향상시킨다. 참조되는 테이블의 숫자가 적을수록 질의는 빨라진다. 예를 들 면 NAME, STATUS, PARENT_INCOME, SELF_INCOME의 네개의 컬럼으로 이루어진 학생 테이블에서 부모님에 의존하는 학생과 독립 한 학생의 이름과 수입에 대해서 질의시, 이 학생 테이블을 두번 참조하여 질의하게 된다..

SELECT NAME, PARENT_INCOME

FROM STUDENT

WHERE STATUS = 1

UNION

SELECT NAME, SELF_INCOME

FROM STUDENT

WHERE STATUS = 0;

( NAME이 프라이머리 키이며, STATUS는 독립한 학생의 경우는 1, 부모님에 의존적인 학생은 0으로 표시한다.) 위의 같은 결과를 테이블 을 두번 참조하지 않고도 질의 할 수 있다.

SELECT NAME,PARENT_INCOME*STATUS + SELF_INCOME(1-STATUS)

FROM STUDENT;

17. JOIN TABLES IN THE PROPER ORDER.

다수의 테이블 조인시 테이블들의 조인되는 순서는 매우 중요하다. 전반적으로, 올바른 순서로 테이블이 조인되었다면 적은 수의 행들이 질의시 참조된다. 언제나 다수의 조인된 테이블들을 질의시 우선 엄격하게 조사하여 행들의 숫자를 최대한으로 줄인다. 이러한 방법으로 옵티마이저는 조인의 차후 단계에서 적은 행들을 조사하게 된다. 뿐만 아니라, 여러 조인을 포함하는 LOOP JOIN에서는 가장 먼저 참조되 는 테이블(DRIVING TABLE)이 행들을 최소한으로 리턴하도록 해야한다. 그리고, 마스터와 상세 테이블 조인시에는(예를 들면 ORDER & ORDER LINE ITEM TABLES) 마스터 테이블을 먼저 연결 시켜야 한다. 규칙에 근거한 옵티마이저의 경우에는 FROM CLAUSE의 마지 막 테이블이 NESTED LOOP JOIN의 DRIVING TABLE이 된다. NESTED LOOP JOIN이 필요한 경우에는 LOOP의 안쪽의 테이블에는 인텍스를 이용하는 것을 고려할 만하다. EXPLAIN PLAN과 TKPROF는 조인 타입, 조인 테이블 순서, 조인의 단계별 처리된 행들의 숫자들을 나타낸다. 비용에 근거한 옵티마이저의 경우에는 WHERE CLAUSE에 보여지는 테이블의 순서는 옵티마이저가 가장 최적의 실행 계획을 찾으려고 하 는 것과 상관 없다. 조인되는 테이블의 순서를 통제하기 위해서 ORDERED HINT를 사용하는 것이 낫다.

http://crowe.wowdns.com:8000/archives/dev_note/ (117 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

SELECT ORDERS.CUSTID, ORDERS.ORDERNO,

ORDER_LINE_ITEMS.PRODUCTNO --+ORDERED

FROM ORDERS, ORDER_LINE_ITEMS

WHERE ORDERS.ORDERNO = ORDER_LINE_ITEMS.ORDERNO;

18. USE INDEX-ONLY SEARCHES WHEN POSSIBLE.

가능하다면, 인덱스만을 이용하여 질의를 사용하라. 옵티마이저는 오직 인덱스만을 찾을 것이다. 옵티마이저는 SQL을 만족시키는 모든 정 보를 인덱스에서 찾을 수 있을 때, 인덱스만을 이용할 것이다. 예를들면, EMP테이블이 LANME과 FNAME의 열에 복합 인덱스를 가지고 있 다면 다음의 질의는 인덱스만은 이용할 것이다.

SELECT FNAME

FROM EMP

WHERE LNAME = ''SMITH'';

반면에 다음의 질의는 인덱스와 테이블을 모두 참조한다.

SELECT FNAME , SALARY

FROM EMP

WHERE LNAME = ''SMITH'';

19. REDUNDANCY IS GOOD.

WHERE CLAUSE에 가능한한 많은 정보를 제공하라. 예를 들면 WHERE COL1 = COL2 AND COL1 = 10이라면 옵티마이저는 COL2=10이라고 추론하지만, WHERE COL1 = COL2 AND COL2 = COL3이면 COL1=COL3이라고 초론하지는 않는다.

20. KEEP IT SIMPLE, STUPID.

가능하면 SQL문을 간단하게 만들라. 매우 복잡한 SQL문은 옵티마이저를 무력화시킬 수도 있다. 때로는 다수의 간단한 SQL문이 단일의 복잡한 SQL문보다 성능이 좋을 수도 있다. 오라클의 비용에 근거한 옵티마이저는 아직은 완벽하지 않다. 그래서 EXPLAIN PLAN에 주의 를 기울여야 한다. 여기서 비용이란 상대적인 개념이기에 정확히 그것이 무엇을 의미하는지 알지 목한다. 하지만 분명한 것은 적은 비용이 보다 좋은 성능을 의미한다는 것이다. 종종 임시 테이블을 사용하여 많은 테이블들을 포함하는 복잡한 SQL 조인을 쪼개는 것이 효율적일 수도 있다. 예를 들면, 조인이 대량의 데이터가 있는 8개의 테이블을 포함할 때, 복잡한 SQL을 두 세개의 SQL로 쪼개는 것이 낫을 수 있다. 각각의 질의는 많아야 네개정도의 테이블들을 포함하며 그 중간 값을 저장하는 것이 낫을 수 있다.

21. YOU CAN REACH THE SAME DESTINATION IN DIFFERENT WAYS.

많은 경우에, 하나 이상의 SQL문은 의도한 같은 결과를 줄 수 있다. 각각의 SQL은 다른 접근 경로를 사용하며 다르게 수행한다. 예를들 면, MINUS(-) 산술자는 WHERE NOT IN (SELECT ) OR WHERE NOT EXISTS 보다 더 빠르다. 예를들면, STATE와 AREA_CODE에 각각 다른 인덱스가 걸려 있다. 인덱스에도 불구하고 다음의 질의는 NOT IN의 사용으로 인해 테이 블 전체를 조사하게 된다.

http://crowe.wowdns.com:8000/archives/dev_note/ (118 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

SELECT CUSTOMER_ID

FROM CUSTOMERS

WHERE STATE IN (''VA'', ''DC'', ''MD'')

AND AREA_CODE NOT IN (804, 410);

그러나 같은 질의가 다음 처럼 쓰여진다면 인덱스를 사용하게 된다

SELECT CUSTOMER_ID

FROM CUSTOMERS

WHERE STATE IN (''VA'', ''DC'', ''MD'')

MINUS

SELECT CUSTOMER_ID

FROM CUSTOMERS

WHERE AREA_CODE IN (804, 410);

WHERE절에 OR을 포함한다면 OR대신에 UNION을 사용할 수 있다. 그래서, SQL 질의를 수행하기 전에 먼저 실행계획을 조심스럽게 평 가해야 한다. 이러한 평가는 EXPLAIN PLAN AND TKPROF를 이용하여 할 수 있다.

22. USE THE SPECIAL COLUMNS.

ROWID AND ROWNUM 열을 이용하라. ROWID를 이용하는 것이 가장 빠르다. 예를들면, ROWID를 이용한 UPDATE는 다음과 같다.

SELECT ROWID, SALARY

INTO TEMP_ROWID, TEMP_SALARY

FROM EMPLOYEE;

UPDATE EMPLOYEE

SET SALARY = TEMP_SALARY * 1.5

WHERE ROWID = TEMP_ROWID;

ROWID값은 데이터베이스에서 언제나 같지는 않다. 그래서, SQL이나 응용 프로그램이용시 ROWID값을 절대화 시키지 말라. 리턴되는 행들의 숫자를 제한시키기위해 ROWNUM을 이용하라. 만약에 리턴되는 행들을 정확히 모른다면 리턴되는 행들의 숫자를 제한하기위해 ROWNUM을 사용하라. 다음의 질의는 100개 이상의 행들을 리턴하지는 않는다.

SELECT EMPLOYE.SS#, DEPARTMENT.DEPT_NAME

FROM EMPLOYEE, DEPENDENT

WHERE EMPLOYEE.DEPT_ID = DEPARTMENT.DEPT_ID

AND ROWNUM < 100;

http://crowe.wowdns.com:8000/archives/dev_note/ (119 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

23.함축적인 커서대신 명시적인 커서를 사용하라.

함축적 커서는 여분의 FETCH를 발생시킨다. 명시적 커서는 DECLARE, OPEN, FETCH와 CLOSE CURSOR문을 사용하여 개발자에 의해 서 생성된다. 함축 커서는 DELETE, UPDATE, INSERT와 SELECT문을 사용하면 오라클에 의해서 생성된다.

24.오라클 병렬 쿼리 옵션을 찾아서 이용하라.

병렬 쿼리 옵션을 사용하면, 보다 빠른 성능으로 SQL을 병렬로 실행할 수 있다. 오라클 7에서는, 오직 FULL TABLE SCAN에 기반한 쿼리 만이 병렬로 수행될 수 있다. 오라클 8에서는, 인덱스가 분할되어있다면 INDEXED RANGE SCANS에 기반한 쿼리도 병렬로 처리될 수 있 다. 병렬 쿼리 옵션은 다수의 디스크 드라이버를 포함하는 SMP와 MPP SYSTEM에서만 사용될 수 있다.

오라클 서버는 많은 우수한 특성을 가지고 있지만, 이러한 특성의 존재만으로는 빠른 성능을 보장하지 않는다. 이러한 특성을 위해서 데이 터베이스를 조정해야하며 특성을 이용하기 위해 특별하게 SQL을 작성해야 한다. 예를 들면, 다음의 SQL은 병렬로 수행될 수 있다.

SELECT * --+PARALLEL(ORDERS,6)

FROM ORDERS;

25.네트웍 소통량을 줄이고 한번에 처리되는 작업량을 늘려라.

ARRAY PROCESSING과 PL/SQL BLOCK을 사용하면 보다 나은 성능을 얻을 수 있고 네트웍 소통량을 줄인다. ARRAY PROCESSING은 하나의 SQL문으로 많은 ROW를 처리할 수 있게 한다. 예를 들면, INSERT문에서 배열을 사용하면 테이블내의 1,000 ROW를 삽입할 수 있다. 이러한 기술을 사용하면 주요한 성능 향상을 클라이언트/서버와 배치시스템에서 얻어질 수 있다.

복합 SQL문은 과도한 네트웍 소통을 유발할 수 있다. 그러나 만일 SQL문이 단일 PL/SQL 블록안에 있다면, 전체 블록은 오라클 서버에 보 내져서 그곳에서 수행되고, 결과는 클라이언트의 APPLICATION에게 돌아온다.

개발자와 사용자는 종종 SQL을 데이터베이스에서 데이터를 검색하고 전송하는 간단한 방법으로 사용한다. 때때로 직접적으로 SQL을 작 성하지 않고 코드 발생기를 사용하여 작성한 APPLICATION은 심각한 성능 문제를 일으킨다. 이러한 성능감퇴는 데이터베이스가 커지면 서 증가한다.

SQL은 유연하기 때문에, 다양한 SQL문으로 같은 결과를 얻을 수 있다. 그러나 어떤 문은 다른 것보다 더 효율적이다. 여기에 기술된 팁과 기법을 사용하면 빠르게 사용자에게 정보를 제공할 수 있는 APPLICATION과 리포트를 얻을 수 있다.

Posted by Crowe Lee at 10:41 AM | Comments (0) | TrackBack

January 27, 2004

JavaServer Faces Tutorial

2003년 10월 18일 부디 쿼니아완(Budi Kurniawan), 역 이상화

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

http://crowe.wowdns.com:8000/archives/dev_note/ (120 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

고 있다. 본 기사를 무리없이 이해하기 위해서는 서블릿, JSP, 자바빈즈, 커스텀 태그에 관한 선행 지식이 필요하다.

무엇보다도 JSF 애플리케이션은 서블릿/JSP 애플리케이션이다. 웹 디스크립터 파일, JSP 페이지, 커스텀 태그 라이브러리, 정적인 파일들 을 가지고있다. 기존 것들과 다른점은 JSF 애플리케이션은 이벤트-드리븐 방식이라는 것이다. 이벤트 리스너 클래스를 사용함으로써 애플 리케이션의 작동 방법을 결정할 수 있다. 다음과 같은 단계를 통해 JSF 애플리케이션을 작성할 수 있다.

1. HTML를 캡슐화하는 JSF 컴포넌트를 사용하여 JSP 페이지를 작성한다. 2. 사용자 입력과 컴포넌트 데이터의 값을 저장할 수 있는 자바빈즈 파일을 만든다. 3. 사용자가 버튼을 클릭하거나 폼을 제출할 때 발생하는 이벤트를 처리할 수 있는 이벤트 리스너를 작성한다. JSF는 ActionEvent와 ValueChangedEvent 2가지를 지원한다. ActionEvent는 버튼을 클릭하거나 폼을 제출할 때 발생 되며, ValueChangedEvent는 JSF 컴포넌트가 변경될 때 일어난다.

이제 JSF가 어떻게 동작하는지 자세히 알아보자.

JSF 작동방법

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

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

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

클라이언트 브라우저속 웹 컨트롤에 의해 발생하는 이벤트는 브라우저 종류, 요청 URL과 같은 HTTP 요청으로 캡슐화된다. 그러므로 Faces 서블릿 처리가 필요한 모든 요청은 바로 서블릿으로 전달되어야 한다. 그렇다면 어떻게 모든 HTTP 요청을 Faces 서블릿으로 처리 하게 할 것인가? 특정한 URL 패턴을 Faces 서블릿으로 매핑하기 위해 배치 디스크립터에다가 servlet-mapping 요소를 기술하면 된다. 관례상 다음과 같이 /faces/* 패턴을 사용한다.

Faces Servlet

javax.faces.webapp.FacesServlet

1

Faces Servlet http://crowe.wowdns.com:8000/archives/dev_note/ (121 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

/faces/*

요청하는 URL은 반드시 에 선언된 패턴을 포함하고있어야 한다. 설정하는 것은 별로 어렵지않으며 Faces 서블릿을 포함하 고 있는 요소가 애플리케이션이 시작될 때 서블릿을 로딩 시키는 요소를 가지고 있다는 것을 기억해두자.

컴포넌트에 의해 발생하는 모든 이벤트를 처리하기 위해 반드시 이벤트 리스너를 작성해야 하고 모든 컴포넌트를 등록해야 한다. 이는 컴 포넌트를 나타내는 커스텀 태그 속 요소를 사용하면 된다. 예를 들어 커맨드 버튼에 의해 발생하는 이벤트를 처리하 기 위해 jsfApp.MyActionListener라는 리스너를 만들었다면 JSP 페이지 안에 다음과 같이 작성해야 한다.

액션 리스너는 반드시 javax.faces.event.ActionListener 인터페이스를 상속 받고 값 변경에 관한 리스너는 javax.faces.event. ValueChangedListener를 상속 받아야 한다. 이제 이벤트-드리븐 방식으로 JSF가 동작 하는 방법을 보기위해 간단한 애플리케이션을 작 성해 보도록 하겠다.

간단한 JSF 애플리케이션

여기에서는 두 개의 숫자를 더하는 간단한 JSF 애플리케이션을 작성할 것이다. 애플리케이션을 실행시키기 위해 Tomcat 5와 JWSDP에 포함된 JSF v1.0 EA4가 사용된다(이것들은 JWSDP(Java Web Services Developer Pack) 1.2에 포함되어 있다). 애플리케이션은 다음 과 같은 것들로 구성되어 있다.

● adder.jsp, JSP 페이지

● NumberBean, 데이터 저장소 자바빈즈

● MyActionListener, 액션 리스너

● web.xml, 배치 디스크립터

JSF 애플리케이션을 실행시키기 위해 JSF 관련 라이브러리를 포함하고있는 .jar 파일이 필요하다. 일단 JWSDP 1.2를 설 치하면 jsf/lib 디렉토리 밑에 앞서 말한 파일을 찾을 수 있을 것이다. WEB-INF/lib 디렉토리 밑에 .jar 파일 복사한다. 다 음은 복사할 .jar, .tld 파일들이다.

● jsf-api.jar 파일은 Faces 서블릿과 javax.faces 패키지 내 관련 클래스들을 포함하고 있다.

● jfs-ri.jar JSF 실행과 관련된 파일이다.

● jstl_el.jar는 JSTL 표현 수식 문법을 처리한다.

● standard.jar는 JSTL 태그들을 사용하거나 JFS 실행을 위한 참조 클래스에 의해 참고 된다.

덧붙여 JSF 애플리케이션은 아파치 자카르타의 일부인 다음과 같은 라이브러리를 필요로 한다. 이 라이브러리들은 본 기사 와 함께 애플리케이션에 포함되어 있다.

● commons-beanutils.jar 파일은 JavaBeans 컴포넌트 프로퍼티에 접근해서 정의하기 위한 유틸리티를 포함한다.

● commons-digester.jar 파일은 Apache Common Digester 클래스들을 포함하고있는 라이브러리이다.

● commons-logging.jar 파일은 일반적인 목적의 유연한 로깅 도구이다.

http://crowe.wowdns.com:8000/archives/dev_note/ (122 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

아래 소 주제에서는 예제의 각 부분에 대해 논의하고 있다. 마지막 소주제인 "애플리케이션 실행과 컴파일"에서는 JSF 애플리케이션이 어 떻게 동작하는지 보여준다.

디렉토리 구조 만들기

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

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

배치 디스크립터 작성하기

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

[리스트 1] 배치 디스크립터 (web.xml 파일)

"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd">

Faces Servlet

http://crowe.wowdns.com:8000/archives/dev_note/ (123 of 158)2005-07-05 오전 11:32:44 Binary Coder's Weblog: Dev. Note Archives

javax.faces.webapp.FacesServlet

1

Faces Servlet

/faces/*

배치 디스크립터에는 두 부분으로 이루어져 있다. 요소는 Faces 서블릿을 등록하고 요소는 URL에 포함 된 /faces/ 패턴을 가지는 모든 요청을 Faces 서블릿으로 보낸다.

JSP 페이지 만들기

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

[리스트 2] adder.jsp 페이지

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

Add 2 numbers


First Number:


Second Number:


Result:


http://crowe.wowdns.com:8000/archives/dev_note/ (124 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

html과 core, 두개의 JSF 태그 라이브러리를 사용하기 위해 두개의 taglib directive를 선언해야 한다. 두개 라이브러리에 대한 태그 라이 브러리 디스크립터는 jsf-ri.jar 파일에 포함되어 있으므로 걱정하지 않아도 된다. 태그 라이브러리에 대한 접두어는 각각 h와 f다.

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

액션 요소는 세션 범위를 가진 NumberBean 자바빈즈를 정의한다.

JSF 컨트롤에 대해 살펴보면 JSF 컨트롤은 반드시 안에 포함되어 있어야 한다.

...

사이에 폼이 들어 갈 수 있다.

...

폼 내부에 두 개의 숫자 입력 창(input_number), 하나의 숫자 출력 창(output_numbers), 명령 버튼(command_button)이 있다.

First Number:


Second Number:


Result:


명령버튼을 위한 액션 리스너에 주의를 기울이자. [그림 2]는 루트 요소가 생략된 JSP 페이지 컴포넌트 트리이다.

http://crowe.wowdns.com:8000/archives/dev_note/ (125 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

[그림 2] adder.jsp 페이지의 컴포넌트 트리 네 개의 자식 컴포넌트를 가지고 있는 폼이 메인 컴포넌트이다.

객체 모델 작성하기

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

[리스트 3] NumberBean 자바빈즈

01 package jsfApp;

02

03 public class NumberBean {

04 int firstNumber = 0;

05 int secondNumber = 0;

06

07 public NumberBean() {

08 System.out.println("Creating model object");

09 }

10

11 public void setFirstNumber(int number) {

12 firstNumber = number;

13 System.out.println("Set firstNumber " + number);

14 }

15

16 public int getFirstNumber() {

17 System.out.println("get firstNumber " + firstNumber);

18 return firstNumber;

19 }

20

21 public void setSecondNumber(int number) {

22 secondNumber = number;

23 System.out.println("Set secondNumber " + number);

24 }

http://crowe.wowdns.com:8000/archives/dev_note/ (126 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

25

26 public int getSecondNumber() {

27 System.out.println("get secondNumber " + secondNumber);

28 return secondNumber;

29 }

30

31 public int getResult() {

32 System.out.println("get result " + (firstNumber + secondNumber));

33 return firstNumber + secondNumber;

34 }

35 }

액션 리스너 작성하기

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

[리스트 4] 명령버튼을 위한 액션 리스너 (MyActionListener.java)

01 package jsfApp;

02

03 import java.util.Iterator;

04 import javax.faces.component.UIComponent;

05 import javax.faces.context.FacesContext;

06 import javax.faces.event.ActionEvent;

07 import javax.faces.event.ActionListener;

08 import javax.faces.event.PhaseId;

09 import javax.faces.tree.Tree;

10

11 public class MyActionListener implements ActionListener {

12

13 public PhaseId getPhaseId() {

14 System.out.println("getPhaseId called");

15 return PhaseId.APPLY_REQUEST_VALUES;

16 }

17

18 public void processAction(ActionEvent event) {

19 System.out.println("processAction called");

20

21 // the component that triggered the action event http://crowe.wowdns.com:8000/archives/dev_note/ (127 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

22 UIComponent component = event.getComponent();

23 System.out.println(

24 "The id of the component that fired the action event: "

25 + component.getComponentId());

26

27 // the action command

28 String actionCommand = event.getActionCommand();

29 System.out.println("Action command: " + actionCommand);

30

31 FacesContext facesContext = FacesContext.getCurrentInstance();

32 Tree tree = facesContext.getTree();

33 UIComponent root = tree.getRoot();

34

35 System.out.println("------Component Tree ------");

36 navigateComponentTree(root, 0);

37 System.out.println("------");

38 }

39

40 private void navigateComponentTree(UIComponent component, int level) {

41 Iterator children = component.getChildren();

42

43 // indent

44 for (int i = 0; i < level; i++)

45 System.out.print(" ");

46

47 // print component id

48 System.out.println(component.getComponentId());

49

50 // navigate children

51 while (children.hasNext()) {

52 UIComponent child = (UIComponent) children.next();

53 navigateComponentTree(child, level + 1);

54 }

55 }

56 }

애플리케이션 컴파일과 실행하기

http://crowe.wowdns.com:8000/archives/dev_note/ (128 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

애플리케이션을 컴파일하기 위해 myJSFApp/WEB-INF/classes 디렉토리로 이동한다. 윈도우에서는 아래와 같이 명령어를 타이핑해 넣 으면 된다.

$ javac -classpath ../lib/jsf-api.jar;../lib/jsf-ri.jar; \

../../../../common/lib/servlet.jar jsfApp/*.java

lib 디렉토리의 라이브러리 파일과 servlet.jar 파일을 사용해야 한다는 것에 주의하자. 톰캣에서는 홈 디렉토리의 하위 common/lib에서 servlet.jar 파일을 찾을 수 있다.

리눅스나 유닉스를 사용하고 있다면 라이브러리 파일을 구분하는 세미콜론을 변경해 줘야 한다.

$ javac -classpath ../lib/jsf-api.jar:../lib/jsf-ri.jar: \

../../../../common/lib/servlet.jar jsfApp/*.java

톰캣을 실행시키고 다음과 같은 URL로 브라우저에서 접근한다.

http://localhost:8080/myJSFApp/faces/adder.jsp

JSP 페이지 이름 전에 /faces/ 패턴을 사용했다는 점에 주의하자. 브라우저에서는 아래 [그림 3]과 같은 결과를 보게 될 것이다.

[그림 3] 애플리케이션 실행 콘솔 창에 다음과 같은 메시지를 보게 될 것이다.

Model Object Created

get firstNumber 0

get secondNumber 0

get result 0

http://crowe.wowdns.com:8000/archives/dev_note/ (129 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

getPhaseId called

이제 입력 창에 2개의 숫자를 넣고 Add 버튼을 눌러보자. [그림 4]와 같이 브라우저는 덧셈의 결과를 보여줄 것이다.

[그림 4] 덧셈의 결과 콘솔창에 나타난 메시지가 중요하다.

get firstNumber 0

get secondNumber 0

processAction called

The id of the component that fired the action event: submitButton

Action command: submit

------Component Tree ------

null

addForm

firstNumber

secondNumber

output

submitButton

------

Set firstNumber 10

Set secondNumber 20

get firstNumber 10

get secondNumber 20

get result 30

http://crowe.wowdns.com:8000/archives/dev_note/ (130 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

결론

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

실제 JSF 애플리케이션은 훨씬 복잡할 뿐만 아니라 종종 여러 개의 JSP 페이지를 가지고 있는 경우도 있다. 이런 경우에는 한 JSP 페이지 에서 다른 JSP 페이지로 이동할 수 있어야 한다. 하지만 이에 관련된 내용은 본 기사의 범위를 넘어서는 것이므로 이에 대한 것은 다른 기 사에서 논의할 생각이다.

부디 쿼니아완(Budi Kurniawan)은 인터넷과 객체지향 프로그래밍을 전문으로 하는 IT 컨설턴트로 마이크로소프트와 자 바 기술 모두를 가르치고 있다.

Posted by Crowe Lee at 06:30 PM | Comments (0) | TrackBack

December 13, 2003

XML-RPC 관련 자료

View: XML-RPC HOWTO

이기종 간의 Platform, Program Language 에 관계없이 RPC를 사용하도록 해 주는 XML-RPC 관련 자료이다. 현재 Version 1.1이고 Perl, Python, C, C++, Java, PHP 언어로 구현되어 있다.

위 Howto 문서에서 XML-RPC와 CORBA, DCOM, SOAP에 관한 비교 및 스펙, 각 언어별 예제를 볼 수 있다.

조만간 위 기술을 사용하여 Blog에 자동으로 Posting해주는 AP를 만들 생각이다.

한글 번역본을 찾았다. ^^;;;;

View: XML-RPC HOWTO 한글 번역본

Posted by Crowe Lee at 11:59 AM | Comments (1) | TrackBack

December 10, 2003

JRun 4에서의 Connection Timeout 오류

View: Java Technology Forums, Topic: Connection timeout

JRun 4의 Data Source를 사용하여 Database를 사용할 때 시간이 걸리는 작업의 경우 다음과 같은 Exception을 내면서 작업이 실패하 게 된다.

java.sql.SQLException: This Connection has either timed out or been closed, and it has returned to its pool. You must re-acquire the Connection.

http://crowe.wowdns.com:8000/archives/dev_note/ (131 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

위의 해결 방법은 다음과 같다.

Jrun 각 서버의 SERVER-INF Directory에는 Data Source 에 관계되는 jrun-resources.xml 파일이 있다.

DS-NAME oracle.jdbc.driver.OracleDriver jdbc:oracle:thin:@127.0.0.1:1521:ORA scott 0DD007CF423910AF53795DBAFA22C668 true jrun.security.JRunCrypterForTwofish true true true 1 1200 20 false 5 30 DS-NAME Pool 0 2147483647 20 420 5 true false false

위에서 의 의미는 "The time that an unique Statement can be executed", 즉 Statement가 실행될수 있는 제약 시 간을 의미한다. 20초 이상의 Query의 경우 Connection이 JRun 엔진에 의해 자동으로 닫히게 되는 것이다. 이 값을 600 (10분) 정도로 변경하여 긴 시간을 필요로 하는 작업을 오류없이 마칠 수 있을 것이다.

Posted by Crowe Lee at 06:17 PM | Comments (1) | TrackBack

November 04, 2003

RSS Utilities: A Tutorial

View: RSS Utilities: A Tutorial

Java Sun.com에서 배포한 RSS Utilities Package 예제 자료에 관한 링크다. JSP 상에서 Tag Library를 사용하여 RSS 파일을 생성할 수 있다고 하는데, 자세한 것은 직접 해봐야 알 것 같다. -_-;;

http://crowe.wowdns.com:8000/archives/dev_note/ (132 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

Posted by Crowe Lee at 02:50 PM | Comments (0) | TrackBack

RSS로 사이트 간의 컨텐츠 공유하기

View: Skypond의 정보 시스템 구축

RSS(RDF Site Summary)의 소개와 파싱방법, 구조, JSP를 이용한 생성하는 내용 등이 소개된다. 소스 파일도 제공...

Posted by Crowe Lee at 01:24 PM | Comments (0) | TrackBack

October 07, 2003

Oracle SQLJ programming for Oracle 8i

출처: Dev World - 개발자들을 위한 세상 (http://www.devworld.co.kr/)

여기에서는 Oracle 8i를 install하고 난후 JDBC programming을 하기 위해 필요한 환경설정과, samples이 위치한 directory 그리고, Oracle에서 지원하는 JDBC Type과 사용방법과 실제 samples을 실행해서 수행되는 것을 살펴보도록 한다.

Sqlj란 무엇인가?

개발자들은 기존의 C언어를 이용해서 Oracle Database에 접근 하고자 할 때 두 가지 방식을 이용했다. 하나는 “Pro*C” 방식이고, 다른 하나는 “OCI Library”를 바로 call하는 방식이었다. 이 두 가지의 차이점은 “Pro*C”의 경우 Embedded Sql문을 이용하기 때문에 기존의 C문장에 다음과 같이 삽입함으로써 Oracle Database에서 데이터를 가공할 수 있었다.

EXEC SQL DELETE FROM dept WHERE deptno = :v_deptno

그러나 만약, OCI Library를 직접 call해서 위와 같은 내용을 수행하려면 low level의 coding이 필요한 데, 이와 비교해 볼 때 Java의 경 우에는 전자와 같은 방식이 “sqlj”방식이고 후자와 같은 low level의 programming이 “JDBC API”를 이용하는 방식이라 할 수 있겠다.

Sqlj와 JDBC API를 이용 시 각 방식의 장단점이 있겠지만, 다음 표로 간단히 확인해 보자.

SQLJ JDBC API

코딩의 양 간결하다. 많은 line수 필요

sql문 syntax 검사 Pre-compile time시에 결정 runtime시에 결정

Host 변수 사용 가능 사용 불가능

Dynamic Sql이용 불가능 사용 가능

위에서 언급된 부분을 살펴보면 SQLJ가 JDBC API를 바로 이용하는 것보다 코딩 line수가 적으며, 이것은 몇 개의 column의 값을 update할 때 더 확실히 알 수 있다. SQLJ는 sql문을 pre-compile time시에 syntax나 objects의 존재 여부 type 검사 등을 위해서 미리 database에 접속을 한 후 검증을 한다. 그러므로, compile-time시에 에러를 발생시키고, runtime시에 에러를 줄임으로써, 개발자들로 하여금 빠르고 쉬운 debugging이 가능토록 한다.

http://crowe.wowdns.com:8000/archives/dev_note/ (133 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

또한, SQLJ는 “Host variables”을 이용할 수 있으므로, 문장이 상당히 간결해 질 수 있으나 JDBC API를 이용할 경우에는 그렇지 못하다. 그러나, SQLJ의 경우 compile시에 sql문이 완전히 조합되어 있어야 하므로, “dynamic sql”등을 사용할 수가 없다. 만약 dynamic sql을 이용하고자 한다면, “JDBC API”를 이용해서 Dynamic SQL을 구현해야 한다.

정리를 하면, “Static SQL”에 해당하는 부분은 SQLJ를 이용하는 것이 좋고, Dynamic SQL에 해당하는 부분은 “JDBC API”를 이용함으로 써 개발 생산성과, rumtime시의 performance를 극대화할 수 있겠다.

SQLJ를 위한 환경 설정

SQLJ를 위해서는 먼저 JDK가 환경 변수에 적절하게 잡혀있어야 하고, 또한 JDBC Driver도 CLASSPATH에 잡혀있어야 한다. 그 이유는 SQLJ도 결국에는 pre-complie되었을 때 JDBC API를 call하는 “*.java” source로 바뀌기 때문이다.

*.sqlj ── SQLJ Pre-compiler ──> *.java which calls JDBC ── Java ──> *.class

위에서 보는 것처럼 먼저 확장자가 “sqlj”인 SQLJ파일을 SQLJ pre-compiler를 이용해서 pre-compile을 한다. 그러면 확장자가 “*. java”인 파일이 생성되고, 이 파일은 내부적으로 JDBC API를 call하는 logic을 담고 있기 때문에, JDBC Driver가 CLASSPATH에 있어야 만 SQLJ 파일이 제대로 동작될 것이다. 그리고, 이것을 일반 Java Compiler를 이용해서 compile하면 “JDBC API”를 이용해서 작성한 것 과 같은 형태의 “*.class”가 생성된다. 물론, 여기서 Type2의 JDBC Driver를 이용할지 Type4의 JDBC Driver를 이용할지도 SQLJ 파일 을 생성할 때 결정할 수 있다.

위에서 보는 것처럼 먼저 JDK와 JDBC Driver를 위한 환경설정이 되어 있어야 하고, 추가로 SQLJ library인 “translator.zip”을 CLASSPATH에 추가해 주어야 한다.

$ORACLE_HOME/sqlj/lib 디렉토리에 보면 library가 두개 있는 것을 볼 수 있는 데, 하나는 “translator.zip”이고 다른 하나는 “runtime.zip” 이다. 두개의 차이점은 “translator.zip”의 경우 sqlj를 compile하고 개발할 때 필요한 library이고, runtime.zip은 SQLJ 에 의해서 생성된 CLASS가 실행될 때 필요한 library이다. 그리고, 두 파일의 크기도 상당히 차이가 나기 때문에 개발할 때와 deploy할 때 를 구별해서 필요한 library를 이용해야 할 것이다. http://crowe.wowdns.com:8000/archives/dev_note/ (134 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

물론, translator.zip만을 가지고도 SQLJ 파일에 의해서 생성된 CLASS가 실행될 수 있도록 환경을 제공한다. 그래서, 위에서는 추가로 다 음과 같이 환경을 설정해 주었다.

export CLASSPATH=$ORACLE_HOME/sqlj/lib/translator.zip:$CLASSPATH

추가로 “sqlj” command는 $ORACLE_HOME/bin에 위치하므로, 당연히 $ORACLE_HOME/bin 은 PATH 환경변수에 설정이 되어 있어 야 할 것이다.

SQLJ의 예제

Oracle 8i를 최초 install하고 나면 다음과 같은 directory에 SQLJ를 위한 예제들이 위치한다.

$ORACLE_HOME/sqlj/demo

여기에 위치한 파일들 중 먼저 README.txt를 읽어 보면 SQLJ가 제대로 실행되는 지를 확인하기 위한 절차들이 나와 있으며, “make”를 이용해서 실행하는 방법과 수작업으로 하나하나 command를 이용해서 실행해 보는 방법, 두 가지가 설명되어 있다. 여기서는 직접 java compiler와 sqlj utility를 이용해서 단계를 천천히 살펴 보도록 하겠다.

1. connect.properties파일의 내용 설정 이 부분은 sqlj에서 쉽게 database에 대한 정보 및 jdbc url등을 파일로 설정하고 이것을 이용하기 위해 Oracle에 서 “oracle.sqlj.runtime.Oracle”이라는 class를 제공하고 있다. 이 class를 이용하면 JDBC Driver를 memory 에 올리는 것뿐만 아니라 Connection과 관련해서 JDBC URL을 일일이 입력할 필요 없이 다음과 같은 문장으로써 위의 스텝들을 모두 skip할 수 있다.

Oracle.connect(TestInstallJDBC.class , “connect.properties”);

“connect.properties” 파일을 open하고 다음과 같이 수정한다. “sqlj.url = jdbc:oracle:oci8:@TNS_ALIAS” //tnsnames.ora에서 설정해준 이름을 지정한다. “sqlj.user = scott” “sqlj.password = tiger” 2. TestInstallCreateTable.java를 compile하고 실행 SQLJ를 테스트하기 전에 먼저 JDBC coding을 한 java source를 이용해서 “SCOTT” schema에 테스트하기에 필 요한 “SALES”라는 테이블을 생성한다. 이것을 생성하기 위해서 “TestInstallCreateTable.java” source를 compile하고 실행하는 결과를 밑에서 볼 수 있다.

http://crowe.wowdns.com:8000/archives/dev_note/ (135 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

3. TestInstallJDBC.java를 compile하고 실행 TestInsstallJDBC.java의 내용은 “SCOTT” schema에 생성한 “SALES” table의 데이터를 먼저 지우고, “Hello JDBC!”라는 String을 입력한 후에 이것을 다시 select해서 입력한 String이 제대로 보이는 지를 확인하는 java source이다. 이것도 sqlj를 이용한 것은 아니고, 단지 JDBC API를 이용해서 delete하고 insert하고 update한 source이다.

위에서 보면 “TestInstallJDBC.java”를 compile하고 class가 생성되었음을 보여주며, 이것을 java interpreter 를 이용해서 실행하고 그 결과인 “Hello JDBC!”를 출력하고 있다.

http://crowe.wowdns.com:8000/archives/dev_note/ (136 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

4. TestInstallSQLJ.sqlj를 compile하고 실행 이번에는 SQLJ 파일을 “sqlj” utility를 이용해서 precompile하고 javac를 이용해서 compile한 후 java interpreter를 이용해서 실행을 시키는 내용을 아래 그림에서 보여주고 있다. 물론, “sqlj” utility는 default로 pre-compile을 해서 ”.java” source를 생성한 후 다시 “javac” command를 이 용해서 “.class”까지 생성해 준다. 그리고, 실행은 일반 java interpreter를 이용해서 실행을 하고 결과는 “Hello SQLJ!”를 출력한다.

위의 그림에서 “sqlj” utility의 options으로 “ser2class”가 있는 데, 이것은 sqlj문장에 해당하는 내용을 원래는 “*.ser”파일에 저정하는 데, “*.ser”파일 대신에 “.class”애 저장할 것을 지정하는 option이다.

이번에는 TestInstallSQLJ.sql의 source를 살펴보도록 하자.

01 import oracle.sqlj.runtime.Oracle; 02 03 import java.sql.SQLException; 04 05 06 // sqlj library에서 제공하는 class로 JDBC Driver를 memory에 올리고, connection을 맺게 한다. 07 // iterator for the select 08 #sql iterator MyIter (String ITEM_NAME); 09 class TestInstallSQLJ { 10 //Main method 11 public static void main(String[] args) { 12 try { 13 /* if you're using a non-Oracle JDBC Driver, add a call here to 14 DriverManager.registerDriver() to register your Driver 15 */ 16 17 // set the default connection to the URL, user, and password

http://crowe.wowdns.com:8000/archives/dev_note/ (137 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

18 // specified in your connect.properties file 19 Oracle.connect(TestInstallSQLJ.class, "connect.properties"); 20 21 TestInstallSQLJ ti = new TestInstallSQLJ(); 22 ti.runExample(); 23 } catch (SQLException e) { 24 System.err.println("Error running the example: " + e); 25 } 26 } 27 //End of method main 28 29 //Method that runs the example 30 void runExample() throws SQLException { 31 String val = "Hello SQLJ!"; 32 33 //Issue SQL command to clear the SALES table 34 #sql { DELETE FROM SALES }; 35 #sql { INSERT INTO SALES(ITEM_NAME) VALUES (:val)}; 36 MyIter iter; 37 38 #sql iter = { SELECT ITEM_NAME FROM SALES }; 39 while (iter.next()) { 40 System.out.println(iter.ITEM_NAME()); 41 } 42 } 43 }

Line 01에서는 sqlj library에서 제공하는 Oracle class를 이용하기 위해서 import를 하고 있다.

Line 08에서는 JDBC API의 ResultSet과 같은 기능으로 SQLJ에서는 “iterator”라는 것을 제공하는 데, interator type의 MyIter class 를 생성하고, member methods로 “ITEM_NAME”이라는 methods를 지정하는 데, 이것은 결국 table의 column이름이고, 이것을 이용 해서 데이터를 fetch하게 된다.

Line 19에서는 Oracle class의 connect methods arguments로 (classNmae, “connect.properties”)이 할당되고, 이 문장에서 JDBC Driver를 메모리에 올리고, Connection을 맺는 일을 하게 된다.

Line 34에서는 “SQL”문장을 “#sql”이라는 keyword를 이용해서 수행하고 있는 데, 기존의 JDBC API를 이용하는 것에 비해서 아주 단순 하게 수행된다.

Line 35에서는 “INSERT”를 하는 SQL문장을 수행하고 있는 데 단 1줄로 수행이 가능함을 볼 수 있다. 이것을 아래 표에서 JDBC API와 비교를 해보면 어느 정도 coding line에 차이가 있는 지를 알 수 있다.

JDBC SQLJ PreparedStatement pstmt #sql { INSERT INTO SALES(ITEM_NAME) VALUES (:val)}; = conn.prepareCall( “insert into sales(item_name) values(?)”); pstmt.setString(1, val); pstmt.execute( );

http://crowe.wowdns.com:8000/archives/dev_note/ (138 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

SQLJ에서는 insert를 하기 위해서 단 한 줄의 문장이 필요하며, 또한 기존의 SQL문을 사용하던 개발자들에게 아주 친숙한 SQL문장 그 자 체를 이용하며 단지 앞부분에 “#sql” 이라는 keyword를 이용하기만 하면 된다. 그러나 JDBC API를 이용할 경우에는 여러 line이 필요하 며, 만약 여러 column이 insert된다면 그에 해당하는 line수가 추가될 것이다.

Line 36은 iterator type인 MyIter class를 이용해서 해서 “iter”라는 instance를 선언하고 있으며, Line 38에서는 데이터를 select해서 iter instance에 값을 assign하고, Line 39에서는 이것을 ResultSet과 마찬가지 방법으로 값을 얻어서 출력하고 있다.

위의 예제에서 보는 바와 같이 SQLJ를 이용하기 위해서는 가장 중요한 것이 “#sql” 이라는 keyword이며, 모든 sql문은 이 keyword를 이 용해서 쉽게 처리될 수 있다. 물론, 여기에서 다루지 못한 많은 부분이 있는 데, 이것은 Oracle 8i manual중에 “SQLJ Developer’s Guide and Reference”를 참조하면 더 많은 부분을 이해할 수 있을 것이라 생각된다.

Posted by Crowe Lee at 11:04 PM | Comments (0) | TrackBack

October 01, 2003

Servlet Filter 를 이용한 한글 인코딩 적용

작성자: 심 우곤 ([email protected]) 본 문서는 JLab (http://www.jlab.net) 홈페이지에서도 보실 수 있습니다.

Tomcat의 악명 높은 한글 문제입니다. HTML의

을 통해 한글 정보를 전달하면 이 값이 깨져버리는데, 이 문제는 Tomcat이 넘 어오는 데이터를 내부적으로 변환해 버리기 때문이지요. 물론, 한글을 변환하는 빈을 만들어서 처리하는 방법도 있습니다만, 매 페이지마 다 빈을 불러서 처리해야 하는 것이 귀찮지요.

여기서 알려드리려 하는 방법은 Tomcat으로 하여금 을 통해 전달하는 값을 URL 인코딩을 하지 않고 EUC_KR로 변환하여 넘기 도록 합니다. 이는 한글문제에 대해 고민을 하지 않아도 된다는 것이지요.

물론, 매 페이지는 한글을 잘 표현할 수 있도록 다음과 같은 지시문을 둘 거라 생각합니다.

<%@ page language="java" contentType="text/html; encoding=euc_kr"%>

이제 본격적으로 Tomcat의 한글문제를 다루도록 합시다. 사실 여기서 다루려는 해결방안은 Tomcat을 설치하면서 딸려 들어오는 예제코 드(4.0.5나 4.1.12 모두에서 찾아볼 수 있습니다)에서 내용을 찾을 수 있었습니다. -_-;; 놀라셨다구요? 저도 놀랐습니다. 정말 등잔 밑 이 어둡다고 하더니만, 이렇게 가까운 곳에 해결책이 있을 줄이야!

%TOMCAT_HOME%\webapps\examples\WEB-INF에 존재하는 web.xml을 열어보면, 다음과 같은 부분을 찾을 수 있습니다. 이 방법 은 Filter를 사용하는 방법으로 사용자의 입력을 서블릿이 처리하기 전에 해당 정보를 Filter가 적절한 방식으로 변환할 수 있도록 합니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (139 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

참 이런걸 보면, 2 Bytes 문자체계를 지원하는 솔루션은 일본인들을 위해서 먼저 만들어진다는 것이 안타깝습니다. 하지만 이로 인해서 우 리나라도 유사한 방법으로 한글문제를 쉽게 해결할 수 있지만요.. 잠깐 푸념이었습니다.

물론 위에 보이는 의 encoding 파라미터의 값을 EUC_JP에서 EUC_KR로 변경해야겠죠? 여기서 한글변환을 위해 사용된 SetCharacterEncodingFilter 클래스는 %TOMCAT_HOME%\webapps\examples\WEB-INF\classes\filters에서 찾을 수 있습니다.

그럼 정리하겠습니다.

Filter를 사용하여 Tomcat에서의 한글문제 해결방안 (JRun 등의 다른 Servlet/JSP 엔진도 해당됩니다.)

1. 구축하고자 하는 여러분의 웹 어플리케이션의 WEB-INF\classes 폴더에 filters라는 새로운 폴더를 생성합니다. 2. 이제 WEB-INF\classes\filters에 SetCharacterEncodingFilter.class 파일을 %TOMCAT_HOME%\webapps \examples\WEB-INF\classes\filters 폴더에서 복사합니다. 3. 그 후 여러분의 웹 어플리케이션의 WEB-INF\web.xml의 상단에 다음 라인을 추가합니다.

Set Character Encoding filters.SetCharacterEncodingFilter encoding EUC_KR

http://crowe.wowdns.com:8000/archives/dev_note/ (140 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

Set Character Encoding /*

※ 참고 인지를 확인합니다. 간혹 2.2버전의 파일이 있는데, 이 때는 아래의 인지하지 못합니다.

매번 SetCharacterEncodingFilter.class 파일을 WEB-INF\classes\filters에 복사하는 것이 귀찮으시다구요?

Tomcat은 여러 웹 어플리케이션들간에 클래스를 공유할 수 있는 공간을 마련해 두었습니다. Tomcat 4.0.5에서는 %TOMCAT_HOME% \classes폴더가 Tomcat 4.1.12에서는 %TOMCAT_HOME%\common\classes 폴더가 있는데, 이들 폴더가 바로 그 공간입니다. 자 그 럼 이곳에 공용 클래스인 SetCharacterEncodingFilter.class를 공유공간에 복사해 봅시다. 절차는 다음과 같습니다.

Tomcat 4.0.5를 사용하시는 분은 %TOMCAT_HOME%\classes에, Tomcat 4.1.12를 사용하시는 분은 %TOMCAT_HOME% \common\classes에 filters폴더를 만들고SetCharacterEncodingFilter.class 파일을 복사합니다. 혹은, 매번 폴더 만들고 복사하기가 귀 찮으시다면 jar로 압축된 파일 TomcatChracterEncodingFilter.jar을 받으시기 바랍니다. 그냥 제가 묶어 놓은 것입니다. 여러분도 예제 로부터 쉽게 만들어 사용하시면 됩니다.

Tomcat 4.1.12를 사용하시는 분은 %TOMCAT_HOME%\lib에, Tomcat 4.1.12를 사용하시는 분은 %TOMCAT_HOME%\common \lib에 받으신 jar파일을 복사합니다.

이로써 우리는 앞으로 만들게 될 웹 어플리케이션에서 SetCharacterEncodingFilter 클래스를 공유하게 되었습니다. 그 결과, 여러분들은 위에 언급한 세 단계의 절차 중 첫 번째와 두 번째 항목을 매 어플리케이션마다 수행하실 필요가 없어진 것이지요. 단지 web.xml 파일에 내용을 추가하시기만 하면 됩니다.

감격의 순간이지 않습니까? 저는 이런 방법이 있다는 사실을 다수의 국내 개발자들이 모르고 있다는 것이 안타깝습니다. 널리 이 방법을 전 파시켜 주시길 바랍니다. 한글 변환 빈을 만들어서 매번 불편하게 사용해 왔다면, 정말 신선한 방법이겠죠? ^_^

마지막으로 SetCharacterEncodingFilter의 소스 파일입니다. 인코딩을 하는부분은 31번 Line의 request.setCharacterEncoding (String encoding); 부분입니다.

01 package filters; 02 03 import java.io.IOException; 04 05 import javax.servlet.Filter; 06 import javax.servlet.FilterChain; 07 import javax.servlet.FilterConfig; 08 import javax.servlet.ServletException; 09 import javax.servlet.ServletRequest; 10 import javax.servlet.ServletResponse; 11 import javax.servlet.UnavailableException; 12 13 14 public class SetCharacterEncodingFilter implements Filter {

http://crowe.wowdns.com:8000/archives/dev_note/ (141 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

15 protected String encoding = null; 16 protected FilterConfig filterConfig = null; 17 protected boolean ignore = true; 18 19 public void destroy() { 20 this.encoding = null; 21 this.filterConfig = null; 22 } 23 24 public void doFilter(ServletRequest request, ServletResponse response, 25 FilterChain chain) throws IOException, ServletException { 26 // Conditionally select and set the character encoding to be used 27 if (ignore || (request.getCharacterEncoding() == null)) { 28 String encoding = selectEncoding(request); 29 30 if (encoding != null) { 31 request.setCharacterEncoding(encoding); 32 } 33 } 34 35 // Pass control on to the next filter 36 chain.doFilter(request, response); 37 } 38 39 public void init(FilterConfig filterConfig) throws ServletException { 40 this.filterConfig = filterConfig; 41 this.encoding = filterConfig.getInitParameter("encoding"); 42 43 String value = filterConfig.getInitParameter("ignore"); 44 45 if (value == null) 46 this.ignore = true; 47 else if (value.equalsIgnoreCase("true")) 48 this.ignore = true; 49 else if (value.equalsIgnoreCase("yes")) 50 this.ignore = true; 51 else 52 this.ignore = false; 53 } 54 55 public FilterConfig getFilterConfig() { 56 return filterConfig; 57 } 58 59 public void setFilterConfig(FilterConfig filterConfig) { 60 this.filterConfig = filterConfig; 61 } 62 63 protected String selectEncoding(ServletRequest request) { 64 return (this.encoding); 65 } 66 }

http://crowe.wowdns.com:8000/archives/dev_note/ (142 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

Posted by Crowe Lee at 10:57 PM | Comments (0) | TrackBack

September 30, 2003

Improving Code Reuse with Servlet Filters

Servlet Filters 에 관한 SUN Tranning Center 웹진 기사이다. 다음에는 이 필터를 사용하여 서블릿 엔진의 종류에 상관 없이 유니코드를 Encoding 하는 예제를 살펴볼 것이다.

2002 년 07 월 01 일 By Stuart Halloway SES Webzine - Tech Tips | Improving Code Reuse with Servlet Filters

서블릿 환경에서 코드를 재사용하기 위한 방법은 여러가지가 있다. 한 예로 상속을 사용해서 YourCoolServlet 클래스에 있는 코드를 새롭 고 더 향상된 YourCoolerServlet에서 가져다 재사용할 수 있다. 또한, Java Servlet 사양은 한 서블릿으로부터 다른 서블릿으로 실행을 연쇄적으로 일어나게 (chain execution) 하는 자연적인 메커니즘을 정의하고 있다. 여러분은 java.servlet.RequestDispatcher의 forward나 include 메서드를 사용해 한 서블릿의 컨트롤을 다른 서블릿으로 전달할 수 있다.

이 기법들은 각기 장단점을 지닌다. 이들을 사용하려면 코드 내에서 서블릿을 명시적으로 wire해야 하는데, 이는 여러분이 개발 시에 재사 용에 대한 계획을 세워야 한다는 것을 의미한다. forward와 include 메서드들을 사용하면 downstream 서블릿에 너무나 많은 권한을 주 게 된다 - 즉 downstream 서블릿이 리스펀스에 쓰는 내용을 여러분이 수정할 수 없게 될 수도 있다.

Java Servlet 2.3 API를 사용하면 서블릿 필터라는 옵션이 제공된다. 서블릿 필터는 Java Servlet 2.3 API에 새로 추가된 기능이다. 이 를 사용하면 여러분이 리퀘스트와 리스턴스의 내용을 변경할 수 있다. 서블릿 엔진이 제공하는 각 자원은 임의의 필터 사슬 (an arbitrary chain of filters) 뒤에 은닉된다. 리퀘스트가 전달되면 각 필터가 이를 살펴보고 underlying 자원의 행동을 수정하거나 수정하지 않을 수 있다. 가장 좋은 점은 관리자가 기존의 코드를 변경하지 않고도 필터 사슬을 만들 수가 있다는 것이다. 관리자가 어떤 자원 앞에 두 개의 필 터를 설치한 경우를 생각해 보자. 각 필터는 리퀘스트를 분석하고 '필터 체인' 상의 다음 객체에 이 리퀘스트를 전달하기 전에 이에 수정을 가할 것인지 아닌지를 결정할 수 있다. 또한 각 필터는 체인 상에서 다음 객체를 호출하기 전이나 후에 관계없이 클라이언트에게 전달될 리 스펀스를 수정할 수도 있다. 서블릿, JSP, static content 또는 여러분이 사용하는 엔진이 제공하는 end point라면 어떤 것이라도 체인의 끝에 위치한 자원이 될 수 있다.

서블릿 필터의 장점을 살펴보기 위해 다른 상황을 고려해 보자. 여러분이 RockSolidBids라는 온라인 경매(입찰) 시스템에서 근무한다고 가정하자. 어느날 아침 출근해보니 여러분의 제휴 기업인 FlakyNetworks Inc 에 네트워크 문제가 발생했다는 사실을 알게 되었다. 불행히 도 여러분의 애플리케이션에는 FlakyNetworks에 접속하는 코드가 다량 포함되어 있다. 여러분에게는 서블릿에 발생한 문제를 일일이 하 나씩 고치고 있을 시간이 없다. 상황이 점점 더 심각해져만 간다 - FlakyNetworks로의 접속 실패와 재시도가 반복되면서 대역폭에 정체 가 발생하고 여러분의 고객들에게 엄청난 불편을 느끼고 있다. 여러분의 임무는 FlakyNetworks Inc와 관련된 모든 리퀘스트를 일시적으 로 거부하기 위해 모든 서블릿 앞에 필터를 추가하는 일이다.

서블릿 필터를 사용하면 기존의 코드를 건드리지 않고도 이 문제를 손쉽게 해결할 수 있다. 우선 Filter 인터페이스를 구현하는 RequestBlocker 클래스를 정의한다 :

01 package com.develop.filters;

02

03 import java.io.*;

04

05 import java.util.*;

http://crowe.wowdns.com:8000/archives/dev_note/ (143 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

06

07 import javax.servlet.*;

08 import javax.servlet.http.*;

09

10

11 public class RequestBlocker implements Filter {

12 private FilterConfig filterConfig;

13 private Properties blockThese = new Properties();

14 private ServletContext ctx;

15

16 {

17 blockThese.setProperty("company", "FlakyNetworks");

18 }

19

20 public void init(javax.servlet.FilterConfig filterConfig)

21 throws ServletException {

22 this.filterConfig = filterConfig;

23 ctx = filterConfig.getServletContext();

24 ctx.log("Filter " + filterConfig.getFilterName() + " initialized.");

25 }

26

27 public void doFilter(ServletRequest servletRequest,

28 ServletResponse servletResponse, FilterChain filterChain)

29 throws java.io.IOException, ServletException {

30 HttpServletRequest hsr = (HttpServletRequest) servletRequest;

31

32 for (Enumeration en = blockThese.keys(); en.hasMoreElements();) {

33 String name = (String) en.nextElement();

34 String param = hsr.getParameter(name);

35

36 if ((param != null) && blockThese.get(name).equals(param)) {

37 HttpServletResponse hResp = (HttpServletResponse) servletResponse;

38 String msg = "Request for " + name + " " + param +

39 " rejected.";

40 hResp.sendError(hResp.SC_SERVICE_UNAVAILABLE, msg);

41 ctx.log(msg);

42

43 return;

http://crowe.wowdns.com:8000/archives/dev_note/ (144 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

44 }

45 }

46

47 filterChain.doFilter(servletRequest, servletResponse);

48 }

49

50 public void setFilterConfig(FilterConfig filterConfig) {

51 this.filterConfig = filterConfig;

52 }

53

54 public FilterConfig getFilterConfig() {

55 return filterConfig;

56 }

57

58 public void destroy() {

59 }

60 }

Filter 인터페이스는 세 개의 메서드를 정의한다.

● init 이 메서드는 서블릿 엔진이 필터의 사용을 시작하기 전에 호출된다. 이 예에서 init 메서드는 ServletContext를 후 에 사용할 목적으로 저장하고 성공적으로 완료되면 이를 기록한다.

● destroy 이 메서드는 서블릿 엔진이 어떤 서비스로부터 필터를 제거하기 전에 호출된다. 특정 필터에만 사용되는 자원을 청 소 (clean-up) 해야 한다면 destroy 메서드를 사용하면 된다.

● doFilter 이 메서드는 필터의 핵심 메서드이다. 일반적인 서블릿 doGet 혹은 doPost 내에서 리퀘스트 및 리스펀스 객체들 에 액세스를 할 수 있듯이 이 메서드 내부에서도 이렇게 할 수 있다. 여러분은 필요시에 이 객체들에 쿼리를 보내거 나 수정할 수 있다. 그리고 나서 filterChain.doFilter를 호출해 체인 상의 다음 필터로 (혹은 이 필터가 마지막 필터 라면 해당 서블릿으로) 이 리퀘스트를 전달(forward)할 수 있다.

doFilter의 RequestBlocker 구현은 매우 간단하다. doFilter는 blockThese 인스턴스가 정의한 key/value 쌍 (즉 "company", "FlakyNetworks") 을 루프로 반복한다. 어떤 리퀘스트 패러미터가 key/value 쌍과 일치하면, 필터는 리스 펀스 객체를 사용해 클라이언트에게 HTTP503 에러 (SC_SERVICE_UNAVAILABLE) 를 전송한다. 이 시점에서 필터가 리 퀘스트를 완전히 처리했기 때문에 filterChain.doFilter를 호출하지 않는다. 리퀘스트가 허가되지 않은 패러미터를 하나도 가지고 있지 않다면 RequestBlocker도 아무 일을 하지 않고 이 리퀘스트를 다음번에 위치한 필터/서블릿에 전달한다.

RequestBlocker는 서블릿 엔진 내에 배포해야만 사용할 수 있다. 다음에 오는 instructions 는 여러분이 Tomcat의 examples 애플리케 이션 내에 배포하고 있다고 가정한다.(??) 다른 엔진들의 instruction도 유사할 것이다.

1. 클래스 경로에 servlet.jar를 지정해서 RequestBlocker를 컴파일하라. Tomcat은 common/lib 에 servlet.jar를 유지한다. RequestBlocker.class 파일을 /{yourTomcat}/webapps/examples/WEB-INF/classes/com/ http://crowe.wowdns.com:8000/archives/dev_note/ (145 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

develop/filters 에 카피하라. 2. /{yourTomcat}/webapps/examples/WEB-INF/web.xml 디플로이먼트 디스크립터를 연다. 3. 아래의 XML 코드를 필터 element 리스트의 밑부분에 추가해서 필터를 정의하라 :

Request Blocker

com.develop.filters.RequestBlocker

이 코드는 "Request Blocker"라는 이름을 여러분의 필터에 연결시킨다. 4. 아래의 XML 코드를 필터-mappings의 밑부분에 추가해서 언제 필터를 사용해야 하는 지 엔진에 알려줘야 한다 :

Request Blocker

/*

이 코드는 애플리케이션에 매핑하는 모든 URL에 필터를 호출하라고 엔진에게 지시한다. Tomcat을 시작하려면, Tomcat 루트 디렉토리로부터 bin\startup을 실행한다. Tomcat이 시작되면 선택한 브라우저를 사용해 examples 애플리케이션을 브라우징할 수 있다.. 이제 애플리케이션이 정상적으로 작동할 것이다. 다양한 example 서블릿을 사용해보자. 이 액세스 각각은 필터링되지만 어떤 리퀘스트도 FlakyNetworks를 언급하지 않기 때문에 필터는 완전히 투명하다. 만약 URL이 FlakyNetworks라는 문자를 가지도록 수정하면 어떤일이 일어날까? 아래의 URL을 시도해 보자 :

http://localhost:8080/examples/servlet/

HelloWorldExample?company=FlakyNetworks

필터가 실행되면서 503 "Request for company FlakyNetworks rejected"라는 브라우저 메시지를 출력할 것이다. 이 간단한 필터는 코드 재사용에 대한 아주 좋은 예를 제공한다. 여러분은 시스템 내의 모든 서블릿을 재사용하고 있으며, 이들의 행동을 변화시켰다 : 이들은 이제 FlakyNetworks로의 호출을 거부한다. 그러나 이런 종류의 재사용은 상속이나 특정 서블릿으로의 위임을 필요 로하지 않는다. 사실상 여러분은 체인의 끝에 서블릿이 있다고 가정할 필요도 없다. 필터는 단순한 HTML 페이지를 포함, 모든 자원에서 잘 실행된다.

Posted by Crowe Lee at 02:36 AM | Comments (0) | TrackBack

September 18, 2003

트랙백 초보자 가이드

View: HOCHAN.NET, [번역] 트랙백 초보자 가이드

Blog 프로그램들에서 공개된 Trackback 기술에 관해 Movable Type를 예로 들어 사용자 측면에서 설명해 놓았습니다.

간단히 말해서, 트랙백은 웹사이트 간에 서로 뭔가를 알려줄 수 있는 수단을 제공하기 위해 만들어진 것입니다. A라 는 사람이 B라는 사람에게, "B님께서 관심을 가지실 만한 거예요."라고 말해주는 방법입니다. 그렇게 하기 위해서 는, A라는 사람이 B라는 사람에게 "트랙백 핑" (TrackBack Ping)을 보냅니다......

Posted by Crowe Lee at 08:14 PM | Comments (1) | TrackBack

http://crowe.wowdns.com:8000/archives/dev_note/ (146 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

Java | Collection Framework

View: Collection Framework

Collection 객체에 대한 기본적인 이해와 Vector - ArrayList, HashMap - Hashtable의 차이점을 다룬 문서다. 아직 정리하다 만 부분이 있지만 이것만으로 비슷 비슷한 개념의 Collection 객체들에 관한 차이점에 대해 확실히 알 수 있었다. 본작자와의 상의없이 정보 공유의 차원에서 옮긴 문서이기 때문에 언제 사라질지 모른다. 쿨럭...

만든이: 송지훈 소속: JavaCafe 부시샵 Email: [email protected]

이번 강좌를 통해 자바의 자료구조인 Collection Framework 에 대해 자세하게 알아보도록 하겠다. 가장 기본적이고 중요한 부분임에도 불구하고 프로그래밍을 공부하는 많은 사람들이 소홀히 생각하는 부분이다. 이 기회에 필자의 강좌를 통해 다시 한번 자바의 컬렉션 프레 임워크에 대해 깊이 있는 이해를 할 수 있었으면 한다. 이 강좌에선 1.4에 추가된 새로운 자료구조, 정확하게 2개의 Map 계열 클래스와 1 개의 Set 계열 클래스, 총 3가지를 포함해서 그 이전에 존재하는 자료구조 클래스들 모두를 설명한다.

1. 자바의 자료구조 Collection Framework 의 구조 2. Collection Framework 인터페이스들과 클래스들 1. Collection 인터페이스 2. Set 인터페이스와 구현 클래스들 1. HashSet 2. LinkedHashSet 3. TreeSet 3. List 인터페이스와 구현 클래스들 1. ArrayList 2. LinkedList 3. Vector 4. Stack 4. Map 인터페이스와 구현 클래스들 1. HashMap 2. LinkedHashMap 3. IdentityHashMap 4. WeakHashMap 5. Hashtable 6. TreeMap 5. Enumeration 와 Iterator 인터페이스 1. Enumeration 2. Iterator 3. J2SDK1.5 에서 추가될 auto-boxing 과 generic 4. 자주 사용되는 컬렉션 객체들의 퍼포먼스 표 5. 효율적인 컬렉션 객체들의 사용 6. 아쉬움을 남긴채 강좌를 마무리하며

http://crowe.wowdns.com:8000/archives/dev_note/ (147 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

1. 자바의 자료구조 Collection Framework 의 구조

다음 그림은 java.util 패키지 안의 컬렉션 프레임워크의 인터페이스를 관계를 나타내는 UML Class Diagram 을 보여주고 있다.

아래 그림에서 실선으로 그려진 화살표는 상속(extend)을 의미한다. 또한 "::" 을 기준으로 왼쪽은 패키지, 오른쪽은 이름을 나타낸다. 네 모 안의 보라색 동그라미 안에 i 라고 써있는 것은 해당 객체가 인터페이스라는 것을 의미한다. 또 컬렉션 인터페이스를 구현한 클래스들을 설명할 때 나오겠지만 점선으로 그려진 화살표는 구현(implements)을 의미하고 클래스이기 때문에 녹색 동그라미안에 C 라는 글자가 쓰 여있는 것을 보게 될 것이다. (참고로 필자는 이클립스 플러그인으로 제공되는 OMANDO 라는 UML 툴을 사용했다.)

그림에서 볼 수 있듯이 자바의 컬렉션 프레임워크는 크게 두가지로 구분된다. 바로 Collection 과 Map 이다. Collection 은 다시 Set 과 List 로 구분된다. 또한 아래 Class Diagram 에서 볼 수 있듯이 Collection 인터페이스를 구현한 클래스들과 연계해서 편리하게 저장된 요 소(Element)들을 다룰 수 있는 2가지 인터페이스가 있다.

● Note :: 요소(Element) 는 객체로 생각해도 무방하다. 그 이유는 자바의 컬렉션 프레임워크의 구성 클래스들에 저 장하거나 꺼내오는 요소의 타입이 객체의 최상위 타입인 Object 이기 때문이다. 따라서 int 등의 primary type 데 이터는 랩퍼(Wrapper) 클래스로 감싸서 넣어야 한다. 예를 들어, int 의 경우엔 Integer 로 감싸서 클래스로 만들 어 넣는 것이다. 아래 컬렉션 계열 인터페이스들을 보면 추가-삭제 메소드들의 파라미터와 리턴값이 모두 Object 인 것을 확인할 수 있을 것이다.

● Issue :: J2SDK1.5 에선 primary type 도 랩퍼 클래스로 감싸지 않고 자동으로 컬렉션 클래스들에 넣어도 되는 auto-boxing 기능을 제공해 줄 예정이다.(내부에서 자동으로 적절한 타입으로 변환시켜 주게 됨) 참고로 C# 에선 이미 auto-boxing 기능이 제공되고 있다. auto-boxing 에 대한 자세한 내용은 이 글의 후반부에 다루도록 하겠 다.

http://crowe.wowdns.com:8000/archives/dev_note/ (148 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

항상 숲과 나무를 같이 볼 수 있는 시야를 갖추어야 무엇이든 제대로, 깊이 있게 이해할 수 있다. 따라서 다시 한번 위에 있는 자바의 컬렉 션 프레임워크의 전체 구조를 표현한 Class Diagram 을 살펴보도록 하자. 각 인터페이스들에 대한 자세한 설명은 아래 부분에서 하도록 하겠다.

Top

2. Collection Framework 인터페이스들과 클래스들

그럼 이제 자바의 자료구조인 Collection, Set, List, Map 등은 어떤 특징에 따라 구분한 것인지를 알아보겠다. 아래의 표에 각각의 특성 을 정리해놨다. 아래 표를 주의 깊게 보도록 하자.

Package Definition

java.util.Collection 순서 없는 단순한 요소들의 집합

java.util.Set 중복을 허용하지 않는 요소들의 집합

java.util.List 순차적 나열, 순서지정이 가능한 요소들의 집합

java.util.Map Key와 Key에 대응하는 값으로 이루어진 구조

java.util.SortedSet 값들이 정렬된 Set

java.util.SortedMap key 가 정렬된 Map

그럼 먼저 Collection 인터페이스를 구현한 어떤 클래스들이 존재하는지 Set, List 로 나눠서 살펴보자. 아래의 표는 Collection 의 구성을 표현한 표다. 표에서 인터페이스와 해당 인터페이스를 구현한 실제 클래스들를 보여주고 있다.

Interface Implementation

Collection Set HashSet LinkedHashSet TreeSet List ArrayList LinkedList Vector Stack

아래의 표는 Map 인터페이스를 구현한 클래스들이다. 역시 인터페이스와 그 구현 클래스들을 보여주고 있다.

Interface Implementation

Map HashMap LinkedHashMap IdentityHashMap WeakHashMap Hashtable TreeMap

이제 해당 인터페이스와 그 구현 클래스들의 특징이 어떤 것이고 어떻게 사용되는를 자세히 살펴보도록 하겠다.

Top

2.1. Collection 인터페이스

우선 먼저 Collection 인터페이스를 살펴보도록 하자.

01 package java.util; 02 03 public interface Collection { 04 // Query Operations 05 int size();

http://crowe.wowdns.com:8000/archives/dev_note/ (149 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

06 07 boolean isEmpty(); 08 09 boolean contains(Object o); 10 11 Iterator iterator(); 12 13 Object[] toArray(); 14 15 Object[] toArray(Object[] a); 16 17 // Modification Operations 18 boolean add(Object o); 19 20 boolean remove(Object o); // Bulk Operations 21 22 boolean containsAll(Collection c); 23 24 boolean addAll(Collection c); 25 26 boolean removeAll(Collection c); 27 28 boolean retainAll(Collection c); 29 30 void clear(); 31 32 // Comparison and hashing 33 boolean equals(Object o); 34 35 int hashCode(); 36 }

위 코드에서 주석으로 설명된 부분에서 알 수 있듯이 기능에 따라 4가지 분류의 메소드들로 나눌 수 있다. 그럼 각 기능에 따른 메소드들 을 자주 사용되는 것들 위주로 간단히 살펴보도록 하겠다.

첫번째로 쿼리(Query) 오퍼레이션들을 살펴보자. 쿼리 오퍼레이션은 컬렉션 안에 저장된 요소의 개수(size() 메소드)나 저장된 요소가 있 는지(isEmpty() 메소드), 컬렉션 안에 해당 메소드 안에 파라미터로 전달한 Object 요소가 들어있는지(contains(Object o) 메소드) 등 의 여부를 질의하는 메소드들의 분류다. 나중에 예제 소스에서도 살펴보겠지만 iterator() 메소드는 컬렉션 안에 저장된 요소들을 Iterator 에 순차적으로 저장한 후 그 Iterator 객체를 리턴해준다.

두번째는 변경(Modification) 오퍼레이션들이다. 메소드 이름만으로도 쉽게 알 수 있듯이 하나의 요소를 컬렉션에 추가(add(Object o)), 삭제(remove(Object o)) 하는 메소드들이다.

세번째는 대량으로 요소의 변경을 가하는 오퍼레이션들이다. 여기서 상당히 간편하게 사용될 수 있는 addAll(Collection c) 메소드가 있는 데 이것은 파라미터로 들어온 컬렉션 객체가 갖고 있는 요소들 모두를 저장하는 메소드이고 removeAll(Collection c) 메소드는 반대로 파 라미터로 들어온 컬렉션 객체가 갖고 있는 요소들 모두를 제거한다. 이외에 clear() 메소드는 해당 컬렉션 객체의 모든 요소를 전부 제거한 다.

마지막으로 비교(Comparison) 및 해싱(Hashing)을 위한 오퍼레이션들을 정의하는 메소드다. 자주 쓰이지 않으므로 별도의 언급은 하지 않겠다.

http://crowe.wowdns.com:8000/archives/dev_note/ (150 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

Top

2.2. Set 인터페이스와 구현 클래스들

Set 인터페이스를 살펴보자. Collection 인터페이스를 상속하므로 큰 차이점은 없고 단지 "Set" 은 중복을 허용하지 않는 자료구조였다는 것을 다시 한번 기억하도록 하자.

01 package java.util; 02 03 public interface Set extends Collection { 04 // Query Operations 05 int size(); 06 07 boolean isEmpty(); 08 09 boolean contains(Object o); 10 11 Iterator iterator(); 12 13 Object[] toArray(); 14 15 Object[] toArray(Object[] a); 16 17 // Modification Operations 18 boolean add(Object o); 19 20 boolean remove(Object o); // Bulk Modification Operations 21 22 boolean containsAll(Collection c); 23 24 boolean addAll(Collection c); 25 26 boolean removeAll(Collection c); 27 28 boolean retainAll(Collection c); 29 30 void clear(); 31 32 // Comparison and hashing 33 boolean equals(Object o); 34 35 int hashCode(); 36 }

위 코드를 보면 Collection 과 동일한 메소드만을 제공해주는 것을 볼 수 있다. 단지 구현 클래스 내부에 equals(Object o) 메소드를 이용 해서 중복을 허용하지 않도록 체크하는 기능이 더해져 있다.

그럼 이제부터 Set 을 구현한 클래스들을 살펴보도록 하겠다.

http://crowe.wowdns.com:8000/archives/dev_note/ (151 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

2.2.1. HashSet

2.2.2. LinkedHashSet(1.4에서 추가)

2.2.3. TreeSet

Top

2.3. List 인터페이스와 구현 클래스들

List 인터페이스는 순서 붙일 수 있는 컬렉션이다. 이 인터페이스의 사용자는 List 내의 어디에 각 요소가 삽입될까를 정밀하게 제어 할 수 있다. 사용자는 정수값의 인덱스(List 내의 위치)에 의해 요소에 액세스(access) 하거나 List 내의 요소를 검색할 수가 있다. Set 과는 다르 게, 보통 일반적으로 List는 중복하는 요소를 허가한다.

01 package java.util; 02 03 public interface List extends Collection { 04 // Query Operations 05 int size(); 06 07 boolean isEmpty(); 08 09 boolean contains(Object o); 10 11 Iterator iterator(); 12 13 Object[] toArray(); 14 15 Object[] toArray(Object[] a); 16 17 // Modification Operations 18 boolean add(Object o); 19 20 boolean remove(Object o); // Bulk Modification Operations 21 22 boolean containsAll(Collection c); 23 24 boolean addAll(Collection c); 25 26 boolean addAll(int index, Collection c); 27 28 boolean removeAll(Collection c); 29 30 boolean retainAll(Collection c); 31 32 void clear(); 33 34 // Comparison and hashing 35 boolean equals(Object o); 36

http://crowe.wowdns.com:8000/archives/dev_note/ (152 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

37 int hashCode(); 38 39 // Positional Access Operations 40 Object get(int index); 41 42 Object set(int index, Object element); 43 44 void add(int index, Object element); 45 46 Object remove(int index); 47 48 // Search Operations 49 int indexOf(Object o); 50 51 int lastIndexOf(Object o); 52 53 // List Iterators 54 ListIterator listIterator(); 55 56 ListIterator listIterator(int index); 57 58 // View 59 List subList(int fromIndex, int toIndex); 60 }

Collection 인터페이스에서 제공해주던 메소드들에 List 인터페이스의 특징인 특정 위치의 요소를 찾거나 특정 위치에 요소를 추가하는 등 의 메소드들이 추가되었다. 메소드 이름이 워낙 일관되고 명확하게 잘 지어져 있기 때문에(필자가 자바를 좋아하는 이유 중 하나) 메소드 이름만으로도 대강 어떤 역할을 하는지 짐작할 수 있을 것이다. List 인터페이스에서 추가된 메소드들은 Bold 를 주어 표현해놨다.

2.3.1. ArrayList

2.3.2. LinkedList

2.3.3. Vector

2.3.4. Stack

Top

2.4. Map 인터페이스와 구현 클래스들

Map 인터페이스는 키(key)를 값(value)에 매핑(mapping) 한다. 또한 Map은 동일한 키를 복수 등록할 수 없고 각 키는 1 개의 값밖에 매핑 할 수 없다. 즉, 하나의 키 값에 대응하는 하나의 값을 갖는 자료구조다.

01 package java.util; 02 03 public interface Map { 04 // Query Operations 05 int size(); http://crowe.wowdns.com:8000/archives/dev_note/ (153 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

06 07 boolean isEmpty(); 08 09 boolean containsKey(Object key); 10 11 boolean containsValue(Object value); 12 13 Object get(Object key); 14 15 // Modification Operations 16 Object put(Object key, Object value); 17 18 Object remove(Object key); 19 20 // Bulk Modification Operations 21 void putAll(Map t); 22 23 void clear(); 24 25 // Views 26 Set keySet(); 27 28 Collection values(); 29 30 Set entrySet(); 31 32 // Comparison and hashing 33 boolean equals(Object o); 34 35 int hashCode(); 36 37 interface Entry { 38 Object getKey(); 39 40 Object getValue(); 41 42 Object setValue(Object value); 43 44 boolean equals(Object o); 45 46 int hashCode(); 47 } 48 }

2.4.1. HashMap

2.4.2. LinkedHashMap(1.4에서 추가)

2.4.3. IdentityHashMap(1.4에서 추가)

2.4.4. WeakHashMap http://crowe.wowdns.com:8000/archives/dev_note/ (154 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

2.4.5. Hashtable

2.4.6. TreeMap

Top

2.5. Enumeration 와 Iterator 인터페이스

Collection Framework 에는 Enumeration 와 Iterator 라는 인터페이스가 있다. 사전적인 의미로는 반복, 순환이라는 뜻을 지니고 있 다. 어떤 객체들의 모임이 있을 때(Collection 계열 구현 클래스들, Collection 인터페이스에 iterator() 메소드가 있었음을 기억해라) 이 객체들을 어떤 순서에 의해서 하나씩 꺼내 쓰기 위한 인터페이스라고 할 수 있다. 원래 Java 2 이전는 Enumeration 이라는 인터페이스가 많이 사용되었지만 최근에는 Iterator 인터페이스가 더 많이 사용된다. 그 이유는 각 인터페이스를 살펴보며 알아보기로 하겠다.

2.5.1. Enumeration

아래의 Enumeration 인터페이스의 코드를 보자.

1 package java.util; 2 3 public interface Enumeration { 4 boolean hasMoreElements(); 5 6 Object nextElement(); 7 }

이 인터페이스는 단지 두개의 메소드만을 제공한다. 이 인터페이스의 사용은 상당히 간단하다. hasMoreElements() 메소드로 인터페이 스 안에 다음 요소가 있는지를 질의한다. 만약 true 가 리턴되었다면(다음 인덱스에 요소가 있다는 의미) nextElement() 메소드로 다음 요소를 꺼내서 사용하면 되는 것이다.

java.util.StringTokenizer 클래스가 Enumeration 인터페이스를 구현하고 있다. 따라서 StringTokenizer 클래스가 제공하는 메소드들 중에서 Enumeration 에서 정의한 2개의 메소드가 제공되는 것을 볼 수 있을 것이다.

2.5.2. Iterator

아래의 코드는 Iterator 인터페이스다.

1 package java.util; 2 3 public interface Iterator { 4 boolean hasNext(); 5 6 Object next(); 7 8 void remove(); 9 }

http://crowe.wowdns.com:8000/archives/dev_note/ (155 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

Enumeration 과의 차이점은 단지 remove() 메소드가 추가된 것 뿐이다. hasNext() 와 next() 메소드는 이름만 약간 다를 뿐 Enumeration 인터페이스의 hasMoreElements() 와 nextElement() 와 정확히 일치하는 기능을 한다.

그럼 왜 Enumeration 대신 Iterator 를 Java 2에서 추가해서 사용할까? 그것은 Enumeration 인터페이스는 집합 내에서 요소를 제거할 방법이 없기 때문이다. 그것을 보완하기 위해 나온 것이 Iterator 인터페이스다.

Top

3. J2SDK1.5 에서 추가될 auto-boxing 과 generic

Top

4. 자주 사용되는 컬렉션 객체들의 퍼포먼스 표

4-1. Set 객체

동기화 설명 가장 빠른 집합. HashMap 보다 느리지만 Set 인터페이스를 구현하고 있다. HashMap 은 Set 이 아 HashSet no 니라 Map 임.

TreeSet no HashSet보다 느리다. 차례대로 키를 사용할 수 있다. (키가 정렬됨)

4-2. Map 객체

동기화 설명 HashMap no 가장 빠른 매핑.

Hashtable yes HashMap 보다 느리지만 동기화한 HashMap 보다 빠르다.

TreeMap no Hashtable 과 HashMap 보다 느리다. 차례대로 키를 사용할 수 있다. (키가 정렬됨)

4-3. List 객체

동기화 설명 ArrayList no 가장 빠른 리스트.

다른 리스트보다 느리지만 큐로 이용했을 경우 더 빠를 수도 있다. 느린 이유는 ArrayList 나 Vector, LinkedList no Stack 과 달리 array 계열이 아니기 때문.

Vector yes ArrayList 보다 느리지만 동기화한 ArrayList 보다 빠르다.

Stack yes Vector 와 동일한 속도. LIFO 큐 기능을 제공한다.

필자생각 :: HashMap, ArrayList 에 동기화를 걸어 사용하는것 보다 동기화된 Hashtable, Vector 를 사용하는 것이 더 빠른것으로 미루 어 짐작컨데 Hashtable, Vector 의 경우에는 동기화가 되어 있는 내부 메소드들이 JIT 컴파일러에 의해 최적화 되는것 같음.

Top

5. 효율적인 컬렉션 객체들의 사용

http://crowe.wowdns.com:8000/archives/dev_note/ (156 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

Vector-Hashtable vs ArrayList-HashMap

보통 일반적으로 Vector 와 Hashtable 을 주로 사용하고 있을 것이다. 이 컬렉션 객체들은 모든 메소드가 synchronized 되어 있기 ㏏?? 동시에 여러 스레드가 접근 할 수 없다. 반명 동일한 기능을 하는데도 불구하고 ArrayList 와 HashMap 은 메소드가 synchronized 로 되 어있지 않아서 스레드들이 해당 객체에 동시접근이 가능하다.

은행에서 현금 입출금에 관련된것처럼 반드시 미션크리티컬한 로직이 필요한 곳에선 Vector와 Hashtable을 사용하는게 바람직하고 당연 하지만필자는 초보 분들이 프로그래밍한 코드에서 멀티스레드 접근을 해도 무방한데도 불구하고 모두 Vector 아니면 Hashtable을 사용하 는 것을 많이봐왔다. 이건 특히나 jsp 처럼 시간을 다투는 프로그램에선 치명타다. 동기화가 필요한지 아닌지를 잘 판단해서 정확히 필요 한 곳에만 Vector나 Hashtable을 사용하고 그 이외의 부분에선 ArrayList 나 HashMap 을 사용해야 할 것이다.

이미 다 아는 얘기라고 하실지도 모르겠지만 모르시는 분들이 너무 많아서 다시 한번 언급해봤다.

필자의 경우에는 효율을 좀 더 높이기 위해 동기화가 필요한 부분도 ArrayList 나 HashMap 에다가 락을 걸어서 멀티스레드의 폐해를 피 해가는 방식을 사용하고 있다. 모든 경우에 이렇게 한다는 것은 아니고 예를 들어 데이터를 넣는 부분은 멀티스레드 접근이 허용되지만 데 이터를 꺼낸 후 삭제해야 하는 부분은 동기화가 필요하다고 가정했을 때 동기화가 필요한 "데이터를 꺼낸 후 삭제" 하는부분에만 락을 걸어 서 동기화 블럭을 최소화시켜서 좀 더 효율을 가져간다는 것이다.

데이터를 컬렉션 객체에 넣고(put) 가져오고(get) 삭제하는(remove) 등의 모든 부분에 동기화가 필요하다면 당연히 그냥 이미 그런 용도 로 만들어진 Vector나 Hashtable 을 사용하는 것이 편하고 ?이렇게 사용 하는 것이 ArrayList 나 HashMap 의 모든 메소드에 락을 걸어 사용하는 것보다 더 빠르다.

Top

6. 아쉬움을 남긴채 강좌를 마무리하며

자바의 컬렉션 프레임워크는 매년 자바의 가장 훌륭한 라이브러리로 선정되는 파트다. 그만큼 설계적인 측면에서나 구현적인 측면에서 배 울 것이 많은 부분이다. 필자는 지금까지 바로 이 자바의 컬렉션 프레임워크에 대해 설명을 했다. 하지만 아쉬움이 남는다. 그 이유는 필자 가 "물고기 잡는 법"을 가르쳐 준 것이 아니라 물고기를 잡아서 준 것이기 때문이다. 즉, 어떻게 이런 자료구조(구현 클래스들)를 만들지에 대한 강좌가 아니라 단순히 만들어진 자료구조를 어떻게 이용하는지에 초점을 맞춰서 설명했다는 것이다.

필자가 독자분들에게 한가지 당부를 한다면 이미 만들어져 있는 api 를 단순히 이용하기 보다는 직접 만들어서 사용할 수 있는 능력을 키우 라는 것이다. 그게 진정 창조적인 그리고 프로페셔널한 개발자가 되기 위한 길이라고 필자는 생각한다. 따라서 우선 독자분들은 컬렉션 프 레임워크의 구현 클래스들이 어떻게 만들어졌는지를 직접 J2SDK 폴더 안의 src.zip 파일의 압축을 풀어서 분석해봤으면 한다. 그리고 나 름대로 직접 그런 자료구조를 구현하기 위한 방법들도 생각해보고 가능하다면 직접 구현해보았으면 한다. 그럼 이제 결코 짧지 않았던 컬 렉션 프레임워크 강좌를 마무리하겠다.

Top

Posted by Crowe Lee at 06:15 PM | Comments (0) | TrackBack

MSDN | Web Multimedia

View: Web Multimedia

Filter 및 Transition, Media Bar 등에 관한 API 및 예제를 보실 수 있습니다.

http://crowe.wowdns.com:8000/archives/dev_note/ (157 of 158)2005-07-05 오전 11:32:45 Binary Coder's Weblog: Dev. Note Archives

This section contains reference information for the Multimedia application programming interface (API).

Filters and Transitions With Microsoft® Internet Explorer 4.0 and later, you can apply various multimedia-style visual effects to your Web page. You can implement these effects in Web pages using Cascading Style Sheets (CSS) properties. By combining filters and transitions with basic scripting, you have a powerful tool for creating visually engaging and interactive documents. Internet Explorer 5.5 and later supports the richest variety of optimized filters. This documentation covers the basics of filters and transitions as well as all of the reference components.

HTML+TIME This section contains reference documentation for the HTML+TIME (Timed Interactive Multimedia Extensions)Â API.

Introduction to the Media Bar The Media Bar default behavior exposes much of the Media Bar functionality to scripting, giving you control over playback, navigation, and the player user interface (UI)......

Posted by Crowe Lee at 11:29 AM | Comments (0) | TrackBack

http://crowe.wowdns.com:8000/archives/dev_note/ (158 of 158)2005-07-05 오전 11:32:45