달력

42024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

ctrl + e 를 무심결에 눌렀더니.... 열려있는 파일들이 우측에 열리네 ^^

Ctrl + Shift + e 누르니 또 열리네 ^^

마우스로 블록 지정하고

Ctrl + Shift + c 누르니 한줄 주석이 토글되네 ^^

 

 

Posted by tornado
|

 public boolean sendMail(String filePath,String host, String receiver, String sender, String subject, String contents, InquireFile[] files){
  
  boolean flag = false;
  
  Properties prop = new Properties();
  
  prop.put("mail.smtp.host" , host);
  
  Session mailSession = Session.getDefaultInstance(prop, null);
  
  try{
   
   InternetAddress from = new InternetAddress(sender);
   
   Message msg = new MimeMessage(mailSession);
   
   msg.setFrom(from);
   
   msg.setRecipient(Message.RecipientType.TO, new InternetAddress(receiver));
   
   msg.setSubject(subject);
   
   Multipart multi = new MimeMultipart();      
   
   MimeBodyPart mbp = new MimeBodyPart();
   
   mbp.setContent(contents, "text/html; charset=ms949");
   
   multi.addBodyPart(mbp);
   
   if(files != null){
    
    for(int i = 0; i < files.length; i++){
     
     if(files[i] == null ) continue;
     
     DataSource fds = new FileDataSource(filePath + "/" + files[i].getFileName() );
     
     MimeBodyPart m = new MimeBodyPart();
     
     m.setDataHandler(new DataHandler(fds));
  
     m.setFileName(  toEng(fds.getName() ) );
     
     multi.addBodyPart(m);
     
    }
   }
   
   msg.setContent(multi);
   
   msg.setSentDate(new Date());
   
   Transport.send(msg);
   
   
   flag = true;
   
   
  }catch(Exception e){
   
   Logger logger = Logger.getLogger(this.getClass());   
   
   logger.error(this.getClass().getName() + " : " + e);
   
  }
  
  return flag;
  
 }

 

 public static String toEng(String origin) throws Exception{
  
  
  return new String(origin.getBytes(), "ISO-8859-1");
 }

 

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

 

재미 있는건  한글 변환 부분 에서 new String(한글.getBytes("KSC5601"), "ISO-8859-1") 처럼 안해두 된다는것임...

 

다운로드시 한글 변환도 위와 같이 하면 URLEncoding 을 안해도 됨 ㅡㅡ 

 

daum.net , naver.com, hotmail.com, outlook express 에서 테스트 함

 

테스트 환경 : Win xp , tomcat 4.1.30 , Apache 2.0.49 , j2sdk 1.4.2_05

 

사용 디비 :  MySQL, Informix , Oracle

Posted by tornado
|

              JSP 에서의 파일 업로드

작성자 : (tornado@dolpung.pe.kr)

 

파일을 업로드 한다는 것은 원격의 사용자가 서버 측으로 네트워크를 통해 파일을 전송하는 일이다

보통 WEB 환경에서 사용자의 입력 값을 다른 페이지로 이동시키기 위해서는 Form 통한 데이터 전송을 하게 된다.

간단하게 사용자의 이름을 입력 받고 내용을 JSP 페이지나 기타 파일로 전송을 해야 때는 아래와 같은 HTML 폼이 작성이 되어야 것이다.

 

<Form action=receive.jsp Method=POST>

이름 : <Input type=TEXT name=userName> <BR>

<input type=SUBMIT value= >

</Form>


..more


Posted by tornado
|

http://java.sun.com/webservices/docs/1.0/tutorial/index.html

 

공부해야혀~~~~ 기초 까먹으면 골아퍼~~~

Posted by tornado
|

정리가 무쟈 깔끔~~

 

 

http://blog.empas.com/inter999/

 

 

 

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

[펌] JSP 에서의 파일 업로드  (3) 2004.08.12
java.sun.com ===&gt; web Service  (0) 2004.08.09
[struts] &lt;html:options /&gt; 태그 ...  (0) 2004.08.03
[펌] &lt;bean:size .. /&gt;  (0) 2004.07.20
[펌] Struts Tips  (0) 2004.07.20
Posted by tornado
|
Dear All
Can any body complete this oode , I mean to say that what needs to be done in
configuration file struts-config, where ARRAY information must be kept.. Can any body
complete the code that it will be directly a working code .I have  successfully
completed select using simple options .

<html:select  name="StudentForm" property="passwd" >
<html:options name="StudentForm" property="languages" />
</html:select>

But want to learn using collections I am suing struts1.1.
I am trying from so many days ...pl. help
Many many thanks in advance pl. help..

Kind Regards
Strutsguy
If nay boy needs all code for  above I can send all with java class and config file .

<html:options>
ARRAY information
ArrayList prod = new ArrayList();
prod.add( new org.apache.struts.util.LabelValueBean( "Widget", "20" ) );
prod.add( new org.apache.struts.util.LabelValueBean( "Sprocket", "29-2a" ) );
prod.add( new org.apache.struts.util.LabelValueBean( "Cog", "29s" ) );
prod.add( new org.apache.struts.util.LabelValueBean( "Dunsel", "943" ) );

LabelValueBean takes the first String argument as the displayed name and the second as
the value returned. We’ll see more about this in a bit.
Now that you have prod set up, you pass that as a session or request bean to the JSP.
request.setAttribute( Constants.PRODUCT_KEY, prod );
(the Constants.PRODUCT_KEY is set to our bean name: "products"; we use this to
centralize bean name management)
To use the data in the JSP, add the following code:
  <html:select property="product_id" styleClass="prodselect" >
    <html:options collection="products" property="value" labelProperty="label" />
  </html:select>
Notes: product_id is the variable the JSP will set to the product ID value of the
product selected by the user. products is the name of the bean (it must match what is
in Contants.PRODUCT_KEY -- we probably could've said
... collection="<%= Constants.PRODUCT_KEY %>" ...
in place of hard-coding the name. The choice is up to you.
property="value" and labelProperty="label" are automatically set by
org.apache.struts.util.LabelValueBean, so only use the values shown here.
The resulting HTML will look something like:
<select name="product_id" size="1">
<option value="20">Widget</option>
<option value="29-2a">Sprocket</option>
<option value="29s">Cog</option>
<option value="943">Dunsel</option>
</select>

Posted by tornado
|

<logic:present name="searchForm" property="results">

<hr width="100%" size="1" noshade="true">

<bean:size id="size" name="searchForm" property="results"/>
<logic:equal name="size" value="0">
<center><font color="red"><b>No Employees Found</b></font></center>
</logic:equal>

<logic:greaterThan name="size" value="0">
<table border="1">
<tr>
<th>Name</th>
<th>Social Security Number</th>
</tr>
<logic:iterate id="result" name="searchForm" property="results">
<tr>
<td><bean:write name="result" property="name"/></td>
<td><bean:write name="result" property="ssNum"/></td>
</tr>
</logic:iterate>
</table>
</logic:greaterThan>

</logic:present>

 

http://otn.oracle.com/oramag/oracle/04-may/o34dev_struts_l1.html

Posted by tornado
|

[펌] Struts Tips

JAVA/JSP_Servlet 2004. 7. 20. 09:57
Posted by tornado
|
RSS란?

RSS는 뉴스, 블로그 등 자주 갱신(update)되는 성격의 사이트를 위한 XML 기반의 포맷입니다. A라는 사이트 (또는 사용자)가 B라는 사이트의 RSS 파일을 정기적으로 수집하면 B 사이트의 갱신된 컨텐츠 제목, 링크, 발췌 내용을 자동화된 과정에 의해 자신의 사이트에 올려놓을 수도 있고, 개인 사용자는 RSS 리더 (reader) 프로그램을 사용해 B 사이트를 직접 방문하지 않고도 최신 컨텐츠를 편리하게 볼 수 있습니다.

블로그 사이트들을 지나다 보면 'Syndicate this site (XML)' 또는 과 같은 아이콘이 있는 것을 보셨을 겁니다. 이것은 모두 RSS 파일의 링크를 표시하는 것으로서 보통 파일명은 index.xml, index.rdf, rss.xml 등이고, RSS는 'RDF Site Summary' 또는 'Really Simple Syndication'의 약자입니다.

RSS 규격들은?

'RSS' 하나에 대해 두 가지의 용어가 있는 이유는, 최초 개발은 Netscape사에 의해 이루어졌으나 Netscape사가 개발을 포기하고 난 후 두 개의 개발 주체에서 규격 개발이 진행되어 왔기 때문입니다. 현재는 RSS-DEV Working Group의 RSS (RDF Site Summary) 1.0UserLand의 RSS (Really Simple Syndication) 2.0이 업계 표준 채택을 위한 경합을 벌이고 있습니다. 두 개의 규격이 기능상 약간씩의 차이가 있지만 UserLand의 2.0이 좀 더 상세한 기능을 제공하고 있습니다.

RSS의 활용분야는 앞에서 설명했듯이 현재 크게 두 가지로 볼 수 있습니다. 사이트의 컨텐츠 배급 [신디케이션, syndication] 또는 수집 [어그리게이션, aggregation], 그리고 개인 사용자의 컨텐츠 사용 편리성 제공이 바로 그것입니다.

사이트에서 활용하려면?

A 사이트가 B 사이트의 컨텐츠를 배급하기로 했다면 A 사이트는 B 사이트의 RSS 파일이 저장된 주소(URL) 파악, 갱신 주기 결정 등을 하고 이 컨텐츠가 표시될 A의 웹 페이지를 구성합니다.

그 후 A 사이트의 RSS 수집/발행 시스템이 작동하면 해당 시스템은 자동적으로 B 사이트 컨텐츠의 제목, 링크, 발췌 내용을 결정된 주기에 따라 갱신하여 A 사이트에 보여주게 됩니다.

또는 A 사이트가 특정 주제에 대한 블로그나 뉴스를 모두 수집하여 서비스하고 싶다면, 그 주제에 관한 컨텐츠를 제공하는 C, D, E 사이트의 RSS 파일을 수집하여 같은 방식으로 서비스하면 됩니다.

이 단계 이전에 B 사이트는 자신의 사이트에서 RSS 파일을 제공해야 하는데 무버블 타입 (Movable Type), 라디오 유저랜드 (Radio UserLand), 블로거 (Blogger), 뉴클리어스 (Nucleus) 등의 블로그 출판 시스템들은 대부분 이 기능을 제공하고 있습니다. 이 시스템들에서는 새 글을 올리면 지정된 RSS 템플릿에 따라 RSS 파일이 자동으로 생성되어 지정한 위치에 저장됩니다.

개인이 활용하려면?

개인 사용자의 경우 웹브라우저를 이용해 B, C, D, E 사이트를 하나씩 방문해서 최신 컨텐츠를 확인하기에는 시간도 많이 걸리고 불편합니다. 이 경우 역시 RSS 리더 프로그램에 각 사이트의 RSS 파일 URL과 갱신 주기를 저장해 놓습니다. 그러면 RSS 리더 프로그램를 이용해 각 사이트의 최신 컨텐츠를 한 눈에 파악하고 어떤 것을 읽어야 할 지 파악할 수 있습니다.

RSS 리더 프로그램은 윈도우, 맥 OS 등 각각의 플랫폼마다 무료 또는 상용 버전들이 개발되어 있습니다. 각자의 필요에 따라 선택해서 사용하면 되겠습니다. 개인적으로는 제가 사용하고 있는 SharpReader (윈도우용)를 추천합니다. 무료이고 RSS 사이트의 분류, 한글 처리, 속도, 사용편이성, 필수 기능 등에 있어서 만족할만 합니다. [스크린샷 보기]

상용으로 많이 쓰이는 것으로는 NewzCrawler가 있습니다. 시험판을 사용해 본 소감은 불필요하게 느껴지는 고급 기능들이 좀 많은 것 같습니다. 다양한 기능들을 원하시는 분은 사용해 보셔도 괜찮을 듯 합니다.

국내에서는 어떻게 쓰이고 있나?

국내 블로그 서비스 업체들은 한미르를 제외하고는 RSS 파일을 제공하지 않고 있습니다. 몇몇 사이트가 제공하고 있는 '링블로그', '다른 블로그 구독' 등의 서비스는 해당 블로그 서비스 내에서만 가능하므로 RSS의 활용 범위와 목적과는 너무나도 큰 차이가 있습니다.

이후 서비스 업그레이드 범위에 이 부분도 포함시키는 것을 서비스 업체들께 권유하고 싶습니다. 블로그는 개별 업체 서비스의 울타리 안에 가둬둔 채로 활성화 시키기에는 아까울 뿐 아니라 적합하지 않은 서비스라고 생각합니다. 이 틀을 깨는 업체가 블로그 서비스 업계에서 앞서나갈 수 있지 않을까 하는 생각이 듭니다.

국내 뉴스 사이트의 경우 RSS를 공개하는 곳은 전무합니다. 외국의 경우 BBC, 뉴욕 타임즈, 가디언, 와이어드 등 유명 언론들이 RSS를 제공, 공개하고 있는 것과 비교했을 때 큰 차이가 있습니다. 빠른 시일 내에 많은 국내 사이트들도 RSS를 제공하게 되기를 기대해 봅니다.

현재 RSS를 제공하고 있지 않은 개인 블로그 사이트들도 이것을 제공한다면 블로그 간의 활발한 커뮤니케이션과 네트워크를 형성하는 데 큰 도움이 되지 않을까 합니다. 아직까지 국내에는 BlogdexMetaFilter와 같은 메타 블로그 서비스가 없는 관계로 개인 블로그들이 그 진가를 발휘하지 못하고 있다는 느낌도 있지만 이후 이런 서비스들이 반드시 생겨날 것이고, 그 때에는 국내 블로그들의 가치가 RSS를 통해 빛을 더할 것이라고 예상해 봅니다.

☞ 관련 링크



/최호찬 기자 (choi@hochan.net)
덧붙이는 글
PS. 국내 사용자들도 RSS 관련 기술에 대해 많이 파악하고 있는 것 같습니다. 하지만 검색을 해보면 해당 국내 자료는 매우 부족한 상황입니다. 관련 전문가들께서 정보 및 자료 제공을 해주신다면 국내에서도 풍부한 논의와 환경을 갖추게 되지 않을까 하고 생각해 보았습니다.

이 기사는 기자의 홈페이지인 HOCHAN.NET에도 함께 실립니다.

Posted by tornado
|

<Listener className="org.apache.catalina.startup.UserConfig"
            directoryName="public_html"
            userClass="org.apache.catalina.startup.PasswdUserDatabase"/>

 

 

사용하고자 하는 host 안에  넣어주면 /etc/passwd 안에 들어있는 user 의 이름으로 된

주소를 인식하여 브라우저에 표현해 준다.

 

xxx.com/~계정/   과 같이 접근..

 

 

사이트가 읍어져서리.. 생각나는대로 계속 블로그에 적는중...

Posted by tornado
|

Commons BeanUtil 에는 참 유용한게 많다..

 

그중 RowSet 과 비스무리(?) 한 것이 하나 있는데..

 

바로 RowSetDynaClass 이다...

 

 

일반적 JDBC 프로그래밍에서는 아래와 같은 순서를 가진다.

 

pstmt = t.prepareStatemet(query);

pstmt.setXXX(....);

rs = pstmt.executeQuery();

 

while(rs.next()){

 ... 작업들.....

}

 

rs.close();

 

이런 구조를 가지고 가게 되며, 커넥션이 닫히기 전에 resultSet 을 순차적으로 내려가면서 작업을 한다.

 

Commons BeanUtils 안에는 RowSetDynaClass 라는 클래스가 있다.

이 클래스는 JDBC 연결이 끊어진 이후에도 사용가능한 CachedRowSet 과 비슷한 기능을 제공해준다.

 

이것의 사용법은 상당히 간단하다. 참조 코드는 다음과 같다.

 

   pstmt = t.prepareStatement(query);
   rs = pstmt.executeQuery();
   rsdc = new RowSetDynaClass(rs);
   
   rs.close();

....

 

finally{   ... conn.close(); }

 

요기까지가 끝이다.. 이후에는 rsdc 라는 녀석을 이용해서 데이터를 취득하면 된다.

이렇게 데이터를 가져오게 되면

rsdc 에서는 getRows() 메소드를 이용하여 List 객체를 취득할 수 있다.

 

List arr = rsdc.getRows();

 

또한 검색해 온 컬럼의 이름들을 Property 로 얻을 수도 있다.

 

 DynaProperty[] dynas = rsdc.getDynaProperties();
  
 for(int i = 0; i < dynas.length; i++){
  System.out.println(dynas[i].getName());
 }

 

DynaProperty 클래스는 일반적인 Property 개념으로 보면 된다.

 

또한 이렇게 얻어온 DynaProperty 배열객체를 이용하여 RowSetDynaClass 를 순환하며 내용을 참조할 수 있다.

 

List 형태로 반납된 rows 들을 순환하게 되면 DynaBean 이라는 객체를 반납하게 된다.

이 형태를 이용하여 rs.next() 작업과 동일한 결과를 얻을 수 있다.

 

 Iterator iter = arr.iterator();
 
 while(iter.hasNext()){
  DynaBean bean = (DynaBean)iter.next();
  
  for(int i = 0; i < dynas.length; i++){
   System.out.println(bean.get(dynas[i].getName()));
  } 
 }

 

 

사용하다 주의할 것은 테이블 컬럼명이 regiDate 와 같이 중간에 대문자가 끼어있을 경우이다

이렇게 중간에 대문자가 끼어있는 것들은 RowSetDynaClass 를 생성하는 과정에서

전부 소문자로 바뀌게 된다.

SQL 상에서는 대소문자를 가리지 않지만 객체에서 값을 얻어올때는 가린다는것에 주의!!!

 

그렇기 때문에 bean.get("regidate") 와 같이 프라퍼티를 전부 소문자로 적어야

제대로 출력될 것이다.

 

 

DAO Layer 측에서 해야할 일들이 DAO를 이용하는 Layer 에 종속적인 작업이 진행될 경우

RowSetDynaClass 를 이용하여 Action 측에서 사용하면 각 레이어의 할일이 조금더 분명해 질수 있다.

 

 

 

 

 

 

 

 

 

 

Posted by tornado
|

Apache 와 톰캣을 연동 했을경우에 골때리는 문제가 하나 생긴다.

 

아파치에서는 Domain 별 디렉토리를 가상호스트로 지정하여 사용하여야 하는데 주소는 다음과 같다.

 

http://aaa.com

 

이 주소에 대하여 DocumentRoot 를 지정할 경우 아래와 같이 특정 디렉토리를 명시하게 된다.

DocumentRoot /usr/local/tomcat4/webapps/ROOT

 

만약 가상호스트로 설정되어있다면

<VirtualHost ....>

   DocumentRoot /usr/local/.......

   .....

</VirtualHost>

 

 

 

또한 aaa.com/test/images 라고 요청 할 경우 디렉토리는 당연히

/usr/local/tomcat4/webapps/ROOT/test/images 와 같은 형태로 요청하게 된다.

 

문제는 test 라는 디렉토리가 하나의 컨텍스트 였을 경우 발생하게 된다.

 

톰캣에서 하나의 웹 애플리케이션의 구성은 webapps/ 를 기준으로 /ROOT  , /examples 등이 있게 된다.

 

이것은 하나의 가상호스트처럼 동작하게 되지만 Apache 에서 볼때는 하나의 디렉토리로 인식하게 되는 것 같다.

 

즉 이 얘기는 http://aaa.com:8080/test 라고 요청할 경우 test 라는 하나의 컨텍스트로 인식하여

 

test 웹 애플리케이션을 호출해 주겠지만 아파치와 연동 되었을 경우에는

 

/test 를 단지 ROOT 애플리케이션의 서브디렉토리로 인식하게 된다는 것이다.

 

아파치에서 톰캣으로 요청을 넘길때 /test/*.jsp  ... /test/*.do 와 같이 넘기게 되면

톰캣에서는 test 라는 하나의 컨텍스트 차원에서 처리를 하지만 아파치에서는 .jsp 와 .do 를 제외한 모든 자원을 test 라는 디렉토리로 보고 처리를 한다는 것이다.

 

이때는 이 컨텍스트를 하나의 도메인으로 빼는게 맞는것 같고...

 

네임서버에서 test.aaa.com 과 같은 형태로 주던지... bbb.com 이라는 완전한 도메인을 빼던지

해주고... Apache 에서 DocumentRoot 를 따로 지정해 주는게 바른 방향 같다.

 

글쓰다가 회의해서리.. 뭔소린지.. 꼬이네.. 캬캬캬

 

 

 

Posted by tornado
|

struts taglib 사용 --- html 태그


html 태그중에서 link 를 걸어야 할 경우가 생기게 되는데 ,
보통 struts 프로그램을 짜다보면 Controller 에서 Model 측을 접근하여
데이터를 가져온 후 해당 데이터를 Java Beans 스타일(또는 Collections) 에
저장하고, 그 객체들을 request 에 담아서 forward 시키게 된다.

jsp 페이지에서는 이러한 객체를 <html:iterate /> 태그와 같은 것들을 이용해서 사용하게 되는데
request 에 저장되어 있는 객체를 ?a=beanValue&b=beanValue 와 같이 사용할
경우에는 아래와 같은 방법으로 이용한다.

 

BoardConfigBean <-- Action 부분에서 Model 측에 접근하여 Wrapping 한
                              사용자 정의 객체이다.

request.setAttribute("BoardConfigBean", new BoardConfigBean().setIdx(1)); 처럼 저장하겠죠.

 

이렇게 저장된 객체를 JSP 에서는 아래와 같이 불러다가 씁니다.

 

<html:link page="/siteMgr/BoardConfig.do?method=delete"
                paramProperty="idx"
                paramId="idx"
                paramName="BoardConfigBean"
                scope="request">삭제</html:link>


html:link 속성 중에 paramName 이라는 속성은 특정 속성에 지정된 객체를
이름으로 지정한다.

paramId 속성은 쿼리스트링 중 key 부분에 해당한다.
paramProperty 속성은 쿼리스트링 중 value 부분에 해당하며
request 와 같은 속성에 저장된 객체의 getter 부분에 해당한다.


html:link 태그에는 map 속성을 이용할 수 있으며
Map 에 담고 그 형태를 html:link 에 넣어줄 경우 자동으로 링크를 만들어 주기도 한다.

편리한거는 자동으로 ?key=value&key1=value1 과 같이 해준다.

 

자세한 거는 struts 사이트를 뒤져주세용...  또 너무 남용하지 마세용

 

Posted by tornado
|

별건 아니지만.. 자칫 실수하면 짜증나는 문제이기에.. 적어놔야징..

 

 

# Struts 에서 Message Resource 를 여러개 설정해야할 경우 방법

 

조건)

          titleResource.properties   <-- 브라우저의 타이틀 메세지 관리

          adminResource.properties  <-- 관리 페이지의 각종 메세지 관리

          ..... 등등등

 

 

이렇게 두개가 있을 경우 먼저 web.xml 파일에 ActionServlet 등록 태그에 아래와 같이 메세지 리소스를 추가.

 

(web.xml)

 

 <!-- Struts ActionServlet -->
 <servlet>
  <servlet-name>action</servlet-name>
  <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> 
  
  <init-param>
   <param-name>adminResource</param-name>
   <param-value>conf.adminResource</param-value>
  </init-param>
  
  <init-param>
   <param-name>titleResource</param-name>
   <param-value>conf.titleResource</param-value>
  </init-param>

 

... 기타 설정들

 

 

 

(strtus-config.xml)

 

 <message-resources parameter="conf.adminResource" />
 <message-resources parameter="conf.titleResource" />

 

 

 

설정하고

<bean:message key="main.title.index" />

... 기타 필요한 메세지들.... 뿌리면 잘 되더만..

 

자세한 사항은 struts 사이트에서 참고바람

 

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

apache + tomcat 연동시 Context 문제  (0) 2004.04.16
&lt;html:link /&gt; 태그 중에서 ^^  (0) 2004.04.07
[펌] MVC 모델과 Observer 패턴  (0) 2004.04.01
[펌] 스트럿츠 이디엄  (0) 2004.04.01
[펌] JavaServer Faces 입문  (0) 2004.04.01
Posted by tornado
|
MVC 모델과 Observer 패턴
eapoeash (2004-03-24 16:58:12)

저자: 김대곤

이 패턴을 설명하기 전에 MVC 모델을 먼저 살펴보자. MVC 모델은 조금은 거창하게 알려져 있는 듯하다. 거의 그런 경우가 드물지만, 몇 년 전 필자가 본 누군가의 이력서에는 자신의 강점을 프로젝트에 MVC모델을 적용한다는 것이라고 적혀 있는 걸 보았다. 필자가 보기에 MVC 모델을 적용한다는 것은 달리기를 한다는 말과 같아 보인다. 필자도 달리기를 한다. 그러나 잘 하지는 못한다. 국민학교(초등학교)때부터 체육은 언제나 주저하게 되는 분야였다. 달리기를 잘하는 것과 달리기를 하는 것은 다른 문제이다.

MVC 모델에서 MVC는 각각 Model, View, Control를 말한다. Model은 데이터 또는 기본 기능을 말하고, View는 유저 인터페이스를 말한다. 이 두가지는 시스템 개발에 있어서 없어서는 안되는 부분이며, 누가 개발을 하더라도 반드시 있기 마련이다. MVC 모델은 C 모델이라고 불러도 상관없을 만큼 한마디로 말해서 Control이라는 레이어 계층을 두자는 것이다. Graphical User Interface를 사용할 때, Model 계층과 View 계층 사이에 Control 계층을 만들어서 사용하자는 것이다. 왜 그렇게 해야 하는가? 여기에는 몇가지 가정이 담겨 있다.

두 계층 사이에 필수적이지 않은 다른 계층을 두는 이유는 간단하게 말해서 두 계층이 직접적으로 Coupling되는 것을 피하려고 하기 때문이다. 즉, Control 계층은 Model 계층과 View 계층의 high Coupling을 막아준다. Model 계층과 View 계층의 high coupling이 좋지 않은 이유는 Model과 View의 생명주기가 다르기 때문이다. 일반적으로 Model 계층의 생명주기가 길며, 변경이 드물게 일어난다고 여겨진다. 예를 들어, 물건을 파는 웹 사이트에서 사이버 매장을 새 단장하는 경우를 생각해 보자. 일반적으로 View만 바뀌지 Model은 바뀌지 않는다. 같은 기능을 가진 시스템에서 사용자에 따라서 다른 View가 존재할 수도 있다. 또는 웹사이트에서도 보고, PDA에서도 보는 경우도 같은 Model이 서로 다른 View로 표현되는 예라고 할 수 있다. 직접적인 Coupling은 Model과 View를 같이 바뀌도록 만든다. 이것을 Control 계층이 막아준다. 그럼 View가 바뀌면 Control이 바뀌어야 하는 것이면 마찬가지 아닌가 하는 의문이 생긴다. Control 계층의 로직은 매우 간단한다. 아니 간단하게 설계해야 한다. 적어도 Model를 바꾸는 것보다 훨씬 쉽게. Control 계층은 단지 Coordinator에 불과해야 하기 때문이다.

MVC 모델은 Layered Style이고 Layered Style에서는 아래 Layer가 상위 Layer를 사용하는 것을 권장하지 않는다. A 계층이 B 계층을 사용하게 되면, A 계층은 B 계층에 종속된다. 즉, B 계층이 없으면 A 계층은 자신의 역할을 수행하지 못하는 것이다. B 계층은 A 계층이 없어도 문제가 없다. 이러한 의존관계를 Usage dependency 라고 부른다. Layered Style에서 상위 계층은 하위 계층에 대해 Usage dependency가 있다. 하위 계층이 더 생명주기가 길며, 변경되는 일이 적어야 한다. 그런데 하위 계층이 상위 계층에 있는 기능을 사용하면 하위 계층이 상위 계층에 종속되어 버린다. 만약, MVC 모델에서 Model이 View의 기능을 사용해야 하는 경우에 Model이 View에 종속되는 것을 어떻게 막을 것인가?

이것이 Observer 패턴을 사용하게 되는 중에 하나이다. 이제 Observer 패턴에 대해서 살펴보자. GoF의 디자인 패턴에는 Observer 패턴의 목적을 이렇게 정의하고 있다. "Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and update automatically." "한 객체의 상태가 바뀔 때 다른 객체들에게 그 사실을 알려주고 자동적으로 업데이트 되도록 하고자 한다면 이 패턴을 쓰시오" 정도 될 것이다. 좀 걸리는 단어는 one-to-many dependency다. 이제 하나씩 살펴보도록 하자.

한 객체의 상태에 변화가 생기면, 그 객체의 상태에 관심있는 객체들에게 알려주는 방식이다. 객체에게 무엇인가를 알려주는 방법은 객체의 메소드를 통해서 이다. 관심 있는 객체의 메소드를 호출해주면 된다. 하지만 객체의 메소드를 사용하면, 이 객체에 대해 usage dependency가 생기게 된다. 즉, coupling이 높아진다. 이 패턴은 특정한 알려주는 방식을 제안하고 있다.

이 패턴이 어떻게 작동되는지 살펴보자. 예제 파일[Download]을 실행해 보자. 아래의 두 화면이 보인다. Input data 에 데이터를 입력하면 Observer 화면에 나타나도록 되어 있다.
그림1

그림2

"Subject.java"에 있는 Vector 데이타가 바뀔 때 자동적으로 바뀐 내용을 보여주려고 한다. Subject 객체가 Observer객체의 내용을 바꾸기 위해서는 Observer객체의 메소드를 호출해야 하고, 메소드를 호출하기 위해서는 그 객체의 Reference가 필요하다.

Subject 객체의 addObserver라는 메소드를 통해 Observer는 자신의 Reference를 제공하고, Subject는 Observer 객체(MyFrame.java)의 update(Vector v) 메소드를 통해 화면을 바꾸어 준다. 별 무리 없이 작동하고 있다. 문제점은? MyFrame 클래스를 삭제해 보면 알게 된다. Subject 클래스가 더 이상 컴파일 되지 않는다. 이유는 간단하다. 자신의 Observer였던 MyFrame 클래스를 소스안에 포함하고 있었기 때문이다. 만약, 객체의 상태에 관심을 가지고 있는 객체가 오직 하나이고, 변경될 소지가 적다면, 위와 같이 코딩해도 별 문제는 없을 것이다. 하지만 Observer가 한 개가 아니라 20개 정도 되면 20 개나 되는 타입의 클래스를 포함하고 있게 된다. 그 중에 updata(Vector v)라는 메소드를 가지고 있지 않은 클래스라도 있으면 그건 바로 에러가 된다.

이러한 문제를 해결하는 방법은 Subject가 각 Observer들의 클래스 타입 대신 인터페이스를 사용하고, 이 인터페이스는 자신을 구현하는 클래스들에게 update(Vector v)라는 메소드를 구현하도록 강제한다. [Download]

코드를 자세히 살펴보면, 이 Observer 패턴과 자바의 이벤트 처리가 같음을 알 수 있다. 리스너(ActionListener)를 만들고, 리스너를 Subject(JButton)에 등록하고 JButton의 상태가 변하면 ActionListener로 강제된 actionPerformed 메소드가 호출된다. 이 패턴도 Observer 인터페이스를 구현하는 객체를 만들고, 만든 객체를 Subject에 등록하고, Subject 객체는 자신의 상태가 바뀌면, 인터페이스에 정의된 메소드를 호출한다. 즉 Observer 패턴은 이벤트 시스템을 구현할 때 사용된다. 이벤트 시스템의 구현은 Observer 패턴이 주는 또 하나의 강점이다.

하지만 기본은 전혀 바뀌지 않았다. 객체에게 무엇인가를 알려주는 방식은 메소드이고 메소드의 호출을 위해서는 객체의 Reference가 필요하는 것. 조금 다른 방식일 뿐 이 두가지 작업은 패턴을 적용해도 피할 수 없는 것이다. 즉, 패턴은 어떤 문제들을 교묘히 피해가는, 평소에는 잘 생각나지 않는 방법을 알려주고 있다. 그래서 패턴을 언제 사용하는가에 대한 이해는 외우지 않고 있으면 잘 생각나지 않고, 사용하지 않으면 이해되지 않는 측면이 있다. 패턴은 처음 적용해 볼 때 가장 어렵다. 한 번 하고 나면 생각만큼 어렵지는 않다.

필자의 패턴 설명은 전체적으로 모든 내용을 다루고 있진 않다. 일반적으로 Subject는 attach(Obsever obs), detach(Observer obs), notifyAll() 이라는 메소드를 가지고 있다. 하지만 detach 메소드는 다루지 않았다. 인터페이스가 강제하는 메소드도 여러 형태가 될 수 있으며, 그 메소드에 내용이 전달되지 않고 Subject의 다른 메소드를 호출함으로 Subject의 상태를 가져오기도 한다. 자세한 사항은 Design Pattern 책을 참고하기 바란다. Jstorm(www.jstorm.pe.kr)에서도 Design Pattern에 관련된 자료들을 찾을 수 있으리라 생각된다.

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

&lt;html:link /&gt; 태그 중에서 ^^  (0) 2004.04.07
[Struts] MessageResource 여러개 쓰기  (0) 2004.04.01
[펌] 스트럿츠 이디엄  (0) 2004.04.01
[펌] JavaServer Faces 입문  (0) 2004.04.01
[펌] Jakarta Digester 학습하기  (0) 2004.04.01
Posted by tornado
|
스트럿츠 이디엄
hanbit (2003-12-19 14:19:09)

†역자 주: 본 기사는 원문의 절반도 안되는 내용이나 우선은 역자 자신이 딱 이거다 싶은 내용과 직접 사용해본 내용만을 번역해 놓은 것이다. 스트러츠를 계속 사용하면서 이해가 되는 부분은 차츰 하나씩 번역해나갈 생각이다.
스타일시트나 다른 HTML 자원을 참조할 시에는 <html:link rewrite>를 사용하라

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

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

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

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

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

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

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

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

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

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

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

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

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

[Struts] MessageResource 여러개 쓰기  (0) 2004.04.01
[펌] MVC 모델과 Observer 패턴  (0) 2004.04.01
[펌] JavaServer Faces 입문  (0) 2004.04.01
[펌] Jakarta Digester 학습하기  (0) 2004.04.01
[펌] Torque프로젝트 사용기  (0) 2004.03.31
Posted by tornado
|
JavaServer Faces 입문
hanbit (2003-10-18 09:15:02)

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

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

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


Mac OS X for Java Geeks

참고 도서

Mac OS X for Java Geeks
Will Iverson




JSF 작동방법

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

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

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

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

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

간단한 JSF 애플리케이션

Source Code

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



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

디렉토리 구조 만들기

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


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

배치 디스크립터 작성하기

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

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

JSP 페이지 만들기

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

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


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

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

객체 모델 작성하기

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

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

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

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

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

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


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

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


[그림 4] 덧셈의 결과

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

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

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

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

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

Jakarta Digester 학습하기

등록일: 2002년 11월 05일

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

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

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

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

Jarkarta Digester 프레임워크

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

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

예제 문서와 빈즈

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

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

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

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

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

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

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

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


Java & XML Data Binding

참고 도서

Java & XML Data Binding
Brett McLaughlin




임의의 함수 호출

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

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

표준 규칙 요약

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

torque-3.1.zip  <==실행

Torque.properties의 편집

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

클라이언트의 코딩과 실행

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

build.properties의 편집

JDBC 드라이버는 lib에 카피

Ant 태스크를 실행


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

avalon-framework-4.1.4.jar

commons-configuration-1.0-dev-3.20030607.194155.jar

commons-dbcp-20030825.184428.jar

commons-pool-20030825.183949.jar

jcs-20030822.182132.jar

jdbc-2.0.jar

jndi-1.2.1.jar

junit-3.8.1.jar

log4j-1.2.8.jar

logkit-1.0.1.jar

stratum-1.0-b3.jar

torque-3.1.jar

village-2.0-dev-20030825.jar

xercesImpl-2.0.2.jar

xmlParserAPIs-2.0.2.jar


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


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

<servlet>

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

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

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

      <init-param>

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

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

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

      </init-param>

      <init-param>

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

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

      </init-param>

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

    </servlet>


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

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

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

package initialize;


/**

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

*/


import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;


import org.apache.torque.Torque;


public class TorqueInit extends HttpServlet{

    private static boolean hasInitialized = false;


        public void init() throws ServletException{

                

                synchronized(TorqueInit.class){

                        

                        if(!hasInitialized){

                                try{

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

                                if(propFileName==null) {

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

                                }

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

                                        Torque.init(propFile);

                                        hasInitialized = true;

                                }catch(Exception e){

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

                                }

                        }

                }

                

        }

}

 

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

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

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


 

torque.applicationRoot = /usr/local/tomcat

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

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

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

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

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

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


torque.defaults.pool.logInterval = 0

torque.defaults.pool.connectionWaitTimeout = 10

torque.defaults.pool.defaultMaxConnections = 80

torque.defaults.pool.maxExpiryTime = 3600


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

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

torque.defaults.connection.user = test

torque.defaults.connection.password = test


torque.database.default=testdb

torque.database.testdb.adapter=mysql


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

torque.dsfactory.testdb.pool.defaultMaxActive=10

torque.dsfactory.testdb.pool.testOnBorrow=true

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

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

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

torque.dsfactory.testdb.connection.user = test

torque.dsfactory.testdb.connection.password = test


torque.idbroker.cleverquantity=true

torque.manager.useCache = true


======

 

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


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


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

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

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

<예>

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

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


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

<!--                                                                      -->

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

<!--                                                                      -->

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

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

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

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

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

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

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


<database name="struts-address">

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

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

[펌] Log4J...

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

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

 

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

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

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

import java.io.FileOutputStream;

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

public class Logging4J {

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

 public void consoleLog() {

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

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

  SimpleLayout layout = new SimpleLayout();

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

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

  logger.addAppender(appender);

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

  HTMLLayout layout = new HTMLLayout();

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

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

  logger.addAppender(appender);

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

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

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

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

  //option setting
  layout.setLocationInfo(true);

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

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

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

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

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

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

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

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

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

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

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

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