98..Etc/Etc...2008. 8. 12. 22:24
반응형
We Solve Password Problems

Forgot your password? Need to regain access to password-protected files or systems? Passwords list destroyed?
Passware software recovers or resets passwords for Windows, Word , Excel, QuickBooks, Access, PDF and more than 100 document types.

Windows Key
Quickly and easily reset Windows login passwords in a matter of minutes – no need to reinstall the system.
Read MoreDownloadBuy Now
 
     
Office Key
Recovers all types of passwords for MS Office documents: Excel, Word, Outlook, Access, PowerPoint and Visual Basic for Applications (VBA).
Read MoreDownloadBuy Now
 
     
Passware Kit Enterprise
A complete password recovery solution that supports more than 100 document types.
Read MoreDownloadBuy Now
Posted by 1010
90.개발관련문서2008. 8. 12. 17:33
반응형

Q : 파일 다운로드시 한글파일의 경우 다운로드 되지 않거나 글자가 깨집니다.
A : 익스플로러 "도구 > 인터넷 옵션 > 고급 "에서 고급 UTF8옵션 해제하시면 됩니다.

최종수정일자 : 2008.01.18 14:49:11



출처 : http://www.ihelpers.co.kr/programming/reference/index.php
Posted by 1010
반응형

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Vertically and Horizontally Centering a DIV</title>

<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />

<script type="text/javascript" src="/mint/mint.js.php"></script>

<style type="text/css">
body {
 font-size: 12px;
 font-family: arial, helvetica, sans-serif;
 color: #333;
}
p {
 margin: 1em;
}
.comments {
 background-color: #e3e3e3;
 border-top: 1px solid #ccc;
 border-bottom: 1px solid #ccc;
 padding: 2px;
}

#mydiv {
 position:absolute;
 top: 50%;
 left: 50%;
 width:30em;
 height:18em;
 margin-top: -9em; /*set to a negative number 1/2 of your height*/
 margin-left: -15em; /*set to a negative number 1/2 of your width*/
 border: 1px solid #ccc;
 background-color: #f3f3f3;
}

</style>

</head>
<body>
<div id="mydiv">
 <p>This is a vertically and horizontally centered &lt;div&gt; tag. Try resizing your browser.</p>
 
 <div class="comments">
  <p><strong>Comments:</strong></p>
  <p>Tested in: <span style="color: darkblue">Firefox, IE6, Opera 7, NN4.7, NN7, and Mozilla 1.2.</span><br />
  Works in: <span style="color: darkblue">Firefox, IE6, Opera 7, NN7, and Mozilla 1.2.</span><br />
  Doesn't work in: <span style="color: darkblue">NN4.7</span></p>
  <p>&copy; Copyright 2003, <a href="/">Infinity Web Design</a></p>
 </div>
</div>
</body>
</html>

Posted by 1010
반응형

Table 줄바꿈

  1. 강제로 줄바꿈(특수문자제외) style="word-break:break-all"
  2. 강제로 줄바꿈(특수문자포함) style="word-wrap:break-word"
  3. 줄바꿈 못하도록 nowrap
Posted by 1010
반응형

1. 소스

<html>
<head>
<title>Bizest 체험관 - KIS</title>
<meta http-equiv="Content-Type" content="text/html; charset=euc-kr">
<link rel="alternate" type="application/rss+xml" href="/rss/" title="RSS feed for Bizest KIS"/>
<link rel="stylesheet" type="text/css" href="/skin/x4/css/blue.css" />
<link rel="stylesheet" type="text/css" href="/skin/x4/css/layout.css" />
<link rel="stylesheet" type="text/css" href="/skin/x4/css/form.css" />
<link rel="stylesheet" type="text/css" href="/skin/x4/css/form.css" />
<link rel="stylesheet" media="print" type="text/css" href="/skin/x4/css/print.css" />

print.css

body {
 
 /*
 font-family:"Times New Roman", serif;
 font-size:12pt;
 */

 background:none;

 margin-left: 20px;
 margin-top: 10px;
 margin-right: 20px;
 margin-bottom: 10px;
}

#container {
 width:100%;
}

#container_head {
 DISPLAY: none;
}

#container_body .left {
 DISPLAY: none;
}

#container_body {
 width:100%;
}

#container_body .right {
 width:100%;
}

#container_footer {
 DISPLAY: none;
}



출처 : http://www.ihelpers.co.kr/programming/tipntech.php?CMD=view&TYPE=0&KEY=&SC=S&&CC=&PAGE=1&IDX=639
Posted by 1010
반응형
1. JavaScript 최적화 도구
2. CSS 최적화 도구
3. 웹 사이트 성능 개선

출처 : http://www.mimul.com/pebble/default/2008/01/29/1201616760000.html

Posted by 1010
61.Linux2008. 8. 12. 17:08
반응형

사용자 생성 및 계정 관리

1. 계정 조회

현재 시스템에 로그인된 사용자 계정을 조회 / 사용자 계정에 대한 정보를 확인

          cat –n /etc/passwd

                      root  :  x  :  o  :  o  :  root  :  /root  :  /bin/bash

                        1    2    3    4      5        6          7

                                    1 : 사용자명
                                    2 : 패스워드 (/etc/shadow 파일에 암호화되어 있음)
                                    3 : 사용자 계정 uid
                                    4 : 사용자 계정 gid
                                    5 : 사용자 계정 이름 정보
                                    6 : 사용자 계정 홈 디렉토리
                                    7 : 사용자 계정 로그인 셀

 

            cat –n /etc/shadow

                          root  :  #$%!234^x13  :  11535  :  o  :  99999  :  7  :  :  :  :

                            1            2            3      4      5      6  7  8  9

                                        1 : 사용자명
                                        2 : 패스워드
                                        3 : 패스워드 파일 최종 수정일
                                        4 : 패스워드 변경 최소일
                                        5 : 패스워드 변경 최대일
                                        6 : 패스워드 만료 경고 기간
                                        7 : 패스워드 파기 기간 (패스워드 파기 후 계정 비활성 기간)
                                        8 : 계정 만료 기간
                                        9 : 예약 필드


2. 계정 생성 및 암호 설정

useradd  생성할 계정명

passwd  생성한 계정명

useradd [옵션] 로그인 계정

            -c comment : 사용자 이름 또는 정보
            -d home_directory : 사용자 계정 홈 디렉토리
            -e expire_date : 사용자 계정 유효 기간
            -f inactive_time : 비활성 기간
            -g initial_group : 기본 그룹
            -G grout : 다음 그룹
            -s shell : 기본 로그인 셀
            -u uid : 사용자 계정 uid

 
3. 계정 변경

usermod [옵션] 로그인 계정

-c comment : 사용자 이름 또는 정보
-d home_directory : 사용자 계정 홈 디렉토리
-e expire_date : 사용자 계정 유효 기간
-f inactive_time : 비활성 기간
-g initial_group : 기본 그룹
-G grout : 다음 그룹
-s shell : 기본 로그인 셀
-u uid : 사용자 계정 uid

                      usermod –d /home/user –m user
                      usermod –e 2003-04-05 user
                      usermod –f 3 user
                      usermod –g users user


4. 계정 삭제

userdel –r 계정 (-r : 해당 계정자의 홈디렉토리까지 한 번에 삭제)

 
5. 그룹조회

cat –n /etc/group

 
6. 그룹생성

groupadd [-g GID [-o]]            그룹 id (-o : GID 499이하 값으로 지정)

                      [-r]                    그룹 id 499이하 값으로 자동 지정
                      [-f]                    강제로 생성

                          groupadd –g 900 toheart (900 – groupid / toheart – 그룹명)

 
7. 그룹변경

groupmod [-g gid [-o]]            gid변경

                        [-n]                    새로운 그룹명으로 변경

                                    groupmod –g 700 toheart
                                    groupmod –n kkum toheart


8. 그룹삭제

groupdel group            group 제거

출처 : http://cafe.naver.com/frody.cafe?iframe_url=/BoardRead.do%3Farticleid=11

Posted by 1010
반응형

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title> obxGetColor, obxStickGraph</title>
<meta http-equiv="content-type" content="text/html; charset=euc-kr" />
 <script>
 IE=(window.showModalDialog) ? true : false;

 function obxGetColor(color,gap) {

  var rtn='',col,tmp;

  for(var x=0;x <6; x+=2) {
   col=parseInt(color.substr(x,2),16)+gap;
   if (col > 255) col = 255;
   else if (col < 0) col=0;

   if(col < 10) rtn+='0'+col.toString(16);
   else rtn+=col.toString(16);
  }

  return rtn;
 }


 function obxStickGraph(dsize) {

  this.step=3;
  this.speed=10;
  this.total=0;
  this.max=0;
  this.dsize=dsize;
  this.statictext=false;

  this.item= new Object();

  this.add = function (id,size,color,text) {
   this.total += size;
   this.max=Math.max(this.max,size);
   this.item[id]= {'size' : size, 'color' : color.replace('#',''), 'text' : text}
  }

  this.draw = function (id,action) {

   var dColor=obxGetColor(this.item[id].color,-20);

   document.write("<div></div>");
   //this.item[id].div=document.body.appendChild(document.createElement('div'));
   var divs=document.getElementsByTagName('div');
   this.item[id].div=divs[divs.length-1];

   this.item[id].div.style.borderLeft="1px solid #"+dColor;
   this.item[id].div.style.overflow="hidden";
   this.item[id].div.style.height="11px";

   if(!action) this.actDraw(id);
   else this.actDraw(id,5);

  }

  this.getCss= function(width,height,color,bwidth,bcolor) {
   if(width<1)width=1;
   return "overflow:hidden;width:"+width+";height:"+height+";background-color:"+color+";border-right:"+bwidth+"px #"+bcolor+" solid";

  }

  this.actDraw= function(id,limit) {

   var pp = this.item[id].size / this.total;
   var sizep = this.item[id].size / this.max;

   if(!limit) var size=this.dsize * sizep;
   else size=limit;

   var hit = Math.round(this.max/this.dsize*size);
   var percent =(Math.round(this.max/this.dsize*size)/this.total*100).toString().match(/[0-9]*(?:\.[0-9][0-9])?/);

   var text = (this.item[id].text) ? this.item[id].text.replace('#',hit).replace('$',percent) : percent + " %"


   var Color=this.item[id].color;
   var hColor=obxGetColor(Color,20);
   var hhColor=obxGetColor(Color,30);
   var dColor=obxGetColor(this.item[id].color,-20);
   var add='',d;

   if(IE || opera) d=new Array(3,2,1);
   else d=new Array(3,4,5);

   this.item[id].div.innerHTML= ''
    +'<div style="'+this.getCss(size-d[0],1,hColor,1,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[1],2,hColor,3,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[2],2,hColor,5,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[2],1,hhColor,5,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[2],2,Color,5,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[1],2,Color,3,hhColor)+'"></div>'
    +'<div style="'+this.getCss(size-d[0],1,dColor,1,hhColor)+'"></div>'
    +'<span style="position:relative;top:'+(-11-IE)+'px;left:'+(((this.statictext)?this.dsize*sizep:size)+5)+'px;font:7pt verdana">'+text+'</span>'

   if(limit)
   if(this.dsize * sizep > limit) {
    obxStickGraphRunObject=this;
    setTimeout('obxStickGraphRunObject.actDraw("'+id+'",'+(limit+this.step)+');',this.speed);
   }else{
    setTimeout('obxStickGraphRunObject.actDraw("'+id+'");',this.speed);
   }

  }


 }


 grp=new obxStickGraph(200) ;
  grp.add('id1',100,'F8CF7B');
  grp.add('id2',200,'#D1D3D3');

  grp.add('id3',500,'FDB480','# hit ($ %)');
  grp.add('id4',300,'3399ff','# hit');
 </script>
 <style>
  td {font: 11px gulim}
 </style>

</head>


<body>

<table>
<tr>
 <td>
  항목 1
 </td>
 <td width="300">
  <script>grp.draw('id1',true);</script>
 </td>
 <td>
  동적 드로우, 기본출력
 </td>
</tr>

<tr>
 <td>
  항목 2
 </td>
 <td>
  <script>grp.draw('id2');</script>
 </td>
 <td>
  정적 드로우, 기본출력
 </td>
</tr>

<tr>
 <td>
  항목 3
 </td>
 <td>
  <script>grp.draw('id3',true);</script>
 </td>
 <td>
  동적 드로우, "# hit" 출력
 </td>
</tr>

<tr>
 <td>
  항목 4
 </td>
 <td>
  <script>grp.draw('id4');</script>
 </td>
 <td>
  정적 드로우, "# hit" 출력
 </td>
</tr>

</table>


<xmp>

생성 :

 <script>

  grp=new obxStickGraph(200) ;
  //젤 긴게 200px 만하게 grp란걸 만든다 (그래프 길이"만" 입니다, 글자 때문에 더 깁니다.)

  grp.add('id1',100,'F8CF7B');
  //그 grp로 명령을 내립니다.
  // "id1" 아이디로 100이란 값을 넣어준다 이때 색은 F8CF7B로
  //색은 조금 어두운 부분(그래프 밑부분)색입니다

  grp.add('id2',200,'#D1D3D3');

  grp.add('id3',500,'FDB480','# hit ($ %)');
  // 4번째 인자는 출력 문잡니다. #은 값으로 $는 퍼센트값으로 바뀝니다

  grp.add('id4',300,'3399ff','# hit');

 </script>


출력 :

 <script>

  grp.draw('id1',true);
  //아이디에 해당되는 그래프를 출력합니다,
  //이때 두번째 인자는 옵션이고 true로 해주면 동적으로 그립니다.(비추-_-;)

 </script>


동적옵션코드 :

 <script>
  grp.step = 3;
  grp.speed = 10;
  //그려주기(grp.draw()) 전에 이런 코드를 넣어주셔도 됩니다.
  //동적으로 그릴때 쓰는 옵션인데
  //스텝은 한번에 늘어나는 길이(픽셀)고, 스피드는 한번 돌아가는;; 속도입니다. 적을수록 빠르고 0이 최소값입니다.

 +yser님의 조언

  grp.statictext=true
  //텍스트 표시를 정적으로 합니다.
 </script>


</xmp>




</body>
</html>

Posted by 1010
90.개발관련문서2008. 8. 12. 16:15
반응형

00 오른쪽 마우스 메뉴에 도스창 열기 추가하기

원문 : http://kkamagui.springnote.com/pages/392898

참고 : http://zextor.tistory.com/2669790

 

들어가기 전에...


1.추가 방법

DOS 시절부터 컴퓨터를 이용하였거나 프로그래머 개발자의 경우 아직도 Dos이용하고 있습니다.

하지만 DOS를 실행하고 원하는 폴더에 접근하기 위해서는 원도우키 + R  또는 [시작]→[실행] 후 “ CMD “ 를 입력 후 도스창이 실행되면 CD 명령어를 이용하여 원하는 폴더에 이동할 수 있습니다.

레지스트리를 수정하여 탐색기를 이용하여 먼저 가고자 하는 폴더에 접근 후 DOS를 실행 하여 바로 해당 폴더의 경로로 연결되어 사용하기 편리합니다.


1. [시작]→[실행]에서 “regedit “를 입력하고 레지스트리 편집기를 실행한 후, 다음 키 값을 찾는다. HKEY_CLASSES_ROOT\Directory\shell

2. Shell 키 위에서 마우스 오른쪽 마우스 클릭(또는 shell 키 선택 후 오른쪽 공백에서 오른쪽 마우스 클릭) 후 [새로 만들기(N)] → [키]를 선택합니다.

3. 새로운 키의 이름을 DOS(이름은 원하시는 이름으로 하셔도 됩니다.)로 수정합니다.

4. 만들어진 DOS 키를 클릭 후 오른쪽의 기본값을 더블 클릭하여 오른쪽 마우스에 표시될 이름을 입력하여 주십시오. ( 예를 들어 도스창이라 입력합니다. )

5. 다음 새로 만들어진 DOS에서 Shell 과 마찬가지로 새로운 키를 만들어 Command 이름으로 수정합니다. ( DOS 와는 달리 반드시 command 이름으로 하여야 합니다. )

6. 만들어진 Command 키를 클릭 후 오른쪽의 기본값을 더블 클릭하여 cmd.exe /k cd "%1" 이라는 문자열을 입력하여 주십시오.

7. 컴퓨터를 재 시작하여 탐색기 실행 후 가고자 하는 폴더를 선택 후 오른쪽 마우스 클릭 후 도스창을 클릭하시면 해당 폴더의 경로로 도스창이 열리는 것을 확인 할 수 있습니다.


본 자료는 (주)웰비아닷컴 의 커뮤니티 - 활용팁 에서 스크랩한 것입니다.


이 자료 외에도 많은 정보가 있으니 필요하신 분은 직접 방문해 보시길 바랍니다.

본 페이지에서는 재부팅 후에 사용할 수 있다고 기술하였지만 바로 사용하고 싶으시면 아래와 같이 해주시면 됩니다.


1. '시작' 이 있는 작업표시줄에 마우스 오른쪽 버튼을 눌러 작업관리자를 띄움니다.

2. 프로세스 탭에서 explorer.exe 를 선택하여 프로세스 끝내기를 합니다. 경고가 뜰 경우 그냥 "예" 를 선택하십시오.

3. 그럼 밑에 작업표시줄이 없어질 것 입니다.

4. 그럼 아까 작업관리자의 응용 프로그램 탭으로 이동한 후 새 작업을 클릭합니다.

5. 열기 옆에 있는 입력창에 "explorer" 를 입력합니다.

6. 이제 탐색기의 오른쪽 메뉴에 DOS를 사용할 수 있습니다.

Posted by 1010
반응형

Network Monitor (Netmon)는 네트워크 프로토콜 트래픽 분석 유틸리티입니다.
Network Monitor 3.1 을 사용해서 네트워크 프로토콜을 수집하는 몇 가지 방법을 정리하였습니다.

Netmon 2.x와 3.x의 가장 큰 변화는 캡쳐 필터링이라고 생각합니다.
캡쳐 필터링 옵션 변화에 따른 패킷 수집 방법을 사례 별로 아주 기본적인 테스트를 해 봅니다.

일반적으로 Netmon 트래픽을 수집하기 위해서는 Client와 Server 측에서 함께 로그 수집을 해야
문제 해결을 위한 보다 정확한 데이터를 얻을 수 있습니다.


아래 그림은 Netmon 3.1 인터페이스입니다.
Catpure Filter 제어, Frame Summary, Frame Details, Hex Details 값을 바로 확인할 수 있습니다.

Capture Filter 에 Filter 구문을 작성한 뒤 반드시 Verify, Apply 하여 체크 및 필터를 적용해야 합니다.

사용자 삽입 이미지




























[환경]
Server : 192.168.0.100
Client : 192.168.0.133


Case 1. 특정 Source IP Address(192.168.0.133) 에서 유입되는 Packet 확인

IPv4.SourceAddress = 192.168.0.133

Client 에서 Server 로 터미널 서비스 접속을 시도 하였습니다.

사용자 삽입 이미지


























Case 2. TCP Source Port 가 3389인 frame 확인

Tcp.Port == 3389

Client 에서 Server 로 터미널 서비스 접속을 시도 하였습니다.

사용자 삽입 이미지


























Case 3. 3389 port Packet은 캡쳐하지 않음

Tcp.Port != 3389

터미널 서비스 3389 포트 커넥션을 시도 하였으나 아래 그림과 같이 캡쳐되지 않습니다.

사용자 삽입 이미지



























Case 4. ARP Packet 찾기

ARP
사용자 삽입 이미지



























Case 5. Source Port 1096, Destination Port 3389 과 일치하는 Packet

Tcp.SrcPort == 1096 AND Tcp.DstPort == 3389
사용자 삽입 이미지



























Case 6. Command 명령을 이용한 Packet 수집
C:\Program Files\Microsoft Network Monitor 3\> nmcap /network * /capture /File client.cap:10M

수집이 완료되면 Ctrl + C 를 입력하여 수집을 중지합니다.

사용자 삽입 이미지
























자, 수집을 하셨으니 이제 분석을 해야겠죠?
누가하죠? 어떻게?.... What?
약은 약사에게 패킷 분석은 과장님에게 ^^;


[참고자료]
Microsoft Network Monitor 3.1
http://www.microsoft.com/downloads/details.aspx?familyid=18b1d59d-f4d8-4213-8d17-2f6dde7d7aac&displaylang=en

The Basics of Reading TCP/IP Traces
http://support.microsoft.com/kb/169292/en-us

Explanation of the Three-Way Handshake via TCP/IP
http://support.microsoft.com/kb/172983/en-us

Into to Filtering with Network Monitor 3.0
http://blogs.technet.com/netmon/archive/2006/10/17/into-to-filtering-with-network-monitor-3-0.aspx


작성자 : Lai Go / 작성일자 : 2008.07.01

Posted by 1010
카테고리 없음2008. 8. 12. 15:30
반응형
작성자 : 기술지원부 김 삼 수 <kiss@nextline.net>
 
아파치 2.X 버전에서 mod_cband를 이용한 트래픽관리
 
mod_cband 이란?
Apache에서 개별홈페이지의 일hit수 제한 및 트래픽을 관리하기 위해 사용하는 모듈로서apache 2.x버전에서 사용할 수 있으며 apache를 Dos방식과 Static방식 중 어느 방식으로 설치했느냐에 따라 적재방법 또한 다릅니다. DOS방식의 mod_cband 모듈적재 및 설정 방법을 알아보도록 하겠습니다.
 
[주요기능]
Apache2용 가볍운 트래픽제한 모듈
   * 사용자별 대역폭제한 기능
   * 가상호스트별 대역폭 제한 기능
   * 목적지별 대역폭 제한 기능
   * 제한기능:
         o 모든사용자 대역폭 제한
         o 다운로드 속제 제한
         o 초당요청수 제한
         o 아이피대역별 제한
   * Support for virtualhosts
   * Support for defined users
   * 제한결과 웹을 통한 확인 (/cband-status)
   * 각 사용자별 제한 결과 확인(/cband-status-me)

 
( 1 ) 다운로드

http://freshmeat.net/redir/mod_cband/60304/url_tgz/mod-cband-0.9.7.5.tgz
http://cband.linux.pl/downloads
 
리눅스 쉘 명령어
 
① tar zxvf mod-cband-0.9.7.5.tgz 파일을 다운로드 합니다.
wget은 웹에서 자동적으로 파일을 받아오는데 사용되는 유틸리티이며 HTTP, HTTPS, FTP 프로토콜을 지원합니다.
[root@nextline bin]# wget
http://freshmeat.net/redir/mod_cband/60304/url_tgz/mod-cband-0.9.7.5.tgz
 

 
( 2 ) 압축해제
 
[tar 명령어 옵션]

tar 명령어는 파일을 묶거나 풀 때 사용되는 리눅스 명령어 입니다.
c : tar 파일을 생성할 때 사용합니다.(여러 개의 파일을 하나의 파일로 묶을 때)
v : 묶을 때나 풀어줄 때 파일들의 내용을 자세하게 보여줍니다.
z : gzip과 관련하여 압축이나 해제를 한꺼번에 하려고 할 때 사용합니다.
x : 주어진 이름의 파일에 대하여 추출합니다.
사용법 : tar [옵션] 파일명
 
리눅스 쉘 명령어

① 다운로드된 mod-cband-0.9.7.5.tgz 파일의 압축을 해제 합니다.
[root@nextline bin]# tar zxvf mod-cband-0.9.7.5.tgz
 


( 3 ) 컴파일
 
리눅스 쉘 명령어

① 압축 해제한 mod-cband-0.9.7.5 디렉토리로 이동 합니다.
[root@nextline bin]# cd mod-cband-0.9.7.5

② ./configure 명령을 실행합니다.
./configure 명령은 프로그램을 설치하기 위해 환경설정을 하는 것으로 ./configure 후Makefile파일이 생성됩니다. 모듈을 적재하기 위해 컴파일을 합니다.
[root@nextline mod_throttle-3.1.2]# ./configure
 

 
( 4 ) make
 
리눅스 쉘 명령어

① make 명령을 실행합니다.
make 명령은 대상 디렉토리의 Makefile이라는 이름을 가지고 있는 파일을 보고, 거기에 설정되어 있는 컴파일 명령을 shell을 통해서 실행하는 명령입니다. ./configure 작업에 의해 생성된 Makefile을 참조하게 되며 소스코드를 실제로 컴파일해서 bibary 파일을 생성합니다.
[root@nextline mod_throttle-3.1.2]# make
 
 
 
( 5 ) make install

① make install 명령을 실행합니다.
make 명령에 의해 생성된 binary 파일을 지정된 디렉토리로 이동시켜주며 실제 프로 그램 설치 작업이 이루어집니다.
[root@nextline mod_throttle-3.1.2]# make install
 
 
   
( 6 ) mod_cband.so 파일생성 확인

① ls 명령어를 이용하여 아파치 모듈들이 위치한 modules 디렉토리에 mod_cband.so 파일이 생성 되었는지 확인합니다.
[root@nextline mod-cband-0.9.7.5]# ls /usr/local/apache/modules/
 
 
 
( 7 ) 모듈적재 확인

 
[vi 에디터 사용법]
 
사용형식 : vi [옵션] [생성할 파일명/편집할 파일명]
 
vi 에디터는 입력모드, 명령모드, 실행모드로 구분됩니다.

입력모드 : vi 편집화면에서 문자를 입력할 수 있는 모드로서 입력모드로 진입하기 위해서는 i, a, o, I, A, O, R등이 있습니다. 즉 초기 vi 편집기 모드는 명령어 모드로 진입을 하기때문에 문자를 입력하기 전에 앞의 단축키중 하나를 먼저 입력해야 원하는 문자를 입력할 수 있습니다.
 
명령모드 : 커서이동/문자삭제/문자(열)교체/문자열검색 등을 할수 있는 모드로서 입력모드에서 편집이 완료되면 Esc키를 눌러 명령모드로 진입하면 됩니다.
 
실행모드 : 특별한 명령어를 실행하는 모드로서 명령어모드에서 ":"(콜론)를 누르면 vi 화면 하단 좌측에 vi 특수명령어를 입력할 수 있습니다.
 
실행모드의 일반적으로 쓰이는 특수 명령어
q : 수정 작업이 이루어지지 않은 상태에서 vi 편집기에서 빠져나옵니다.
q! : 수정 작업이 이루어진 부분을 적용시키지 않고 vi 편집기를 강제로 빠져나옵니다.
w : 수정된 작업을 저장합니다.
wq : 수정된 작업을 저장하고 vi 편집기에서 빠져나옵니다.
 
초기 명령어모드 -> 입력모드진입 -> 편집 -> 명령어모드 -> 실행모드 -> 종료
 
① httpd.conf파일에 모듈이 등록되었는지 확인합니다.
[root@nextline mod-cband-0.9.7.5]# vi /usr/local/apache/conf/httpd.conf
LoadModule cband_module         libexec/mod_cband.so
 

 
( 8 ) httpd.conf파일의 메인 환경설정

메인 설정 부분은 가상호스트에도 공통으로 적용되는 설정으로 mod_cband 모듈을 적용시키기 위해 아파치 환경 설정파일인 httpd.conf을 수정합니다.
 
① vi 에디터를 이용하여 httpd.conf파일을 엽니다.
[root@nextline mod-cband-0.9.7.5]# vi /usr/local/apache/conf/httpd.conf
 
② 아파치에 cband 모듈을 적용시키기 위해 다음 라인을 추가 합니다.
 
<IfModule mod_cband.c>
        <Location /cband-status>
                SetHandler cband-status
        </Location>
        <Location /cband-status-me>
                SetHandler cband-status-me
        </Location>
        <Location /~*/cband-status-me>
                SetHandler cband-status-me
        </Location>
        <Location /cband-status>
                Order deny,allow
                Deny from all
                Allow from all
        </Location>
</IfModule>
 
 
  ③ 트래픽 관리자모드 접근 ip설정

<Location /cband-status>
                           Order deny,allow
                           Deny from all
                           Allow from all
        </Location>
 
위 설정은 throttle로 분석된 일hit 및 일 트래픽 제한에 대하여 상황페이지를 볼 관리자페이지에 대한 접속제한 설정입니다. 즉 관리자PC 한곳에서만 분석된 결과페이지를 볼 수 있도록 하려면 아래와 같이 수정합니다.
 
관리자 PC 아이피 : XXX.XXX.XXX.XXX

<Location /cband-status>
                           Order deny,allow
                           Deny from all
                           Allow from XXX.XXX.XXX.XXX
         </Location>

 
( 9 ) 가상호스트 환경설정
 
개별 홈페이지 트래픽 현황 및 관리를 하기 위해서는 httpd.conf <Virtual Hosts>부분에 홈페이지 별로 설정을 하여야 합니다.
 
nextline.co.kr 도메인에 하루에 300M(300*1024*1024byte)의트래픽을 제공하는 설정입니다. bit로 따지면, 2.4Gbit/일 트래픽을 제공하는 것입니다. 만약 하루에 300M를 초과했다면, 503 에러 페이지가 뜨게 됩니다.
 
① nextline.co.kr 도메인을 가진 가상호스트를 추가 하도록 하겠습니다.
 
<VirtualHost xxx.xxx.xxx.xxx>
DocumentRoot /home/nextline/public_html
Servername nextline.co.kr
ServerAlias www.nextline.co.kr
CBandLimit 300Mi
CBandPeriod 1D
</VirtualHost>

 

② httpd.conf 설정 후 적용시키기 위해 apache를 재 시작 시켜 줍니다.
[root@nextline mod-cband-0.9.7.5]# /usr/local/apache/bin/apachectl restart

 
( 10 ) 서버전체 cband 상황보기 (관리자모드)
 
일hit수 일전송량의 제한을 웹브라우즈로 확인하기 위하여 다음과 같은 URL로 확인하도록 하겠습니다. 먼저 서버전체의 제한사항을 관리자가 확인하기 위한 예입니다.
cband 페이지는 기본 15초마다 리플레쉬 합니다.
 
① 주도메인이 nextline.co.kr로 설정된 예입니다.
확인하는 방법 :http://IP주소/cband-status
 

② 개별사이트 cband 상황보기 (사용자모드)
확인하는 방법 : http://nextline.co.kr/cband-status-me
 

 
( 11 )지시자 및 단위설명

  단위
  전송속도 단위
  kbps, Mbps, Gbps - bits per second:1024, 1024*1024 , 1024*1024*1024 bps
  kb/s, Mb/s, Gb/s - bytes persecond:  1024, 1024*1024, 1024*1024*1024 b/s
  기본 : kbps
 
  트래픽 쿼터 단위
  K, M, G - bytes: 1000, 1000*1000,1000*1000*1000 bytes
  Ki, Mi, Gi - bytes: 1024, 1024*1024,1024*1024*1024 bytes
  기본 : K
 
  시간(기간) 단위
  S, M, H, D, W - 초, 분, 시간, 일, 주
  기본 : S
 
  지시자들
  이름 : CBandDefaultExceededURL
  설명 : 제한을 초과했을때보여줄 URL  (지정하지 않으면, 503 에러 페이지)
  문맥 : Serverconfig
  문법 :CBandDefaultExceededURL URL
 
  이름 : CBandDefaultExceededCode
  설명 : 제한을 초과했을시 보여줄 에러코드
  문맥 : Server config
  문법 :CBandDefaultExceededCode HTTP_CODE
  예제 :CBandDefaultExceededCode 509 
 
  이름 : CBandScoreFlushPeriod
  설명 : scoreboard 파일에기록할 요청수, mod_cband 의 성능에 영향을 준다.
  기본값 : 1
  문맥 : Server config
  문법 :CBandScoreFlushPeriod 요청수
  예제 :CBandScoreFlushPeriod 100 (매 100번의 요청에 한번씩 scoreboard 파일에 기록)
 
 
  이름 : CBandSpeed
  설명 : 가상호스트 도메인의 최대 속도,요청수, 접속수  설정
  문맥 :<Virtualhost>
  문법 : CBandSpeed kbpsrps max_conn
       kbps - 초당 최대 전송속도
       rps - 초당 최대 요청수
       max_conn - 최대 동시 접속수
       예제 : CBandSpeed 102410 30
       최대 1024kbps전송속도로 제한, 초당 10개의 요청 처리, 동시 접속을 30개로 제한.
 
  이름 : CBandRemoteSpeed
  설명 : 접속자(IP)의 최대속도, 요청수, 접속수 제한 (접속자당 설정)
  문맥 :<Virtualhost>
  문법 : CBandRemoteSpeedkbps rps max_conn
        kbps - 초당 최대 전송속도
        rps - 초당최대 요청수
        max_conn - 최대 동시 접속수
        예제 : CBandRemoteSpeed20kb/s 3 3
        접속자(ip)에대해 최대 20kb/s , 초당 3개의 요청, 동시 접속 3개로 제한.
 
  이름 : CBandClassRemoteSpeed
  설명 : 정의한 class(ip 범위)에대해 최대속도, 요청수, 접속수 제한
  문맥 :<Virtualhost>
  문법 :CBandClassRemoteSpeed class_name kbps rps
        class_name - 이미 정의한 클래스 이름 (IP범위)
        kbps - 초당 최대 전송속도
        rps - 초당 최대 요청수
        max_conn - 최대 동시 접속수
  예제 : <CBandClassgooglebot_class>
          CBandClassDst 66.249.64/24
          CBandClassDst 66.249.65/24
          CBandClassDst 66.249.79/24
         </CBandClass>
          CBandClassRemoteSpeedgooglebot_class 20kb/s 2 3
          위에서 정의한클래스(googlebot_class)의 요청에는 20kb/s 의 전송속도,
         초당 3개의 요청, 동시 접속 3개로 제한.
 
  이름 : CBandRandomPulse
  설명 : 속도 제한을 위해서 임의의파형을 생성한 다음 처리하는 mod_cband의 처리 방법이다. 부하가 많을 때는 자동 Off된다.

  문맥 : Global
  문법 : CBandRandomPulseOn/Off
 
  이름 : CBandLimit
  설명 : 제한할 전송량을 설정한다.(기간은 CBandPeriod 에서 설정)
  문맥 :<Virtualhost>
  문법 : CBandLimit limit
        limit - 전송량, 사용단위: K (kilo), M (mega), G (giga), Ki (kibi), Mi (mebi),Gi (gibi)
  예제 : CBandLimit 10M
        전송양을 10M(10*1000*1000bytes)로 제한한다.
        CBandLimit 10Mi
        전송양을 10M(10*1024*1024bytes)로 제한한다.
 
  이름 : CBandClassLimit
  설명 : 정의한 class(ip범위)에대해 제한할 전송량 설정.
  문맥 :<Virtualhost>
  문법 : CBandClassLimitclass_name limit
        class_name - 이미 정의한 클래스 이름(ip범위)
        limit - 전송량, 사용단위: K (kilo), M (mega), G (giga), Ki (kibi), Mi (mebi),Gi (gibi)
 
  이름 : CBandExceededURL
  설명 : 제한을 초과했을시 보여줄URL, 지정하지 않으면 503 에러 발생
  문맥 :<Virtualhost>
  문법 : CBandExceededURLURL
 
  이름 : CBandExceededSpeed
  설명 : 전송양을 초과했을시 , 전송속도제한 설정.
  문맥 :<Virtualhost>
  문법 :CBandExceededSpeed kbps rps max_conn
        kbps - 초당 최대 전송속도
        rps - 초당 최대 요청수
        max_conn - 최대 동시 접속수
  이름 : CBandScoreboard
  설명 : 가상호스트의 scoreboard파일 지정. (성능향상을 위해 필요)
  문맥 :<Virtualhost>
  문법 : CBandScoreboardpath
        (path는 아파치(nobody또는 apache)권한으로 쓰기 가능해야 함)
 
이름 : CBandPeriod
  설명 : 용량제한기간(이 기간이 지나면,측정되었던 용량은 지워진다.)
  문맥 :<Virtualhost>
  문법 : CBandPeriod period
        period - 사용단위: S (초), M (분), H (시간), D (일), W (주)
  예제 : CBandPeriod1W  (1주일)
        CBandPeriod 14D  (14일)
        CBandPeriod 60M  (60분)
 
  이름 : CBandPeriodSlice
  설명 : 기간이 길때는 나눌 기간을명시한다.
  기본값 : slice_len = limit
  문맥 :<Virtualhost>
  문법 : CBandPeriodSliceslice_length
  예제 : CBandLimit 100G
        CBandPeriod 4W
        CBandPeriodSlice 1W
        4주는 1주일 단위로 나뉜다(4W/1W = 4). 용량은 100G/4=25G
        1주에 25G, 2주째 50G 이렇게 나눠 처리 된다.
 
  이름 : <CBandUser>
  설명 : 새로운 cband 가상 사용자설정
  문맥 : Server config
  문법 : <CBandUseruser_name>
 
  이름 : CBandUserSpeed
  설명 : cband 가상 사용자의 속도,요청수, 동시 접속수 제한
  문맥 : <CBandUser>
  문법 : CBandUserSpeedkbps rps max_conn
        kbps - 초당 최대 전송속도
        rps - 초당 최대 요청수
        max_conn - 최대 동시 접속수
  예제 : CBandUserSpeed100kb/s 10 5
 
 
  이름 : CBandUserLimit
  설명 : cband 가상 사용자의 저송용량 제한.
  문맥 : <CBandUser>
  문법 : CBandUserLimitlimit
     limit - 사용용량, 사용단위: K (kilo), M (mega), G (giga), Ki (kibi), Mi (mebi),Gi (gibi)
  예제 : CBandUserLimit 10M
        CBandUserLimit 10Mi
 
  이름 : CBandUserClassLimit
  설명 : cband 가상 사용자의 정의한class(ip범위)에 대해 제한할 전송량 설정
  문맥 : <CBandUser>
  문법 :CBandUserClassLimit class_name limit
        class_name - 지정한 class(IP범위)이름
       limit -사용용량, 사용단위: K (kilo), M (mega), G (giga), Ki (kibi), Mi (mebi),Gi (gibi)
 
  이름 : CBandUserExceededURL
  설명 : cband 가상 사용자의,제한을 초과했을시 보여줄 URL,
        지정하지 않으면 503 에러 발생 ( 가상호스트에서 )
  문맥 : <CBandUser>
  문법 :CBandUserExceededURL URL
 
  이름 : CBandUserExceededSpeed
  설명 : cband 가상 사용자의,전송양을 초과했을시 , 전송속도 제한 설정.
  문맥 : <CBandUser>
  문법 : CBandUserExceededSpeed kbps rps max_conn
        kbps - 초당 최대 전송속도
        rps - 초당 최대 요청수
        max_conn - 최대 동시 접속수
 
  이름 : CBandUserScoreboard
  설명 : cband 가상 사용자의,scoreboard 파일 지정.
  문맥 : <CBandUser>
  문법 : CBandUserScoreboard path
        (path는 아파치(nobody또는 apache)권한으로 쓰기가능해야 함)
 
  이름 : CBandUserPeriod
  설명 : cband 가상 사용자의, 용량제한기간(기간이 지나면, 측정되었던 용량은 지워진다.)
  문맥 : <CBandUser>
  문법 : CBandUserPeriodperiod
        period - 사용단위: S (초), M (분), H (시간), D (일), W (주)
  예제 : CBandUserPeriod 1W
        CBandUserPeriod 14D
        CBandUserPeriod 60M
 
  이름 : CBandUserPeriodSlice
  설명 : cband 가상 사용자의,기간을 나눌 기간 명시
  기본값 : slice_len = limit
  문맥 : <CBandUser>
  문법 :CBandUserPeriodSlice slice_length
  예제 : CBandUserLimit100G
        CBandUserPeriod 4W
        CBandUserPeriodSlice 1W
        4주는 1주일 단위로 나뉜다(4W/1W = 4). 용량은 100G/4=25G
        1주에 25G, 2주째 50G 이렇게 나눠 처리 된다.

 
( 12 ) 개별홈페이지 cband 정책 적용 예
 
① 자료실 속도제한
   nextline.net 도메인에 대해서 속도를 1024kbps로 제한하며, 초당 10번의 연결, 동
   시 접속자를 30으로 제한하는 예제입니다.
  
<VirtualHost xxx.xxx.xxx.xxx>
DocumentRoot /home/nextline1/public_html
Servername nextline.net
ServerAlias www.nextline.net
CBandSpeed 1024 10 30
CBandRemoteSpeed 20kb/s 3 30
</VirtualHost>
 
                         
 
 
확인하는 방법 : http://nextline.net/cband-status-me
 
 
② 사용자 일트래픽 제공 및 초과시 연결수 제한 nextline.com 도메인에 대해 하루에 100Mbyte의 트레픽을 제공하며, 100M를 초가했다면, 속도를 128bps로 제한, 초당 5번의 연결, 동시접속자를 15로 제한하는 예제입니다.
 
<VirtualHost xxx.xxx.xxx.xxx>
DocumentRoot /home/nextline2/public_html
Servername nextline.com
ServerAlias www.nextline.com
CBandLimit 100Mi
CBandExceededSpeed 128 5 15
CBandPeriod 1D
</VirtualHost>
 

 
확인하는 방법 : http://nextline.com/cband-status-me

 
   
( 13 ) 그 외 적용 예를 들어보겠습니다.
 
① 한 사용자에 여러 도메인을 운영할 때 입니다.
위 설정은 nextline이라는 가상 사용자를 지%E

Posted by 1010
98..Etc/Etc...2008. 8. 12. 15:09
반응형

준비물:

utra edit - 에디터 프로그램입니다. hex에디트까지 가능하게 해줍니다.
qpst - 꼭 qpst가 아니여도 좋습니다. 핸드폰에 파일을 다운로드, 업로드만 가능하게 해주면 됩니다.
공학계산기 - 윈도우의 계산기나 손에 들고 있는 계산기면 OK. 윈도우의 계산기일경우 보기->공학용 을 찾아서 클릭해주시면 됩니다.


1. 이론

hex란 무엇인가?

사용자 삽입 이미지

hex란 hexagonal 의 단축형으로써 16진법이라는 뜻입니다. 우리들이 흔히 쓰는 10진수는 decimal, 2진수는 binary, 8진수는 octal 이라고 표시합니다.  간단하게 축약해서 16진수-hex, 10진수-dec, 2진수는 bin, 8진수는 oct 입니다.10진수는 0부터 9까지 가 1의 자리를 나타내며, 9에서 1이 더해지는순간 십의 자리에는 1이 일의 자리에는 0이 오는 10이 됩니다. 2진수는 0과 1 뿐입니다. 1에서 1이 더해지는순간 십의 자리에 1이 오고 일의 자리에 0이 와서 10이 됩니다. 즉 0(0), 1(1), 10(2) 입니다. 괄호안은 10진수로 읽었을때입니다. 8진수도 0부터 8까지로 이루어져있습니다. 마지막으로 16진수는 0부터 F까지 이루어져 있습니다. 이는 0,1,2,3,4,5,6,7,8,9,A(10),B(11),C(12),D(13),E(14),F(15)로 이루어져 있는 것입니다. 즉 9라는 숫자에서 1이 더해져도 십의 자리에 1이 오지않고 A가 됩니다. 이후로 B, C, D, E, F로 넘어가서 F에서 1이 더해지는순간 십의 자리에 1이 오고 일의 자리에 0이 와서 10이라는 숫자가 됩니다. 컴퓨터에 데이터가 저장될때에는 2진수와 16진수로 저장이 됩니다. 우리가 2진수는 123라는 숫자를 입력하게 되면 1111011이라는 숫자로 저장이됩니다. 16진수의 경우에는 7B라는 숫자로 저장이 됩니다. 하지만 2진수는 컴퓨터가 빠르게 이해는 할지몰라도 사람이 보고 읽고 이해하기엔(이를 가독성이라고 합니다) 너무 어렵죠. 그때문에 16진수코드를 쓰게 되는겁니다.


게임에서 hex조작은 어떻게 하나?

파일을 hex코드로 열어서 해당관련 숫자를 찾아서 숫자를 바꾸어주시면됩니다. 엉뚱한 코드를 손을 대셨다가 게임이 엉망이 되는 경우도 많으니 조심하셔야합니다. 이제 실습에 들어가보겠습니다.

사용자 삽입 이미지

일단 QPST로 접속을 합니다. 그리고 원하는 게임폴더로 찾아갑니다. 저의 경우에는 제일 만만한 판타지포에버2를 예로 들겠습니다. (참고로 저는 SKT통신사며 쓰이는 핸드폰은 MS500입니다.)

사용자 삽입 이미지


게임이 저장된 forever0.db 파일을 다운받습니다.

사용자 삽입 이미지


만약 ultra edit가 hex로 열지 못한다면 편집->hex기능을 찾아서 클릭해주시면 됩니다.

그리고 게임에 접속해서 케릭터의 골드, 레벨, 경험치, 스킬포인트, 찍은 스킬 등등을 공책이나 컴퓨터의 메모장에다가 잘 적어둡니다.

저의 경우 골드가 15032216골드 입니다. 약 1500만골드군요. 이미 이전에 손을 써두었기에 저런 골드가 가능해졌습니다.

사용자 삽입 이미지


자 15032216이라는 숫자를 계산기에 입력을 합니다. 그리고 이상태로 Hex를 클릭해보세요.

사용자 삽입 이미지


E55F98이라는 숫자로 바뀝니다. 앞으로 숫자는 2개씩 끊어서 보기로 합시다.
E5 5F 98 입니다. 이숫자를 잘 기억해두었다가, 아까 ultra edit로 연 forever0.db파일에서 맞는 숫자를 찾아봅니다.

사용자 삽입 이미지


아! 찾았군요! E5 5F 98 잘보입니다. 이 숫자를 FF FF FF 로 변경하시면 금액이 변경됩니다. FFFFFF라는 숫자는 10진수로 16777215입니다. 즉 약 1600만골드입니다.

이번엔 케릭터의 레벨을 찾아가봅시다. 저의 경우 케릭터의 레벨은 81입니다. 81이라는 dec숫자를 계산기로 통해서 hex로 바꾸면 51이라는 숫자가 됩니다. 이또한 찾아봅시다.

사용자 삽입 이미지


아! 또 찾았군요! 이 숫자를 FF로 바꾸시면 255레벨로 바뀝니다. 하지만 일반적인 게임들이 보통 99레벨이 끝인것을 감안해봐서 99레벨로 바꾸기로 합시다. 99라는 dec숫자를 hex로 바꾸어보면 63입니다. 아까 찾은 51이라는 숫자 대신 63을 바꾸어 적습니다.

그리고 파일을 저장해서 다시 qpst로 핸드폰으로 저장합니다.

그리고 게임을 실행해보시면??

와우!! 놀랍네요. 전부적용이 되어있습니다. 이제 레벨노가다와 골드노가다는 안해도 되겠군요.

기타 제가 찾아서 쓰고 있는 주소들은

골드:0번     b,c,d라인 

레벨: 1번 - 200번 4
      2번 - 240번 7
      3번 - 280번 a

스킬 : 1번 - 230  0 부터 240 0 까지
       2번 - 270  3 부터 280 3 까지
       3번 - 2b0  6 부터 2c0 6

exp : 200 a b
      240 d e
      290 0 1

스킬포인트 : 240 5
            280 8
             2c0 b

입니다.

사용자 삽입 이미지


위에 제가 적은것을 보는방법은 간단합니다. hex코드에서 왼쪽 끝을 보시면 해당 00000000h 라는 주소들과 위에는 0부터 f까지 주소가 있습니다. 이것을 찾아서 보시면 됩니다. 예를 들어 골드의 경우 00000000h의 주소의 b,c,d라인에 있다는 소리입니다.

이것으로 간단한 hex조작을 해보았습니다. 여러분들도 쉽게 하실수 있기를 기대하겠습니다. 만약 내용이 어렵다면 댓글을 주시면 빠르게 확인해서 답을 드리겠습니다

Posted by 1010
반응형
EXE, DLL, SFX 등의 PE 파일을 분석해서 사용한 압축기, 암호화기, 컴파일러 정보를 표시하는 프로그램입니다

PE 파일이 어떤 컴파일러로 컴파일되었고,어떤 패킹 되었는지 알수있게 하는 프로그램입니다.

본 프로그램은 사용에 아무런 제한이 없는 프리웨어입니다.


EXE, DLL, SFX 등의 PE 파일을 분석해서 사용한 압축기, 암호화기, 컴파일러 정보를 표시하는 프로그램입니다. PE 파일 내의 470 개 이상의 서로 다른 기호들을 감지할 수 있습니다.

파일분석, 어셈블링, 디어셈블링 등을 하는 분들께 필요합니다.




  • 알려지지 않은 파일이나 변경된 파일을 감지하기 위한 "Advanced" 감지 모드
  • 쉘 확장, 커멘드 라인 지원, 항상 위에 옵션, 드래그 & 드롭 등의 인터페이스 제공
  • 동시에 여러 파일 또는 여러 디렉터리를 스캔할 수 있음
  • Task Viewer나 컨트롤러 제공
  • Generic OEP Finder나 Krypto ANALyzer 와 같은 플러그인을 사용할 수 있는 인터페이스
  • 발견적(Heuristic) 스캐닝 옵션
  • PE details, Imports, Exports, TLS viewers 제공
  • 빠른 디셈블러 내장
  • 헥스 뷰어 내장

    사용자 삽입 이미지



  • Posted by 카이란
    Posted by 1010
    반응형
    사용자 삽입 이미지
    사용자 삽입 이미지
    사용자 삽입 이미지
    사용자 삽입 이미지
    사용자 삽입 이미지
























    본 프로그램은 사용에 아무런 제한이 없는 프리웨어입니다.

    사용자 PC에 설치된 알수없는 프로그램들의 정보를 제공하며 불필요한 프로그램의 언인스톨을 도와 드립니다.


    제작자 설명
    애니그레이는 사용자 PC에 설치된 프로그램 정보 제공 및 알수 없는 프로그램을 유저가 쉽게 확인 할수 있도록 제공하며 불필요한 프로그램을 손쉽게 삭제 할수 있도록 도와 드립니다.

    인터넷의 발전으로 인터넷 웹서핑이 일반화 되어 있는 요즘, 개인 컴퓨터에는 사용자가 알수 없는  Atcive-X ,기타 설치프로그램 및 여러 상주 프로그램들이 PC상태에 많은 영향을 주고 있습니다.

    이러한 상태에서는 현재 프로그램 가동이 느려지거나 사용이 불가능하기도 합니다. 클린애드 기술연구소에서 개발한 사용자 컴퓨터에 설치된 프로그램정보제공 프로그램 ‘애니그레이(anygray)’ 는 사용자의 컴퓨터에 사용자가 알지 못하는 프로그램에 대한 정보를 제공하고 사용자가 불필요하다고 판단할시 삭제를 할수 있도록 도와드리는 프로그램입니다.

    그리고 [시작프로그램 의뢰하기]라는 기능을 제공하여 사용자가 시작프로그램에 등제된 알수없는 프로그램에대해 특별한 검색 포탈 사이트의 검색이 필요없이 애니그레이를 통해서 정보를 얻을수 있도록 제공합니다.


    주요기능
    사용자 PC의 프로그램 검색기능
    사용자PC에 설치되어 있는 프로그램에 대한 정보를 제공하고 사용자에게 설치된 프로그램의 용도 및 삭제 방법등을
    제공합니다.

    사용자PC에 설치된 프로그램 리뷰
    사용자 컴퓨터에 설치된 프로그램에 대한 리뷰를 제공하여 사용자에게 유익한 프로그램은 더욱더 사용을 권장하도록 유도하고, 그렇지 못한 프로그램의 경우 삭제를 유도함으로써 사용자가 안심하고 프로그램을 사용할수 있도록 합니다.
    앞으로 더욱 더 양질의 리뷰를 제공할 예정입니다.

    프로그램 전체보기 기능
    애니그레이 인터페이스는 사용자에 설치되어 있는 프로그램을 간단하게 볼수 있는 [간단보기기능]과 컴퓨터의 [제어판]의 개념과 같은 [전체프로그램보기 기능]이 있어 인터페이스를 사용지 사용자가 불편함이 없도록 두가지 기능의 인터페이스를 제공하고 있습니다.

    시작프로그램 관리기능
    애니그레이를 통해서 사용자컴퓨터의 시작프로그램관리에 등제된 프로그램을 삭제 또는 관리하실수 있습니다.

    시작프로그램 의뢰하기 기능

    사용자의 시작프로그램에 설치되어 있는 프로그램에 대해 애니그레이에 의뢰함으로서 시작프로그램에 등제된 프로그램에 대한 정보를 얻으실수 있습니다.

    실행프로세스 관리기능

    현재 실행중인 프로세스가 종료가 안될경우 애니그레이를 통해서 종료하실수 있습니다.

    히스토리기능

    사용자가 애니그레이를 사용한 흔적을 히스토리를 통해서 확인하실수 있습니다.

    도움말 기능

    애니그레이를 사용하기위한 기본지식을 얻으실수 있습니다


    주요특징
    애니그레이는 사용자의 컴퓨터에 알수 없는 프로그램에 대한 정보를 제공하고 필요 없을 경우에는 삭제를 유도하는 헬퍼프로그램입니다.
    백신치료프로그램과는 개념이 다르며, 잘모르고 무턱대고 삭제되는 프로그램이나 사용자를 불편하게 하는 프로그램에 대한 정보를 제공하여, 사용자 스스로가 판단하고 삭제 또는 예방을 하도록 유도하는 프로그램입니다.
    최소한 사용자PC의 레지스터리나 기타 시스템관련 파일을 거의 건드리지 않으며, 프로그램자체가 사용자가 편안하게 사용할수 있는 프로그램입니다.
    앞으로 더욱더 연구하여 100% 프리웨어인만큼 사용자에게 유익한 프로그램이 되도록 연구개발하겠습니다.


    실행 및 설치방법
    다운로드 받은 파일을 실행하여 순서에 따라 설치 하시면 됩니다.




    Posted by 1010
    반응형
    네트워크 프로그래밍을 하다보면 송/수신된 패킷을 확인이 필요할 때가 있다.
    이때 주로 사용되는 네트워크 패킷 캡쳐 프로그램일 소개하려 한다.
    SmartSniff 는 일단 무료로 사용가능하며 다른 패킷 캡쳐 프로그램보다 간편하게 이용할 수 있다.

    제작사 홈페이지: http://www.nirsoft.net/

    사용자 삽입 이미지
    Posted by 1010
    90.개발관련문서2008. 8. 12. 14:14
    반응형
    출판일 :2005년 10월호

     패킷 분석기는 네트워크 구축이나 운영시 발생하는 문제를 해결하기 위한 필수 요소다. 때문에 네트워크 관리자라면 누구라도 패킷 분석기의 세상에 한번쯤은 빠져들어야 한다. 패킷 분석기는 스니퍼와 같은 고가의 상용 제품도 있지만 이더리얼과 깉이 누구라도 쉽게 무료로 사용할 수 있는 공개용 솔루션도 있다. 이번호에는 국내뿐 아니라 해외에서도 네트워크 관리자라면 보편적으로 많이 사용하고 있는 이더리얼의 주요 기능과 활용법에 대해 소개한다.

    최성열| 파이오링크 기술지원센터장

     한국의 큰 명절인 지난 추석에 필자는 중요한 일 때문에 가족들과 떨어져 일본에서 보내야만 했다. 신규로 구성하는 네트워크에서 문제가 발생해 일본의 파트너들과 함께 일을 했다. 그곳에서 발생한 문제는 이더넷 패킷이 일부 변경되거나 패킷이 가끔 손실되는 현상이었다. 어떻게 해서든 문제를 찾아야 하는 상황이라 파이어월, 2, 3, 4계층 스위치 등 어느 구간에서 문제가 발생하는지를 찾기 위해 여러 명의 엔지니어들과 함께 일했다.
    보통 패킷을 분석할 때는 예상되는 구간에 한 대 정도만 분석기를 설치하고 수집된 데이터를 기반으로 분석을 하는데, 이번에는 어떤 장비도 믿을 수가 없었기에 각 구간마다 분석기를 설치했다. 덕분에 당시 설치했던 패킷 분석기만 13대였다(사실 필자가 문제 해결을 위해 이렇게 많은 장비를 사용하기는 처음이었다). 이때 사용한 프로그램으로는 상용으로 가장 많이 사용되는 스니퍼(Sniffer)와 공개용으로 많이 사용되는 이더리얼(Ethereal)이었다.
    대부분의 엔지니어들의 노트북에는 이 두 개의 프로그램이 탑재돼 있을 것이며, 어떤 시스템을 사용하는가는 그 구간을 담당하는 엔지니어가 익숙한 정도에 달려 있다.


    패킷 분석기의 역할과 중요성
    며칠 동안 필자가 뚫어지라 바라보았던 이더리얼과 스니퍼를 우리는 패킷 분석기(Packet Analyzer)라고 부른다. 이들 솔루션은 연결된 네트워크에 지나가는 패킷들을 하나도 빠짐없이 모두 그 형태에 맞게 나열해주고 일부 필요한 것들은 통계로 알려준다.
    이 같은 패킷 분석기의 사용은 문제를 해결할 때 뿐만이 아니라 여러분들이 책으로만 봐왔던 네트워크 프로토콜의 실제 동작을 직접 확인하는 용도로 사용할 수도 있으며, 필요에 따라서는 책으로 소개되지 않은 애플리케이션(P2P, 바이러스, 메신저)에 대해서도 쉽게 파악하는 용도로 사용할 수도 있다. 




    패킷 분석기는 (그림 1)처럼 확인하고자 하는 구간에 설치해 패킷들을 캡처하고, 관리자를 이를 기반으로 문제를 분석할 수 있도록 한다. 일반적으로 컴퓨터에서 어떤 통신이 안될 경우에는 해당 컴퓨터에 이더리얼을 설치하거나, 해당 컴퓨터가 설치된 같은 네트워크에 연결해 문제가 있는 컴퓨터와 동일한 동작을 해 볼 수도 있다. (그림 1)에서 무언가를 자세하게 들여보는 듯한 '돋보기’그림이 패킷 분석기라는 툴의 특징을 가장 잘 나타내준다고 할 수 있다.



    (화면 1) 이더리얼 실행 화면


    이더리얼과 같은 패킷 분석기의 값어치는 여러분들이 어떻게 사용하느냐에 따라서 하늘과 땅 차이가 난다. (화면 1)의 이더리얼 실행 화면에서 볼 수 있듯이 짧은 순간에도 상당히 많은 패킷들을 수집하므로 툴에서 제공하는 여러 기능을 적절히 이용할 줄 아는 지혜를 가져야 한다. 그렇지 않으면 짧은 내용의 패킷 분석을 위해서 너무 많은 시간을 들여야 할지도 모른다.
    보통 이더리얼을 처음 접한 사람들은 모두들 큰 그림의 패킷 하나하나를 제일 깊은 단계로 생각을 한다. 하지만 패킷 분석기의 가장 중요한 특징은 바로 패킷 하나를 세부적인 단계로 나눠볼 수 있다는 점이다. (화면 1)을 보고 여러분들이 이미 알아차렸을 수도 있지만 대부분의 툴은 패킷 하나하나의 세부 구조와 정보를 우리가 알기 쉽게 번역해 주고 있다.



    (화면 2) 웹에서 GET을 보낼 때의 패킷 세부 내용


    그동안 필자의 연재를 지속적으로 보아온 독자라면 (화면 2)를 조금만 살펴봐도 별 어려움 없이 내용을 이해할 수 있을 것이다. (화면 2)는 여러 패킷도 아니고 단 하나의 패킷이다. 앞에서 설명했지만 패킷의 리스트(List)를 주욱 살펴봐서 어떤 패킷이 오는지 혹은 오지 않는지를 따지는 것은 아주 기본적인 사항이며, 나아가서는 이렇게 패킷 하나하나를 살펴서 어떤 정보들을 주고받았는지도 살펴야 한다(아마 시간이 지나면 여러분들도 그렇게 될 수 밖에 없다. 왜냐하면 때로는 패킷은 다 왔는데도 통신이 안 되는 경우가 있기 때문이다).


    이더리얼 구하기와 설치하기
    이더리얼은 공개용이므로 홈페이지(www.ethereal.com)에 가면 누구라도 손쉽게 구할 수가 있으며 설치 또한 간단하다. 하지만 주의할 점은 이더리얼 설치 전에 컴퓨터의 LAN 카드로 들어오는 패킷들을 이더리얼로 수집해 주는 Winpcap(www.winpcap.org)을 먼저 설치해야 한다. 이 프로그램이 없으면 이더리얼은 무용지물이다.
    현재 제공되는 이더리얼의 버전은 0.10.12 버전이다. 맨 앞이 1이 아닌 0이어서 불안해 할 수도 있지만 이미 여러 차례 패치가 됐고 많은 사람들이 사용하고 있으므로 실무에 사용해도 전혀 손색이 없다(사실 상용 버전보다는 디자인 측면에서 많이 부족하지만 요즘 나온 버전에는 버튼에 아이콘들도 추가해서 제법 틀을 갖춰가고 있다).


     

    <이더리얼의 탄생 과정>
    이더리얼은 리눅스 계열에서 사용한 패킷 덤프(dump) 프로그램인 'tcpdump'라는 툴에서 시작됐다. tcpdump는 리눅스에서 시스템에서 처리하는 패킷을 다양한 옵션으로 볼 수 있는 명령어이고, 지금도 리눅스 애호가로부터 많이 사용되고 있는 명령어다. 이더리얼은 리눅스에서도 비슷한 화면구조로 돼 있는 GUI 프로그램이며, 필자가 설명하고 있는 것은 이것을 윈도우에서 사용할 수 있도록 돼 있는 프로그램이다. 이더리얼 홈페이지를 자세히 살펴보면 깜짝 놀랄 세 가지 일이 있다.
    첫번째는 현재 이더리얼은 719개의 다양한 프로토콜을 지원한다는 점이다. 이 세상에 얼마나 많은 프로토콜이 존재하는지는 필자도 잘 모르지만, 실무에서 사용하고 있는 프로토콜 대부분은 아마도 이더리얼이 지원할 것이다(프로토콜이 지원된다는 것은 (화면 2)에서 처럼 해당 프로토콜을 세부적으로 사람이 볼 수 있도록 정리할 수 있다는 의미다).
    두번째로는 정말 다양한 플랫폼에서 동작한다는 점이다. 리눅스(레드햇, SuSE, 데비안, PLD, ROCK, SCO, 슬랙웨어), 솔라리스(인텔, 썬), Irix, FreeBSD, OpenBSD, AIX, HP-UX, 맥킨토시 등 다양한 플랫폼에서 동작한다. 아마 어떤 상용 툴도 이렇게 다양한 플랫폼은 지원하지 못할 것이다.
    그리고 마지막 특징으로는 NAI의 스니퍼, 썬의 Snoop, 쇼미티의 Surveyor, AIX의 iptrace, 마이크로소프트의 Network Monitor, HP-UX의 nettl, 와일드패킷의 Etherpeak 등 여러 상용 패킷 분석기에서 캡처한 것들도 볼 수가 있다는 것이다. 이 같은 놀라운 특징들 때문에 이더리얼의 사용을 강력하게 추천하는 것이다.

     


    필자가 이더리얼을 사용하는 법
    스니퍼 사용시 캡처를 할 때 필터(Filter)를 사용하고, 캡처가 끝난 후에 필요에 따라서 캡처된 데이터에서 필터를 사용하는 방법을 사용했었다. 반면 이더리얼의 특징 중 하나가 캡처하는 화면을 실시간으로 보는 것은 기본이고, 필요에 따라서는 캡처하면서도 각종 옵션으로 화면에 나타나는 것들을 조절, 분석하는 데 많은 도움을 줄 수가 있다는 점이다. (화면 3)은 이더리얼에서 캡처를 시작할 때 화면이다.



    (화면 3) 캡처를 시작할 때의 다양한 옵션       


    이더리얼을 비롯한 대부분의 패킷 분석기는 캡처 시작 전에 몇 가지 기본적인 설정을 하고나서 시작한다. 여러분들이 처음 이더리얼을 구동했다면 제일 먼저 어떤 인터페이스에서 구동할 것인가를 결정해야 한다. 많은 사람들이 이 선택을 잘못하고 캡처를 시작하면서 되려 이더리얼이 몹쓸 툴이라고 흉보는 경우가 많다.
    필자는 그동안 많은 시간을 투자해 실시간으로 캡처하면서 보는 습관을 들였다. 이 방법이 분석에 많은 도움이 돼 이제는 'Display Options'에 있는 두가지 옵션(화면 3에서 선택된)을 대부분 기본으로 사용한다. 이 옵션을 사용하면 실시간으로 캡처된 리스트들이 자동으로 업데이트되고, 리스트를 보여주는 스크롤이 자동으로 올라가 최근 패킷만 볼 수가 있다. 때문에 실시간으로 패킷을 보고 싶은 필자에게는 아주 유용하다.
    (화면 3) 중 'Name Resolution'이라는 부분은 화면에 보여지는 MAC, IP, TCP/UDP 포트 등을 연관된 이름들로 확인해 보여주는 옵션이다. 예를 들면 MAC은 앞에 있는 3바이트가 제조사 MAC이기 때문에 이를 검색해 보여주고, IP에 해당하는 컴퓨터 이름(DNS 이름), TCP/UDP에 해당하는 프로토콜 이름(TCP 80 → HTTP)으로 보여준다.
    하지만 이 옵션은 유용하면서도 실시간으로 많은 패킷들을 받아서 보여줘야 하는 프로그램 입장에서는 많은 부하를 줄 수 있다. 그러니 수십 Mbps 이상을 캡처할 때는 가급적 이들 옵션은 사용하지 않는 것이 낫다(하지만 실제로는 여러분이 직접 경험하고 느껴본 후에 어떤 것이 효율적일지 나만의 옵션을 찾는 게 중요하다).
    만약 실시간으로 패킷을 볼 게 아니라 필요에 따라서 캡처했다가 나중에 분석할 계획이라면 파일로 저장하는 옵션을 선택하자. 네트워크에 발생하는 문제들은 이번에 필자가 일본에서 겪었던 것처럼 언제 어느때 발생할지 모르는 경우가 있기 때문에 그것 때문에 실시간으로 패킷을 계속 보고 있는 것이 때로는 바보같은 일이기 때문이다.
    이 경우에는 패킷을 일정한 사이즈, 시간마다 자동으로 저장해 나중에 분석이 필요할 때 어느 파일부터 선택해서 분석해야 하는지에 도움이 될 수 있다. 그렇다고 내 컴퓨터의 하드디스크가 크다고 무조건 수 기가바이트로 남기는 것은 나중에 분석을 안하겠다고 하는 것과 마찬가지다. 더구나 윈도우도 그렇게 큰 크기는 읽지 못할뿐더러 수백 메가바이트나 되는 데이터를 프로그램에서 띄운다는 것은 절대 불가하다.


    캡처 필터/화면 필터 잘 쓰는 사람이 고수
    이더리얼의 고수는 바로 필터를 자유자재로 사용하는 사람이다. 우선 캡처 필터는 리눅스에서 tcpdump를 한번이라도 써 봤던 사람이라면 이해할 만한 문법으로 돼 있다. 처음에는 어려워 보이지만 조금만 이해하면 패킷의 다양한 옵션까지도 선택할 수가 있다.
    캡처 필터는 수동으로 입력하는 게 기본인데, 잘 모르겠다면 (화면 3)에 있는 캡처 필터 부분을 클릭해 보면 기본 예제를 볼 수 있다. 물론 'Help'를 누르면 추가 예제 몇 개가 더 나올 것이다. 여기서 중요한 것은 단지 필터 한 개를 잘 사용하는 것이 아니라는 점이다. and, or, not 등의 다양한 연산을 잘 사용하는 것이 제일 중요하다. 다음은 이더리얼에 있는 예제들이다.


    (1) 08:00:08:15:ca:fe 라는 MAC을 가진 호스트에서 사용하는 패킷만 캡처
    ether host 08:00:08:15:ca:fe


    (2) 192.168.0.10라는 IP 주소에서 오는 패킷이나 그쪽으로 향하는 패킷 캡처
    host 192.168.0.10


    (3) TCP 프로토콜 중 포트 80을 사용하는 패킷 캡처
    tcp port 80


    (4) IP 주소가 192.168.0.10이면서 TCP 포트 80을 사용하지 않는 패킷 캡처
    host 192.168.0.10 and not tcp port 80


    위의 예 뿐만이 아니라 arp, 출발지(src), 목적지(dst), 서브넷 기준으로도 구분이 가능하고 패킷사이즈에 따른 캡처도 가능하다. 이를 가장 잘 익힐 수 있는 방법은 메뉴얼을 읽거나 tcpdump 옵션을 찾아보는 것이다.


    <not 사용을 습관적으로>
    필터를 사용할 때 종종 사용하게 될 옵션 중 하나가 바로 'not'이다. 이 옵션은 말 그대도 어떤 특정 패킷들은 너무도 당연해서 볼 필요가 없을 경우에 이들을 제외한 나머지를 보고자 할 때 사용하는 옵션이다.

     

    다음은 icmp를 이용한 웜인 웰치아(Welchia)가 한창 유행일 때 사용했던 옵션이다. 이 패킷의 특징은 icmp를 사용하는 핑(ping)을 이용해 불특정 네트워크를 검색하느라 라우터, 파이어월과 같은 IP 처리 장비들이 제대로 서비스를 못하는 문제를 야기시켰다. 하지만 이 패킷은‘92바이트'라는 패킷 사이즈를 가지는 게 특징이었으므로 단순히 icmp를 네트워크에서 분리할 경우에는 어떤 호스트에 문제가 있는지를 찾기는 쉽지 않았다. 하지만 툴을 제대로 사용할 줄 아는 사람이라면 다음과 같은 방법을 쓰면 어렵지 않게 구분해 낼 수가 있다.



    (화면 4) 필터 4. icmp and ip[2:2] = 92


    (화면 4)의 옵션인 'icmp and ip[2:2] = 92'를 살펴보자. 무슨 암호문자 같기도 하지만 해석해 보면 그리 어렵지는 않다. 우선 icmp이면서 ip[2:2] = 92라고 했다. ip는 ip인데 뒤에 [2:2]라는 이상한 옵션이 추가됐다. 여기서 앞에 숫자 2는 바이트 단위로, IP 헤더 중에 2바이트인 16비트 뒷부분을 가리킨다. 그리고 뒤에 숫자 2는 거기부터 다시 16비트이다. 바로 그 값이 92인 것을 가리킨다. 물론 한번에 이해하기가 쉽지는 않을 것이다. 사실 필자도 처음 이 옵션을 찾아냈을 때 한참을 해매야만 했다.




    (그림 2)는 IP 헤더의 구조다. 이제 앞에서 설명한 2:2의 비밀을 파헤쳐 보자. (그림 2)에서는 32비트 단위로 구분을 했고 그중에 헤더의 16비트, 즉 2바이트 뒤가 바로 패킷의 길이를 나타내는 부분이다. 그리고 길이를 나타내는 필드는 전체 16비트, 즉 2바이트 안에 표기하게 돼 있다. 그 길이가 92인 값을 찾아달라는 것이다. 이제 이해가 좀 될 것이다. 아직도 이해가 안된다면 지난 3월호에 연재했던 IP 헤더에 대해 다시 한번 공부하자.
    실시간으로 패킷을 볼 때면 리스트가 너무 빨리 지나가기 때문에 사실 이더리얼에 능숙한 사람이라도 보고 싶은 부분만을 잡아내기란 쉽지 않다. 그래서 이더리얼은 실시간 패킷을 볼 때 바로바로 필터를 적용할 수 있는 방법을 제공한다. 이 필터의 장점은 캡처 필터에서 사용된 옵션대로 필터는 하고 있지만, 화면에 보이는 부분만 다시 필터를 사용할 수가 있다는 것이다. 그렇기 때문에 다시 화면 필터를 제거하면 캡처 필터에서 사용했던 필터대로 나타나게 된다. 가장 중요한 것은 실시간 작업을 할 수가 있다는 점이다. 그런데 이 필터 옵션은 캡처 옵션과는 조금 다른 형태를 갖고 있으므로 여러분들이 조금만 신경 쓰면 어렵지 않게 좋은 결과를 얻을 수가 있다.



    (화면 5) ICMP/TCP 등이 혼재돼 있는 캡처


    (화면 5)처럼 몇가지 필터를 사용해 캡처를 시작했는데 캡처 중에 icmp만 보고 싶다면 캡처를 정지할 필요가 없다. 그냥 화면 필터 부분에 (화면 6)과 같이 해주면 된다.



    (화면 6) 실시간 캡처 중 다시 icmp만 화면 필터 적용


    (화면 6)은 icmp만을 다시 보기 위해서 적용한 화면 필터로, 필요한 부분을 봤다면 삭제하거나 'Clear'라고 하면 원래의 필터대로 보이게 하는 옵션이다. 실시간으로 되는 것도 제법인데 이 옵션은 아주 마음에 드는 기능 중 하나다. 이 필터를 제대로 쓰고 싶다면 옆에 있는 Expression 메뉴를 클릭해 보자. 필자가 웰치아(Welchia)를 이더리얼로 캡처할 때 사용한 옵션이 화면 필터에서는 (화면 7)과 같이 Expression 옵션에서 사용할 수가 있다.




    (화면 7) 화면 필터의 Expression



    (화면 8) IP 전체 길이 92바이트 선택


     

    (화면 8)은 Expression의 IP 부분만 선택해서 세부적인 필드 중에 길이(Length) 필드의 값이 92인 것을 선택한 화면이다. 이처럼 여러분들이 프로토콜에 대한 세부지식(헤더구조 등)만 안다면 얼마든지 많은 옵션을 사용해 정말 분석다운 분석을 할 수가 있다.
    이번에는 컬러 필터라는 것을 한번 사용해 보자. 컬러 필터는 화면 필터와 같은 옵션이지만 여기에 색(Color)을 겸비해서 사용하는 방법으로 가독성을 한층 올려주는 방법이다. 메뉴의 'Color Rules'를 선택해 필터와 보여지는 색깔을 선택하면 된다. 배경색깔과 각 패킷 리스트의 글씨를 구분할 수 있다.



    (화면 9) TCP 패킷들의 색깔만 필터 적용


    이 기능은 단순히 화려하게 보이려는 목적보다는 패킷을 조금 더 빨리 분석하기 위한 이더리얼의 배려이니 자신만의 컬러 필터를 만들어 별도로 저장해 보자.


    캡처하는 방법에 대한 고민
    앞에서 필자가 거짓말을 조금 보태서 이더리얼을 이용하면 많은 것을 할 수 있고 마치 이더리얼을 사용할 줄 알면 패킷 분석의 고수가 될 것 같은 인상을 풍긴 듯 한다. 그런데 사실은 정말 그렇다. 
    패킷분석기를 이용해도 별로 소용이 없다라고 말하는 이들 중 대부분은 필자가 아는 한 잘못된 방법이나 위치에서 패킷 분석기를 이용해 캡처를 하는 경우다. 때문에 정작 문제가 발생했을 때 중요한 패킷을 캡처하지 못한 경우가 많다. 사실 이번 출장에서 필자가 문제를 해결할 때도 그런 면이 없지 않았다. 하지만 너무 많은 구간에서의 문제 발생 가능성이 있을 때는 어쩔 수 없기도 하다. 생각해 보라. 패킷 분석을 위해 수십 대의 분석 툴을 설치한다는 것이 쉬운 일인가. 더군다나 캡처는 캡처지만 이후에 이 패킷을 모두 분석해야 하는데, 캡처한 데이터가 수백 기가바이트가 넘어간 시점에서는 정말 암담할 뿐이다. 하지만 조금만 신경쓰면 이 같은 문제들은 조금씩 줄일 수는 있다.


    이더리얼을 어디에 연결할까
    문제가 생긴 네트워크에 그냥 이더리얼이 설치된 컴퓨터를 연결한다고 해서 모든 패킷이 캡처되는 것은 분명 아니다. 그렇기 때문에 어떻게 연결해야 할까를 이제는 고민해야 한다.
    만약 내 컴퓨터만의 문제라면 별 문제없이 내 컴퓨터에서 구동만 하면 되지만 네트워크의 다른 호스트의 문제나 다른 패킷을 분석하려면 모든 패킷이 지나다니는 구간을 선택해야 한다. 가장 좋은 방법이 스위치에서 업링크 포트를 미러링하는 방법이다. 이 방법을 사용할 경우, 스위치에서 간단한 미러링 설정만 하면 된다.




    가끔 어떤 작업자는 미러링 설정을 할 때 특정 패킷(In or Out)만 선택하는 경우가 있는데, 이런 부분을 선택할 때도 신중하게 내가 얻고자 하는 데이터와 관련이 있는가를 고민해야 한다. 특별한 문제가 없다면 In/Out을 모두 선택하는 방법을 추천한다. 참고로 일부 스위치들은 미러링을 받는 포트에 연결된 컴퓨터가 다른 포트와의 통신이 안되는 경우가 있다. 이 경우 패킷 분석기가 설치된 컴퓨터를 원격에서 제어하는 방법도 고민해야 한다. 그래서 가끔 LAN 카드를 하나 더 연결하는 경우도 있다.
    그리고 필요에 따라서는 외부와 연결되는 한 포트만 미러링을 해도 되지만 N:1처럼 여러 포트를 미러링해야 할 필요가 있을 수도 있다. 가급적이면 유연한 생각을 갖고 운영할 수 있도록 한다.
    가끔씩 미러링이 현실적으로 불가능할 경우에는 탭(Tap)이라는 장비를 사용하는 경우가 있다. 이 장비를 연결되는 두 장비 중간에 연결해 필요한 패킷들을 패킷 분석 툴이 설치된 컴퓨터로 보내줄 수가 있다. 이런 장비들은 요즘 한창 사용되고 있으므로 알아둘 필요가 있다.

    탭이나 미러링 장비를 연결할 때 가끔 연결에만 신경을 쓰다가 포트 설정(Speed/Duplex)을 무시하는 경우가 있는데, 이 부분도 신경을 쓰도록 하자. 포트 설정이 잘못될 경우 패킷 분석기와의 통신에서 패킷이 손실될 수가 있기 때문이다. 이 같은 사소한 문제로 거사를 놓칠 수는 없지 않겠는가?
    스위치에서 미러링을 하는 이유는 스위치는 허브와 달리 입력되는 패킷이 모든 포트로 전달되지 않기 때문이다(사실 이것이 바로 스위치의 장점이다). 그래서 모든 패킷을 받지 못하기 때문에 미러링을 사용하는 것이다. 그래서 가끔 역으로 입력되는 모든 패킷을 모든 포트로 전달하는 허브를 이용하기도 하는데 개인적으로는 이 방법은 권장하지 않는다. 대량의 트래픽 처리시 아무래도 허브의 성능 부족이나 충돌(Collision) 등으로 인해 분석에 혼란을 야기 할 수도 있기 때문이다.


    컴퓨터의 시간을 맞추자
    이더리얼을 사용할 때 정말로 신경써야 할 게 한가지가 더 남아 있다. 그것은 바로 컴퓨터의 시간을 맞추는 일이다. 지난호의 시스로그 서버도 그렇지만, 이들이 남기는 시간은 모두 컴퓨터에 맞춰진 시간을 기준으로 한다.
    문제가 다시 발생했을 때 분석시 정말 사소한 시간 문제로 고민하지 않으려면 컴퓨터의 시간을 주변의 장비들과 동일하게 맞춰야 한다. 가장 좋은 방법은 NTP(Network Time Protocol)를 사용하는 방법인데, 대부분 관리가 가능한 장비들은 NTP를 지원하므로 윈도우용 NTP 클라이언트를 인터넷에서 찾아 시간을 동기화하자.
    혹시라도 이런 작업이 귀찮아서 나중에 분석할 때 시간차를 계산해서 하겠다는 생각은 아예 버려라. 잠깐의 귀찮음이 여러분들의 시간과 실력을 한참 퇴보하게 만들것이다. 패킷분석은 때로는 초 단위 보다 더 세분화해서 하는 경우가 있으니 부디 필자의 조언을 듣기 바란다.
    이더리얼은 공개용이지만 정말 추천할 만한 툴이다. 사실 필자도 처음에는 불법(?) 스니퍼를 사용하면서 패킷 분석기를 접해봤지만 지금은 대부분의 경우 이더리얼을 사용한다. 물론 업무용도로 말이다. 매달 쓰는 원고 내용마다 중요하지 않은 것은 없지만, 이번에 소개한 패킷 분석기는 정말로 그 어느것보다도 중요하다는 점을 강조하면서

    Posted by 1010
    반응형

    HTTP WATCH를 많이 사용하시는데 IE7에서 안되서 답답하셨다면

    찰스를 사용해보세요~

    Posted by 1010
    98..Etc/Etc...2008. 8. 12. 13:08
    반응형
    1. GWT란 무엇인가?

    - Java를 이용해 Rich Ajax 애플리케이션을 제작할 수 있는 오픈 소스 프레임워크.
    - JavaScript/HTML 코드를 자동으로 생성해 냄.
    - 모든 종류의 브라우저를 지원함.
    - Pure JavaScript/DHTML at the client side.
    - Pure Java at the server side.

    2. GWT를 써야 하는 이유?

    - No need to learn/use JavaScript language -> Leverage existing Java skills.
    - No need to handle browser incompatibilities -> GWT handles them for you.
    - No need to learn/use DOM APIs -> Use pure Java APIs.
    - No need to handle forward/backward buttons -> GWT handles this for you.
    - No need to build commonly used widgets -> GWT provides most of them.

    3. GWT의 단점

    - Java 1.4 버전까지만 지원.
    - GWT로 자동 생성된 HTML과 JavaScript 코드는 꽤 무거움.
    - Hosted 모드에서는 실행 속도가 느림.
    - 다양한 Widget을 제공하지 않음.
    - 동기식(Synchronization) RPC(Remote Procedure Call)를 지원하지 않음.
    - Modal 대화창 개발에는 적합하지 않음.
    - 클라이언트는 client 폴더의 하위 폴더에만 접근할 수 있음.
    - 쓸만한 무료 UI 디자이너 툴을 제공하지 않음.
    - 한글 처리 문제.

    * 출처
    - http://www.ibm.com/developerworks/kr/library/os-ad-gwt1/index.html
    - http://www.ibm.com/developerworks/kr/library/os-ad-gwt2/index.html
    - http://www.ibm.com/developerworks/kr/library/os-ad-gwt3/index.html
    - http://www.devbg.org/seminars/seminar-GWT-26-september-2007/AJAX-Applications-with-Google-Web-Toolkit-Nakov-v1.0.pdf
    - http://blog.dev.daewoobrenic.co.kr/tc/jcfblog/entry/Google-Web-ToolkiGWT-%EB%8B%A8%EC%A0%90-%EB%B0%8F-%EC%B0%B8%EC%A1%B0-%EB%A7%81%ED%81%AC
    - http://factorystories.springnote.com/pages/1089916
    - http://naucika.tistory.com/15



    출처 : http://chocodonut.tistory.com/248
    Posted by 1010
    98..Etc/Etc...2008. 8. 12. 13:07
    반응형

    Google Web Toolkit, Apache Derby, Eclipse를 사용하여 Ajax 애플리케이션 구현하기, Part 1: 환상적인 프론트엔드 (한글)

    자바 프레임웍에서 Ajax 애플리케이션 구현을 위한 Google Web Toolkit

    developerWorks
    문서 옵션
    수평출력으로 설정

    이 페이지 출력

    이 페이지를 이메일로 보내기

    이 페이지를 이메일로 보내기

    
    제안 및 의견
    피드백

    난이도 : 중급

    Noel Rappin, Senior Software Engineer, Motorola, Inc.

    2007 년 2 월 06 일

    Google Web Toolkit (GWT)은 동적 Java™Script의 생성에 혁신을 가져왔습니다. GWT를 사용하면, 개발자들은 익숙한 자바 기술을 사용하여 사용자 인터페이스(UI)와 이벤트 모델을 디자인하고 대다수의 브라우저에 익숙한 코드를 만드는 일을 하게 됩니다. 이 글을 통해, GWT의 기초를 설명하고, GWT에서 Asynchronous JavaScript + XML (Ajax) 애플리케이션을 만드는 방법과, 자바 언어로 코드를 작성하는 방법을 설명합니다. 또한 온라인에서 피자를 판매하는 Slicr라고 하는 Web 2.0 비즈니스 샘플을 가지고, GWT 애플리케이션을 생성 및 실행하는 방법을 설명합니다.

    GWT를 사용하여, 전통적인 자바 GUI 인터페이스를 구현하는 것 보다 훨씬 더 쉽게, 더욱 풍부한 Ajax 브라우저 클라이언트 인터페이스를 구현할 수 있다. GWT가 매우 놀랍기는 하지만, 전체 웹 애플리케이션을 이것 하나로 만들 수는 없다. 서버에 데이터 스토어가 있어야 하고, 데이터를 자바 객체로 변환하여, GWT가 서버에서 클라이언트로 전달할 수 있도록 하는 프레임웍이 있어야 한다. 본 기술자료 시리즈에서, 100% 순수 자바 데이터베이스인 Apache Derby를 사용하는 방법을 설명한다.

    이 글에서는 GWT에 대한 중점적인 설명과 더불어, GWT를 설정하는 방법과 사용자 액션에 반응하는 간단한 클라이언트 인터페이스를 구현하는 방법을 설명한다. 후속 기술자료에서는 Derby 데이터베이스를 설치하고, GWT 프론트엔드를 Derby 기반 백엔드에 연결하는 방법을 설명한다. 마지막으로, 개발 환경 밖에서 시스템을 전개하는 방법도 배운다.

    Ajax 리소스 센터에서는 Ajax 프로그래밍 모델과 관련한 기술자료, 튜토리얼, 포럼, 블로그, wiki, 이벤트, 뉴스 등이 제공된다.

    Google Web Toolkit이란 무엇인가?

    GWT를 사용하여, 자바 프로그래밍 언어로 Ajax 애플리케이션을 개발할 수 있다. Ajax 애플리케이션의 장점은 전통적인 UI 애플리케이션과 연결되는 풍부한 인터랙티브 환경일 것이다. 그림 1은 샘플 GWT 인터페이스로서 데스크탑 이메일 애플리케이션이다. 이 데모는 GWT 웹 사이트에서 볼 수 있다.


    그림 1. GWT 이메일 데모
    A GWT e-mail demonstration

    GWT의 가장 고유한 기능은 Ajax 애플리케이션을 만들 수 있고, 자바 언어로 코드를 작성할 수 있다는 점이다. 선호하는 자바 통합 개발 환경(IDE)을 사용하여, 클라이언트를 디버깅 할 수도 있다. 자바 객체를 사용하여 클라이언트와 서버간 통신도 가능하고, 모든 것이 자바 애플릿 보다 가볍다.

    GWT는 기본적으로 컴파일러이다. GWT는 자바 코드를 HTML 페이지에 삽입되어 실행될 수 있는 JavaScript 코드로 변환하여 클라이언트 측 애플리케이션을 실행하는데 사용된다. JavaScript 코드는 거의 모든 브라우저에서 지원되므로 여러분은 프로그램의 인터페이스와 인터랙션에 집중할 수 있다.

    물론, GWT가 컴파일러일 뿐이라면, 거론하지 않았을 것이다. 다행히도, 그 이상의 기능을 한다. GWT는 컴파일러 메커니즘 뿐 만 아니라, 아래와 같은 부가적인 특징이 있다.

    • 표준 UI 위젯은 유연하고, 거의 모든 주요 브라우저에서 실행된다. (Safari와 Opera 포함)
    • 클라이언트 측의 이벤트를 포착하고 응답하는 이벤트 메커니즘.
    • 웹 애플리케이션과 서버 간 비동기식 호출을 관리하는 프레임웍.
    • Ajax 애플리케이션이 예상 Back 버튼 작동에 관여하지 않도록 하는, STATEFUL 브라우저 히스토리를 만드는 메커니즘.
    • JUnit을 사용하여 클라이언트 애플리케이션용 단위 테스트를 작성하는 테스트 프레임웍.

    본 시리즈에서는 이러한 기능들을 설명할 것이다. 하지만 먼저, GWT를 다운로드 및 설치해야 한다.

    GWT 설치하기

    이 글을 쓰고 있는 현재, GWT 최신 버전은 1.2이다. (참고자료) GWT는 Microsoft® Windows® XP, Windows 2000, Linux® 그리고, Mac OS X에서 실행되는 GTK+ 2.2.1 또는 그 이후 버전에서 완벽히 지원된다.

    다운로드 한 압축 파일을 원하는 디렉토리에 압축을 푼다. 버전들마다 약간 다르지만, 기본 엘리먼트는 다음과 같다.

    • 세 개의 .jar 파일: gwt-user.jar 에는 프로젝트 classpath에 필요한 사용자 클래스들이 포함되어 있다. gwt-dev-windows.jar 또는 gwt-dev-linux.jar에는 컴파일러 코드가 포함되어 있다. 세 번째 파일, gwt-servlet.jar는 전개(deploy)에 사용된다.
    • 세 개의 명령행 유틸리티: applicationCreator, junitCreator, projectCreator. (Windows에서는, .cmd가 붙는다.)
    • 샘플 코드 디렉토리

    GWT를 사용할 때, 일부 파일들은 임시 파일들을 관리하기 위해 GWT 홈 디렉토리에 놓인다.

    프로젝트 만들기

    다운로드를 마치면, 가장 먼저 프로젝트를 만들어야 한다. 온라인에서 피자를 파는, Slicr라고 하는 새로운 Web 2.0 비즈니스의 온라인 사이트를 구축할 것이다. GWT 프로젝트를 설정하는 방법들은 IDE를 사용할 것인지의 여부에 따라 다르다. 여기에서는 GWT 명령행 유틸리티의 지원도 받는 무료 Eclipse를 사용하도록 하자.

    명령행 유틸리티를 사용하여 Eclipse 프로젝트를 만든다.

    1. 하드 드라이브의 적당한 곳에 slicr라고 하는 새로운 디렉토리를 만든다.
    2. 새로운 slicr 디렉토리에 명령어 프롬프트를 연다.
    3. 다음 명령어를 입력한다. (슬래시와 그 외 것들을 각자 운영 체계에 맞게 변환한다.)

    <GWT_HOME>/projectCreator -eclipse slicr

    이 명령어로는 GWT 프로젝트에 필요한 최소한의 것만 만든다. 새로운 src 하위 디렉토리와 새로운 .project와 .classpath 파일이 생겼다.

    지금 바로 프로젝트로 시작할 수 있지만, GWT는 그 이상의 구조를 기대한다. 다음 명령어를 사용하여 설정할 수 있다.


    <GWT_HOME>/applicationCreator -eclipse slicr com.ibm.examples.client.Slicr

    -eclipse slicr 인자는 Eclipse 프로젝트의 이름이고, projectCreator에서 사용했던 것과 같아야 한다. (그렇지 않으면, Eclipse 내에서 GWT 애플리케이션을 시작하는데 문제가 생긴다.) 마지막 인자는 애플리케이션의 메인 클래스가 될 클래스 풀네임(fullname)이다. 이때, 패키지명의 마지막은 의미상 client로 정하기로 하고, client 이전의 부모 패키지 이름은 여러분이 알아서 작성하도록 한다. (일단, 'com.ibm.examples'로 부모 패키지 이름을 정하였다.)

    이 명령어는 몇 가지 파일을 생성한다. .java 파일은 메인 클래스이고, 해당 클래스에 동반된 패키지 명에 부합되게 부모 디렉토리 들이 생성된다. 여러분은 최하위 디렉토리에는 clientpublic이 생성되어 있는 것을 확인 할 수 있다. 앞에서 거론한 public 디렉토리에는 Slicr.html와 Slicr.gwt.xml을 확인 할 수 있다. 또한, GWT는 eclipse가 사용하게 될 Slicr.launch파일과 두 개의 셀 스크립트 파일을 생성 한다.




    위로


    Eclipse로 옮기기

    이제 프로젝트를 Eclipse로 옮긴다.

    1. Eclipse를 열고, File > Import를 클릭한다.
    2. 새로운 창에서, General 트리를 확장하여 Existing Projects into Workspace를 선택한다.
    3. Next를 클릭한 다음 Browse를 클릭하여 slicr 루트 디렉토리를 선택한다.
    4. 프로젝트를 선택하고, Copy projects into workspace 옵션이 설정되지 않도록 한다. (프로젝트를 옮기지 않을 것이기 때문이다.)

    이 프로세스를 통해, 코드를 Eclipse로 옮겨서 볼 수 있다. GWT는 세 개의 중요한 파일들을 만들었다. 첫 번째가 com.ibm.examples 패키지에 있는 slicr.gwt.xml 파일이다. Listing 1은 XML 설정 파일이다.


    Listing 1. slicr.gwt.xml
                    
    <module>
        <inherits name='com.google.gwt.user.User'/>
        <entry-point class='com.ibm.examples.client.Slicr'/>
    </module>
    

    여기에서 GWT 애플리케이션에 대한 모듈을 정의할 수 있다. 모듈(module)은 GWT 코드의 기본 단위이고, 클라이언트가 사용하는 HTML 페이지가 참조한다.

    두 번째 파일은 퍼블릭 디렉토리에서 만들어진 Slicr.html 파일이다. 이것은 웹 애플리케이션의 프론트 페이지로서 클라이언트로 보내지는 .html 파일이다. 실제로 필요하지 않는 많은 코멘트들이 들어있다. Listing 2는 파일의 핵심 부분이다.


    Listing 2. Slicr.html
                    
    <html>
        <head>
            <title>Wrapper HTML for Slicr</title>
            <meta name='gwt:module' 
                content='com.ibm.examples.Slicr'>
        </head>
        <body>
            <script language="javascript" 
                src="gwt.js"></script>
            <iframe id="__gwt_historyFrame" 
                style="width:0;height:0;border:0"></iframe>
            <h1>Slicr</h1>
            <p>
                This is an example of a host page for 
                the Slicr application. 
            </p>
            <table align=center>
                <tr>
                    <td id="slot1"></td>
                    <td id="slot2"></td>
                </tr>
            </table>
        </body>
    </html>
    

    가장 중요한 것은 바로 위의 .html 파일이다. 원하는 어떤 HTML이라도 추가할 수 있다. 가장 중요한 것은 HTML 파일에 원하는 어떤 내용이라도 추가 및 변형 할 수 있다는 것이다. 단지, GWT는 아래 네 가지 엘리먼트에 의해서 실행된다.

    • meta 태그: name 애트리뷰트와 content 애트리뷰트는 모듈의 완전한 형식을 갖춘 논리적 이름이다. (XML 모듈 파일과, 확장자 없는 XML의 파일 이름을 포함하고 있는 패키지이다.) 이 태그는 HTML 페이지를 특정 모듈로 연결시킨다. 페이지를 호출하면 모듈이 시작된다. (특히, 모듈에 있는 모든 엔트리 포인트들이 초기화 된다.)
    • script 태그: 이 태그는, GWT 자바 코드를 JavaScript 코드로 변환할 때 생성된 파일 중 하나인 gwt.js라고 하는 파일을 로딩한다. 이 파일은 모든 클라이언트 코드의 로딩을 제어하기 때문에, 프로그램을 실행하는 것 보다 이를 포함시키는 것이 더 중요하다.
    • iframe 태그: 이 태그를 쓰여진 대로 정확히 사용하면 웹 애플리케이션은 히스토리와 상태를 기억할 수 있다. GWT 애플리케이션에서 Back 버튼을 실행 불가로 만들 필요가 없다.
    • td 태그: 특정 .html 파일에 있는 이 태그에는 JavaScript 식별자들이 포함된다. 특별한 것은 없지만, GWT는 이 식별자를 엘리먼트를 배치할 장소로서 사용한다.

    자바 시작 클래스

    GWT는 재사용이 가능한(boilerplate) 자바 시작 클래스도 Listing 3과 같이 만든다.


    Listing 3. 자바 시작 클래스
                    
    public class Slicr implements EntryPoint {
    
      public void onModuleLoad() {
        final Button button = new Button("Click me");
        final Label label = new Label();
        button.addClickListener(new ClickListener() {
          public void onClick(Widget sender) {
            if (label.getText().equals(""))
              label.setText("Hello World!");
            else
              label.setText("");
          }
        });
        RootPanel.get("slot1").add(button);
        RootPanel.get("slot2").add(label);
      }
    }
    

    위의 자바 클래스는 client 패키지에 있고, 이로 인하여 GWT는 해당 코드를 JavaScript로 컴파일 할 것이다. 이것은 파일에 해당 특정 제한이 있는 것 같지만, 코드 내부를 보면 기본적인 Java 1.4 코드일 뿐이다.EntryPoint 인터페이스는 onModuleLoad()만을 정의하였으며, GWT는 이 모듈을 참조하는 HTML 페이지가 로딩되면 이 메소드를 자동으로 호출하게 되는 것이다.

    위의 코드는 단순하다고 할 수 있다. 처음 두 라인에 버튼과 레이블을 정의한다. 그 다음 두 라인에서는, RootPanel.get() 메소드를 사용하여, 이 위젯들을 HTML 페이지의 특정 엘리먼트로 연결시킨다. 이 메소드에 대한 인자는 HTML 페이지에 정의된 엘리먼트의 JavaScript ID이다.

    위의 코드 라인들 사이에 Swing에서와 같이 이벤트를 묶는데 사용하는 것과 비슷한 방식을 사용하여 이벤트 리스너(eventListner)를 정의한다. 위의 경우, 버튼을 클릭하면 ClickListener가 호출된다.




    위로


    GWT 프로그램 실행하기

    GWT가 만든 샘플 프로그램을 실행해 보자. GWT 프로그램을 실행하는 두 가지 방법이 있다. 바로 웹 모드(Web mode)와 호스트 모드(hosted mode)이다. 웹 모드는 완전한 전개 모드로서, GWT 프로그램을 JavaScript 코드로 컴파일 한 후에 실행한다.

    호스트 모드는 개발하는 동안 사용된다. 호스트 모드란, 클라이언트와 서버 코드를 한번에 시뮬레이트 하면서, 개발하는 동안 전개를 단순화 시키는 일종의 에뮬레이터이다. (호스트 모드는 Mac OS X에서는 불가능하다.) 디버거와 함께 IDE를 사용한다면, 호스트 모드에서 GWT 프로그램을 실행할 수 있으며, JavaScript로 컴파일 될 코드 부분에서 중단점(breakpoint)을 지정하고 그의 변수(variable)의 변경사항을 알아낼 수 있다. 이러한 부분은 개발 시 매우 유용하며, GWT로 작업하게 되면, 대부분 호스트 모드를 사용하게 된다.

    또한, 여러 가지 방법들로 호스트 모드를 실행할 수 있다. 이전에 실행했던 applicationCreator 스크립트는 명령행에서 호출할 수 있는 Slicr-shell 스크립트를 만들어서 호스트 모드를 실행할 수 있도록 하였다. 또한, 이 글 초반에, GWT 프로젝트를 Eclipse로 반입하는 방법을 설명했다. Eclipse 프로젝트에서, Run 메뉴 또는 툴바에서 Debug 또는 Run을 선택할 수 있다. 생성된 창을 통해서는 Java Application을 클릭하여 Slicr 옵션을 볼 수 있다. 이 옵션은 GWT가 만들었고, 나머지 프로젝트와 함께 Eclipse로 반입했던 Slicr.launch 파일에서 사용할 수 있다.


    그림 2. 호스트 모드 호출하기
    Invoking hosted mode

    호스트 모드에서 실행할 때, 두 개의 창이 나타난다. (처음 실행할 경우, 호스트 모드 설정이 초기화되는 동안 1분여의 시간이 걸린다.) 그림 3에서 보이는 첫 번째 창의 이름은 Google Web Toolkit Development Shell / Port 8888.이다. 여기에는 GWT의 에러와 로그 메시지들이 들어있다. 툴바를 사용하여, 새로운 호스트 브라우저를 열 수 있고, 스크린상의 로그를 확장, 축소, 삭제할 수 있다.


    그림 3. 호스트 모드 쉘 윈도우
    Hosted mode shell window

    그림 4에 보이는 두 번째 창은 브라우저 모습이다. 보다시피, slicr.html 페이지에서 생성된 정적 HTML과 Slicr.java EntryPoint 클래스에서 생성된 버튼 위젯이 있다. 버튼을 클릭하면 레이블이 선택된다. 설정 단계에서 오류가 있었다면, 이 창을 볼 수 없고 대신 쉘에 에러 메시지가 나타난다. 모든 이름들이 정확한지를 확인하라. (특히, .launch 파일에서, 정확한 프로젝트 디렉토리가 지정되었는지를 확인한다.)


    그림 4. 호스트 모드 브라우저
    Hosted mode simulated browser

    클라이언트 설정하기

    이 글에서는 스크린상에서 위젯을 실행시키는 것에 초점을 맞추겠다. 그림 5에 보이는 스크린은 매우 단순해 보이지만, 꽤나 기능적이다.


    그림 5. Slicr
    Slicr

    페이지가 로딩될 때 이러한 위젯들을 생성시키려면, EntryPoint 클래스의 onModuleLoad() 메소드에 코드를 넣어야 한다. Listing 4는 두개의 데이터를 정의하고, 각 패널을 구현하기 위해 헬퍼를 호출하는 메소드를 정의하고 있다. 이 코드가 실행되려면, slicr ID로 HTML 페이지에 엘리먼트를 삽입해야 한다. 이렇게 하면 리스팅의 RootPanel.get() 명령어가 그 페이지의 엘리먼트를 찾을 수 있다. 가장 쉬운 방법은 이전 HTML 리스팅의 테이블을 >div id="slicr" /<로 대체하는 것이다.


    Listing 4. 모듈 로드 이벤트 핸들러
                    
    private DockPanel panel;
    private List clearables;
    
    public void onModuleLoad() {
        clearables = new ArrayList();
        initDockPanel();
        panel.add(buildActionPanel(), DockPanel.SOUTH);
        panel.add(buildPizzaTypePanel(), DockPanel.WEST);
        panel.add(buildToppingPanel(), DockPanel.EAST);
        RootPanel.get("slicr").add(panel);
    }
    

    위젯 설정하기

    DockPanel에 모든 위젯을 설정하도록 한다. GWT에서의 DockPanel이란, Swing에서 BorderLayout을 사용하는 Panel에 해당한다. Swing에서는 한 개의 panel 클래스와 여러 개의 레이아웃 매니저가 있는 반면, GWT에는 여러 Panel 서브클래스가 있고, 각각 자식 위젯을 전개하는 고유의 알고리즘이 있다. 다른 패널 클래스로는 SimplePanel, HTMLTable, FlowPanel, StackPanel 등이 있다. DockPanel을 만드는 것은 어렵지 않다. 아래 Listing 5에서와 같이 세터(setter)가 이 일을 대신한다.


    Listing 5. 메인 패널 초기화 하기
                    
    private void initDockPanel() {
        panel = new DockPanel();
        panel.setBorderWidth(1);
        panel.setSpacing(5);
    }
    

    SOUTH 패널(button) 생성하기

    DockPanel의 선착순 원리를 따르기 때문에, 먼저 하단부 패널을 정의한다. 이런 방식으로, SOUTH 위젯은 전체 패널에서 실행한다. 아래 Listing 6과 같이 HorizontalPanel을 액션 패널로서 정의한다. (GWT의 HorizontalPanel이란, Swing에서의 box와 비슷하다고 보면 된다.)


    Listing 6. SOUTH (buttons) 패널
                    
    public HorizontalPanel buildActionPanel() {
        HorizontalPanel actions = new HorizontalPanel();
        actions.setSpacing(10);
        Button clear = new Button("Clear");
        clear.addClickListener(new ClearClickListener());
        Button newPizza = new Button("Another Pizza");
        Button submitOrder = new Button("Submit");
        actions.add(clear);
        actions.add(newPizza);
        actions.add(submitOrder);
        return actions;
    }
    

    GWT Button 위젯을 사용하여 세 개의 버튼을 만든 다음, 패널에 추가한다. 또한, 나중에 정의하게 될 Clear 버튼에 ClickListener를 만든다. GWT는 이벤트 리스너들을 Swing과는 다르게 나눈다. ClickListener는 마우스 클릭만 리스닝한다. (종종, 인라인 클래스로서 정의된 리스너를 보곤 하는데, 이 스타일은 읽고 테스트 하기 까다롭기 때문에 네임드 클래스를 만들었다.)

    WEST 패널(pizza type) 생성하기

    pizza type 의 패널은 Listing 7과 같이 복잡하지 않다. GWT RadioButton 위젯을 사용하면 된다.


    Listing 7. WEST (pizza types) 패널
                    
    public static final String[] PIZZA_TYPES = new String[] {
        "Thin Crust Medium", "Thin Crust Large", 
        "Thin Crust X-Large", "Thick Crust Medium", 
        "Thick Crust Large"
    };
    
    private VerticalPanel buildPizzaTypePanel() {
        VerticalPanel pizzaTypes = new VerticalPanel();
        HTML label = new HTML("<h2>Pizza</h2>");
        pizzaTypes.add(label);
        for (int i = 0; i < PIZZA_TYPES.length; i++) {
            RadioButton radio = new RadioButton("pizzaGroup", 
                PIZZA_TYPES[i]);
            clearables.add(radio);
            pizzaTypes.add(radio);
        }
        return pizzaTypes;
    }
    

    HTML을 실행하는 레이블인 HTML 위젯을 사용할 것이다. HTML 위젯은 결국 HTML상의 <span> 태그 주위의 래퍼(Wrapper)로서 역할을 한다. RadioButton 컨스트럭터(constructor)는 두 개의 인자들을 취한다. 첫 번째는 라디오 버튼용 스트링 레이블이고, 두 번째는 텍스트 레이블이다. 각 버튼을 패널과 인스턴스 리스트에 추가하면 이 리스너들 중 한 곳에서 사용하게 될 것이다.


    EAST 패널 (toppings) 만들기

    topping 패널은 Listing 8처럼 좀더 복잡하다. 사용자가 다양한 토핑을 가진 피자를 만들 수 있도록 해야 한다. 토핑 버튼을 클릭하면 두 쪽 모두 체크되지만, 한 쪽만 체크되거나, 개별적으로 삭제될 수 있다. 모든 것의 줄을 맞춰야 하기 때문에 Grid를 사용한다.


    Listing 8. 토핑 그리드
                    
    public static final String[] TOPPINGS = new String[] {
        "Anchovy", "Gardineria", "Garlic", 
        "Green Pepper", "Mushrooms", "Olives", 
        "Onions", "Pepperoni", "Pineapple", 
        "Sausage", "Spinach"
    };
    
    private VerticalPanel buildToppingPanel() {
        VerticalPanel toppings = new VerticalPanel();
        toppings.add(new HTML("<h2>Toppings</h2>"));
        Grid topGrid = new Grid(TOPPINGS.length + 1, 3);
        topGrid.setText(0, 0, "Topping");
        topGrid.setText(0, 1, "Left");
        topGrid.setText(0, 2, "Right");
        for (int i = 0; i < TOPPINGS.length; i++) {
            Button button = new Button(TOPPINGS[i]);
            CheckBox leftCheckBox = new CheckBox();
            CheckBox rightCheckBox = new CheckBox();
            clearables.add(leftCheckBox);
            clearables.add(rightCheckBox);
            button.addClickListener(new ToppingButtonListener(
                    leftCheckBox, rightCheckBox));
            topGrid.setWidget(i + 1, 0, button);	
            topGrid.setWidget(i + 1, 1, leftCheckBox);
            topGrid.setWidget(i + 1, 2, rightCheckBox);
        }
        toppings.add(topGrid);
        return toppings;
    }
    

    VerticalPanelHTML 위젯을 사용한다. GWT Grid에 모든 것을 놓기 때문에, 그리드의 크기를 설정해야 한다. 그리드의 각 셀에는 플레인 텍스트나, 또 다른 GWT 위젯을 포함시킬 수 있다. 각 행(row)에, 버튼과 두 개의 체크 박스를 만들고, 이들을 셀에 맞춰 정렬한다. 버튼용 리스너를 추가하고, 체크 박스를 clearable 리스트에 놓는다.

    리스너 정의

    위젯을 설정했다면, 두 개의 정의된 리스너를 보자. 두 개 중 더 단순한 것이 Clear 버튼에 대한 것이다. Listing 9와 같이 이 버튼은 clearable 리스트에서 실행되고, 모든 것을 지운다.


    Listing 9. Clear 버튼에 정의된 리스너
                    
    private class ClearClickListener implements ClickListener {
        public void onClick(Widget sender) {
        for (Iterator iter = clearables.iterator(); iter.hasNext();) {
                CheckBox cb = (CheckBox) iter.next();
                cb.setChecked(false);
            }
        }
    }
    

    주: GWT에서, RadioButton은 실제로 CheckBox의 서브클래스이다. 따라서 위 코드는 class cast exception을 트리거(trigger)하지 않는다.

    토핑 버튼용 리스너는 더 복잡하다. 제휴 체크 박스들 중 어떤 것도 선택되지 않으면, 이 리스너는 두 개의 체크 박스 모두를 선택한다. 다른 상황에서는, 두 가지 모두를 지운다. Listing 10은 그 예이다.


    Listing 10. 버튼에 정의된 리스너
                    
    private class ToppingButtonListener implements ClickListener {
    
        private CheckBox cb1;
        private CheckBox cb2;
    
        public ToppingButtonListener(CheckBox cb1, CheckBox cb2) {
            this.cb1 = cb1;
            this.cb2 = cb2;
        }
    
        public void onClick(Widget sender) {
            boolean unchecked = !cb1.isChecked() && !cb2.isChecked();
            cb1.setChecked(unchecked);
            cb2.setChecked(unchecked);
        }
    }
    

    공유 사이트...

    digg Digg
    del.icio.us del.icio.us
    Slashdot Slashdot

    예고

    이번 글에서는 slicr 클라이언트를 구현해 보았다. 다음 글에서는 Derby 데이터베이스를 사용하여 서버 측에서 데이터 레이어를 구현하고, 데이터베이스에서 온 데이터를 GWT 클라이언트로 보내질 수 있는 자바 객체들로 변환하는 방법을 설명하겠다. 서버와 클라이언트를 연결하는 원격 프로시저 아키텍처도 설명한다.

    서버 측이 개별적으로 실행되어야 한다면, 개발 및 실행 환경에 이를 전개하는 방법을 고려해야 한다. 또한, 인터페이스를 보기 좋은 모양으로 만드는 방법도 배울 것이다. 그 전에 GWT 다운로드 사이트에 가서 직접 실행해 보기 바란다.

    기사의 원문보기



    참고자료

    교육

    제품 및 기술 얻기

    토론


    필자소개

    Noel Rappin 박사는 Georgia Institute of Technology의 Graphics, Visualization, and Usability Center 소속이며, Motorola의 소프트웨어 엔지니어이다. wxPython in Action and Jython Essentials를 공동 집필했다.

    Posted by 1010
    01.JAVA/Java2008. 8. 12. 12:09
    반응형

    개발자가 놓치기 쉬운 자바의 기본원리

    • 전성호(커뮤니티본부 커뮤니티개발1팀), 2006년 10월

    초록(abstract)

    개발자가 놓치기 쉬운 자바의 기본 원리에 대하여 기본적이긴 하지만 개발하면서 느끼고 경험한 내용을 정리하였다.

    목차

    1 객체지향의 구멍 static
    1.1 Java는 객체지향 언어이다?
    1.2 전역변수
    2 Java는 Pointer언어이다? (Java에는 Pointer밖에 없다?)
    2.1 Java는 primitive형을 제외하곤 모두 Pointer이다
    2.2 null은 객체인가?
    2.3 String에 대하여
    2.4 객체지향의 캡슐화 파괴 주의
    2.5 배열에 대하여
    2.5.1 배열은 object 인가?
    2.5.2 배열의 length는 왜 field(member variable)인가?
    2.5.3 final과 배열에 대하여...
    2.5.4 "Java에서의 다차원 배열은 존재하지 않는다."
    2.6 인수(parameter/argument)전달의 개념
    2.6.1 "Java에서 parameter(argument) 전달은 무조건 'call by value' 이다"
    2.6.2 "C와 같은 언어는 static linking이지만, Java는 dynamic linking이다."
    2.7 GC 에 대하여 잠깐!
    2.7.1 "Garbage Collection은 만능이 아니다."
    2.8 Java Pointer 결론
    2.8.1 "결국 Java에는 pointer가 있는 것인가, 없는 것인가?"
    3 상속과 interface의 문제점
    3.1 상속
    3.1.1 상속에 있어서의 생성자(constructor)
    3.1.2 "down cast는 본질적으로 매우 위험하다"
    3.1.3 "추상클래스에 final이 있으면 compile error이다"
    3.2 interface
    3.2.1 "interface는 interface일뿐 다중 상속의 대용품이 아니다."
    3.3 상속 제대로 사용하기
    4 package와 access 제어에 관한 이해
    4.1 package
    4.1.1 "package는 '계층구조' 인가?"
    4.1.2 "compiler 가 인식하는 class검색 순서(소스코드내 클래스가 발견될 경우 그 클래스의 위치를 찾는 순서)"
    4.2 access 제어
    4.2.1 "interfacde member의 access 제어"
    4.2.2 그렇다면 interface를 다른 package에 대하여 숨기고 싶으면 어떻게 하는가?
    5 기타 Java 기능
    5.1 Thread
    5.1.1 "Multi Thread에서는 흐름은 복수이지만 data는 공유될 수 있다."
    5.1.2 "Thread는 객체와 직교하는 개념이다."
    5.1.3 "Synchronized 의 이해"
    5.1.4 "Thread 사용법의 정석은?"
    5.2 Exception
    5.2.1 "finally 절은 반드시 어떠한 경우에도 실행되는가?"
    5.2.2 "예외의 종류 3가지 (Error, RuntimeException, 그밖의 Exception)"
    5.2.2.1 Error
    5.2.2.2 RuntimeException
    5.2.2.3 그밖의 Exception
    5.2.3 "OutOfMemoryError는 어떻게 처리해야 하는가?"
    5.3 Object Serialize
    5.3.1 "Serialize를 위해서는 marker interface인 java.io.Serializable interface를 implements해야한다."
    5.3.2 "super class는 Serializable이 아닌데 sub class만 Serializable인 경우의 문제점"
    5.3.3 "transient field의 복원(?)관련"
    5.3.4 "Stack Overflow에 주의하라!"
    5.4 "nested class / inner class / 중첩클래스"
    5.4.1 "중첩클래스의 개념"
    5.4.2 "내부클래스는 부모의 참조를 몰래 보유하고 있다."
    5.4.3 "local inner class에 대하여"
    5.4.4 "anonymous class(무명클래스)에 대하여"
    6 이래도 Java가 간단한가?
    6.1 method overload 에서의 혼란?
    6.1.1 "overload란 이름이 가고 인수가 다른 method에 compiler가 다른 이름을 붙이는 기능"
    6.1.2 "그렇다면 overload에서 실제로 혼동되는 부분은 무엇인가?"
    6.1.3 (참고) 또다른 혼동, overload한 method를 override 하면?
    6.2 상속/override/은폐 에서의 복잡함
    6.2.1 "Java class의 member 4 종류"
    6.2.2 "override시 method 이름에 대한 함정"
    6.2.3 "또다른 나의(?) 실수 - 말도 안되는 오타"
    6.2.4 "static member를 instance를 경유하여 참조해서는 안 된다."
    6.2.5 "super keyword는 부모의 this"
    6.3 상속에 관한 또 다른 문제
    6.4 그밖의 함정
    6.4.1 "생성자에 void 를 붙인다면?"
    6.4.2 "if / switch 의 함정"
    7 Java 기능 적용 몇가지
    7.1 대규모 개발에서 interface 분리하기
    7.1.1 "interface 분리의 필요성"
    7.2 Java에서의 열거형
    7.3 Debug write
    8 Java 5.0 Tiger 에 대하여
    8.1 Working with java.util.Arrays
    8.2 Using java.util.Queue interface
    8.3 java.lang.StringBuilder 사용하기
    8.4 Using Type-Safe Lists
    8.5 Writing Generic Types
    8.6 새로운 static final enum
    8.7 Using java.util.EnumMap
    8.8 Using java.util.EnumSet
    8.9 Convert Primitives to Wrapper Types
    8.10 Method Overload resolution in AutoBoxing
    8.11 가변적인 argument 개수 ...
    8.12 The Three Standard Annotation
    8.13 Creating Custom Annotation Types
    9 The for/in Statement
    9.1 for/in 의 자주 사용되는 형태
    10 Static Import
    10.1 static member/method import
    11 References

    1 객체지향의 구멍 static #

    1.1 Java는 객체지향 언어이다? #

    "Java는 완전한 객체지향 언어이다" 라는 주장을 자주 접하게 된다. 만일 이것이 사실이라면 Java를 사용하는 한 "기존의 절차지향 프로그래밍을 전혀 할수 없을것 같지만 그렇지 않다. 빠져나갈 구멍이 있는 것이다. static을 이용하면 비 객체지향 언어처럼 코딩할 수 있다.

    static method는 instance가 아닌 클래스에 속하는 method로, class method라고 부른다. 반대로 static이 아닌 method는 instance method라고 부른다.

    static method는 this가 없다. instance method에는 숨겨진 파라미터로 this가 건네진다. (아래 "객체지향에 흔희 있는 오해" 참고) 하지만 static method는 절차지향의 함수와 동일하므로 숨겨진 파라미터 this는 없다. 그래서 static method에서는 전달한 this가 없으므로 instance method를 호출하거나 instance field를 참조할 수 없는 것이다.

    (참고) 객체지향에 흔히 있는 오해

    • 오해1. "객체지향에서는 객체끼리 서로 메세지를 주고 받으며 동작한다." 라는 말을 듣고 다음과 같이 생각할 수 있다. "객체지향에서는 객체가 각각 독립하여 움직인다는 것인가, 그러면 각 객체에 독립된 thread가 할당되어 있단 말인가?" 그렇지 않다. "메세지를 보낸다"라는 것은 단순히 각 객체의 함수 호출에 불과하다.

    • 오해2. "객체지향에서는 method가 class에 부속되어 있다"는 말을 듣고 다음과 같이 생각할 수 있다. "그러면 instance별로 method의 실행코드가 복제되고 있는 것이 아닌가?" 물론 이것도 오해다. method의 실행코드는 종래의 함수와 동일한 어딘가 다른곳(JVM의 class area)에 존재하며 그 첫번째 파라미터로 객체의 포인터 this가 건네질 뿐이다.

    • 오해3. "그렇다면 각 instance가 method의 실행코드를 통째로 갖고 있지 않는 것은 확실하지만, method의 실행 코드의 포인터는 각 instance별로 보관하고 있는것이 아닌가?" 이것은 약가 애매한 오해이긴 하다. JVM 스펙에서는 class영역에 실행코드를 갖고 있으며, method 호출시 별도의 stack frame이 생성되어 실행되고 실행 완료시 복귀 주소를 전달한다.

    1.2 전역변수 #

    static에서 public field는 전역변수(global variable, 글로벌 변수)이다. 여기서 "글로벌 변수는 왜 안 되는가"에 대해 잠깐 생각해 본다. 우리는 흔히 "글로벌 변수는 될수있는한 사용하지 않는 것이 좋다"라고 한다. 그 이유는 글로벌 변수는 어디서든 참조할 수 있고 값을 변경할 수 있기 때문이다.

    또한 파라미터나 리턴값으로 교환해야 할 정보를 글로별 변수를 경유(사용)하여 건네주면 함수의 역할이 불분명 해지고 흐름도 애매해 진다. 마지막 이유로는 "글로벌 변수는 하나밖에 없다"는 것이다. 이는 어디서 이값을 변경했는지 알 수 없게 하는 지름길이고 실무에서도 간혹 발생하긴 하지만, 이 하나밖에 없는 변수가 버전업으로 두개가 필요하게 되었을때 확장도 대형 프로젝트에서는 힘들어진다.

    따라서 static에서 public은 final을 붙여 상수로 사용해야지 그 외의 용도는 자제하는 것이 좋을 것이다.

    • (참고) final 초기화에서의 주의점. 예를 들어 다음과 같은 코드를 보았을때 우려되는 점은 무엇인가?

    public final static Color WHITE = new Color(255, 255, 255);

    위의 코드는 java.awt.Color에서 발췌한 것인데, final 변수는 한번 초기화 되면 변경이 불가능한데 object로 초기화 할 경우 WHITE라는 필드가 변경될 수 없는 것이지 그것이 가리키는 객체는 아니라는 점이다.

    과거 신규 서비스 개발시 final 변수 필드에 설정파일을 읽어 cache하는 singleton class의 특정 member 를 이용하여 초기화 할 경우 이 멤버값이 변경되면 final 변수의 값이 변경되었는데 프로그램에서는 이상한 짓을 하는 원인을 찾기가 상당히 어려웠던 경험을 하고 난 후 부터 이런 코드는 냄새나는 코드로 여겨지게 되었다.

    static은 글로벌변수와 동일하므로 남발해서는 안된다. static을 사용할 경우 다음 두가지는 최소한 기억한다.

    1. static field는 final의 경우와 달리 정말 "하나여도 되는지" 여부를 잘 생각해야 한다.
    2. static method는 주저하지 말고 쓰되 다음 두가지의 경우 매우 활용적이다.
      1. 다른 많은 클래스에서 사용하는 Utility Method 군을 만드는 경우. (주로 Utility Class의 method)
      2. 클래스 안에서만 사용하는 "하청 메소드(private method)". 이유를 예를 들어 설명하면, 아래와 같은 조금은 과장된 클래스가 있다고 하자.

                    public class T ..                    private int a;                    private int b;                    private int c;                                        private int calc(){                        c = a + b;                        return c * c;                    }                       ....other method or getter/setter...

    위의 클래스 T의 경우 내부에서 calc라는 instance 함수를 사용하게 되면 c 의 값이 매번 변하게 된다. 이는 무심코 하는 실수로 클래스내에서 private method는 모든 멤버 instance 변수에 접근 가능하게 되면서 발생한다. c의 값이 변하지 않기를 바랄수 있다. 이때 안전한 방법은 다음과 같이 calc 하청 메소드를 static method로 수정하면 안전하다.

                private static int calc(int a, int b){               int c = a + b;               return c * c;            }

    여기서 a와 b는 멤버 변수를 접근할수 없어 전달해야한다.(static에는 this가 없어 instance field를 참조할 수 없다는 것은 이미 위에서 설명했다.) 또한 c도 같은 이유로 사용할 수 없어 로컬 변수로 선언하고 사용하고 있다. 이럴 경우 메소드가 약간 커질수 있지만 instance member 변수를 안전하게 사용할 수 있다는 장점이 있다. 이것은 static을 다시한번 생각하게 하는 좋은 예가 되었을 것이다.

    2 Java는 Pointer언어이다? (Java에는 Pointer밖에 없다?) #

    2.1 Java는 primitive형을 제외하곤 모두 Pointer이다 #

    "Java에는 포인터가 없다" 라고 Java의 장점 이라고 생각하는 것은 입문자도 외우고 있다. 하지만 이 부분은 의외로 Java를 혼란스럽게 하는 주범이라고 생각한다. Java에 포인터가 없기는 커녕 primitive(int, short, char, long...등 JVM의 Heap에 object로 생성되지 않는것들)를 제외하면 "포인터 밖에 없는 언어이다"라는 명제가 성립되게 된다. 사실 여기서 포인터라고 함은 C 의 그것과는 조금 다른 reference(참조)이긴 하지만...

    "즉, 자바의 클래스형의 변수는 모두 포인터이다."

    2.2 null은 객체인가? #

    Java에서 공참조(힙에 실제로 참조되는 object가 없는 참조)의 경우는 당연히 객체가 붙어 있지 않다. 그러나, Java API 레퍼런스의 NullPointerException 항에는 다음과 같이 기술되어 있다.

    "object가 필요한 경우 application이 null을 사용하려고 하면 throw된다. 가령 다음과 같은 경우이다."
    • null object의 instance method 호출
    • null object의 field(member variables)에 대한 액세스 또는 그 값의 변경
    • null의 길이를 배열처럼 취득할 경우
    • null의 slot을 배열처럼 액세스 또는 수정
    • null을 Throwable처럼 throw 할 경우

    위에서 null object라는 말이 등장하는데 이는 공참조에 객체가 붙어 있지 않은 것이 아니라 null을 가리키는 객체라고 볼수 있다. 즉, 공참조라는 것은 JVM에서 봤을때 아무것도 참조하지 않는것이 아니라 null이라고 하는 object를 참조하고 있는것이다. 그러나 JSL 4.3.1에서는 다음과 같이 나와있다.

    "참조값(reference)은 이러한 객체의 포인터나 어떤 객체도 참조하지 않는 특수한 null참조가 된다"

    즉, 공참조는 어떤 객체도 참조하지 않는다고 단정하고 있다. 하지만 '==' 연산에 있어 두개의 객체가 모두 null이거나 동일한 객체 또는 배열 참조의 경우 true라고 되어있는것으로 봐서 서로 다른 두 객체가 동일한 null을 참조하고 있으므로 true가 된것이 아닌가 하는 생각을 할 수 있다.

    즉, null이 Object의 instance 형태는 아니지만 개념적으로 봤을때 null도 object라고 봐야 하지 않을까?

    2.3 String에 대하여 #

    String Object에 대한 생각.

                String str = "111222";            String a = "111";            String b = "222";            String c = "111";            String d = b;            String t = str.substring(0,3);  //111

    위의 소스를 보고 다음이 참인지 생각해 보자. (==연산자는 포인터의 비교이지 값의 비교가 아님)

    1. str == (a + b) ==> 이것은 두개의 참조와 하나의 참조를 비교했으므로 당연히 false이다.
    2. a == b ==> 이것은 당연히 false
    3. d == b ==> 이것은 동일한 reference이므로 true
    4. a == t ==> a 와 t 는 둘다 값이 "111"이다. 하지만 이것은 서로 다른 참조를 가져 false이다. 그렇다면 다음 5번도 false일까?
    5. a == c ==> 이것은 true이다. 아.. 4번과 혼란스럽다. 이것이 참인 이유는? ==> 이것의 해답은 JSR 3.10.5에 다음과 같이 나와 있기 때문이다.

    "동일한 내용의 문자열 리터럴에 관해서는 인스턴스를 하나밖에 생성하지 않는다."

    즉, 위의 a와 c 는 '=' 를 이용하여 문자열 리터럴을 생성하게 되는데 a 에서 이미 만들어 놓았으므로 c에서는 그것을 참조한다.

    2.4 객체지향의 캡슐화 파괴 주의 #

    "object pointer를 반환하는 getter method는 객체지향의 캡슐화가 파괴될 가능성이 있다." 이는 object형의 field(member variable)의 getter에서 이 object를 그냥 반환하면 이 object를 받은쪽이나 참조하고 있는 다른쪽에서 이 object의 내용을 변경하게 되므로 사실 캡슐화(은닉)는 이루어 지지 않았다고 봐야한다.

    "이럴 경우 object를 clone(복제) 하여 반환하지 않아도 되는지를 반드시 생각해 본다."

    object의 복사에는 shallow copy와 deep copy가 있다.

            //(참고)Member에는 두개의 field(Identity Class 형의 ID와 Family Class 형의 family)가 있다.                 /** shallow copy */        Member shallowCopy(){            Member newer = new Member();            newer.id = this.id;            newer.family = this.family;                        return newer;        }                     /** deep copy */        Member deepCopy(){            Member newer = new Member();            newer.id = new Idetity(this.id.getId(), this.id.getName());            newer.family = new Family(this.family.getFamilyName(), this.family.getFamilyInfo());                        return newer;        }        

    위 소스에서 보듯이 shallowCopy 는 object를 복사하여 반환한것 처럼 보이지만, 사실은 Member object만 새로 생성되었을뿐 Member의 field는 newer와 this 둘다 서로같은 힙의 id와 family를 참조한다. 하지만 두번째 method인 deepCopy의 경우 Member의 field를 새로 생성하여 복사하므로 서로 다른 id와 family이다.

    "Java에서는 clone이라는 method가 준비되어 사용되는데 이는 기본이 shallow copy임을 명심해야 한다. deep copy를 사용하기 위해서는 clone method를 overload하거나 따로 만들어 직접 기술해야 한다." (참고) object를 immutable(변하지 않는, 불변의 객체)로 만드는 요령
    1. 모든 field(member variable)를 생성자(constructor)를 이용하여 초기화 한다.
    2. 모든 field는 private으로 선언하고, getter method는 만들되 setter는 기술하지 않는다.

    즉, 값을 변경하기 위해서는 object를 다시 만들어야만 하는 불편은 있지만 안전하게 사용하려 할때 유용하다.

    2.5 배열에 대하여 #

    2.5.1 배열은 object 인가? #

    JVM에서 배열은 object로 취급되어 object와 같이 aload, astore와 같이 bytecode로 기술되어 진다. int[] iarr = new int10; 에서 보는것과 같이 new로 Heap 영역에 object를 생성하므로 object임을 알 수 있다.

    2.5.2 배열의 length는 왜 field(member variable)인가? #

    String의 길이를 구할때는 length()와 같이 method를 이용하는데 배열은 object임에도 불구하고 legth와 같이 필드로 되어있다. '이래도 Java가 완전한 객체지향 언어인가' 라는 의심이 들게 한다. 그렇다면 length가 public이므로 array.length = 100; 과 같이 하면 배열 크기가 변경되나?

    이것은 컴파일 오류가 난다. length는 final이라 값을 변경 할 수 없다는 것이다. 그렇다면 final field로 한 이유는 무엇이냐는 Java News Group에 찾아보면 대부분이 "효율을 위해서"라고 되어 있다. JIT compiler를 사용하지 않는한은 method보다는 field가 빠른건 당연한 것이다.

    그런데 정말 알수 없는것은 byte code에서는 arraylength라는 전용명령으로 컴파일 된다. 즉, length는 Java의 문법이 어찌되었든 JVM레벨에서는 field가 아닌것이 분명하다. 그렇다면 효율을 위해서 field로 했다는 것은 도데체 무슨 소리인가?

    전문가들의 대답에는 이것은 Java의 수수께끼 중 하나라고 대답하는 사람이 많다고 한다.^^;

    2.5.3 final과 배열에 대하여... #

    우리가 흔희 앞에서도 나온바 있지만 final은 값을 변경할 수 없는 것이라고만 생각하지 object로 되어 있을 경우 그 object는 변경 가능하다는 것을 잊곤한다. 배열도 object이므로 마찬가지다.

    final int[] iarr = new int[5]; 일경우 iarr = null; 은 에러가 나지만 iarr[3] = 5; 는 에러가 나지 않는다. 즉, final이 지정되어 있는것은 iarr이지 iarr이 가리키는 곳 배열의 요소가 아닌 것이다.

    2.5.4 "Java에서의 다차원 배열은 존재하지 않는다." #

    가령 2차원 배열 처럼 보이는 int[][] iarr 또는 int[] iarr[] 은 일차원 배열 두개이지 2차원 행열 구조가 아닌것이다. 즉, 두개의 배열은 각각이 배열로 되어 있는 것이지 테이블(행열)형태가 아니다.

    2.6 인수(parameter/argument)전달의 개념 #

    2.6.1 "Java에서 parameter(argument) 전달은 무조건 'call by value' 이다" #

    primitive type의 경우 호출한 쪽의 변수값은 호출 받은 method내에서 값이 변경되어도 변경되지 않는다. reference type의 경우도 reference되는 object에 대해서는 함께 변경되지만 reference pointer는 call by value이다. object를 가리키는 pointer는 call by value로 변경되지만 Heap의 실제 object내용은 변경되지 않는다.

    2.6.2 "C와 같은 언어는 static linking이지만, Java는 dynamic linking이다." #

    따라서 Java는 Class 파일이 처음에 한꺼번에 memory에 읽혀지는 것이 아니라 런타임시에 그것이 필요해 졌을때 읽혀지고 링킹된다. static field의 영역도 Class가 읽혀지는 시점에 비로서 확보된다. 이렇게 되면 최초 가동시간이 단축되고 끝까지 사용하지 않는 Class의 경우 신경쓸 필요가 없어지게 된다.

    따라서 static field는 프로그램이 시작되어 해당 Class가 필요해 졌을때 JVM이 알아서 load/link 해 준다. 즉, static field는 프로그램이 실행되기 시작할 때부터 끝날때까지 계속해서 존재하는 것이라고 보면 된다. (참고) 링킹(linking)의 의미

    link된다는 것은 Class가 memory에 loading될 때 특정 메모리 번지에 loading되는데 이 메모리 번지는 loading될때 마다 다른 번지수에 loading된다. 이때의 메모리 주소값(Java에서는 실제 메모리 값이 아닐 수 있다)을 현재 실행중인 프로그램에서 알 수 있도록 하여 해당 Class에 대한 참조가 가능하도록 연결하는 과정이다.

    정적(static) link라는 것은 이러한 메모리에 대한 주소 정보를 컴파일 시에 compiler가 미리 결정하는 것이고, 동적(dynamic) link라는 것은 프로그램 수행 중 결정되는 것을 의미한다. 정적인 link의 경우 직접적으로 메모리의 번지값이 할당 되는 것이 아니라 offset값(기준위치로 부터의 index값)으로 연결시킨다.

    2.7 GC 에 대하여 잠깐! #

    2.7.1 "Garbage Collection은 만능이 아니다." #

    Java에는 free가 없다. GC가 알아서 해준다. 하지만 GC 수행중에는 프로그램이 멈추는 것과 동일한 증상이 나타나기 때문에 GC가 자주 발생하지 않도록 프로그램 해야 한다. 서비스 되고 있는 시스템에서도 가끔 시스템이 응답이 늦어지는 시점이 있는데, 이는 GC가 수행되고 있는 중이 대부분이다.

    그렇다면 GC가 자주 발생하지 않도록 해야 하는데 가장좋은 방법은 무엇일까? 그것은 바로 불필요한 객체를 생성하지 않는 것이 아닐까?

    개인적으로 Java에 free가 없는것이 너무나 든든하게 느껴진다. 이유는 두개의 변수가 Heap내의 하나의 object를 reference하고 있을 경우 실수로 하나의 변수만 free해 버리면 나머지 하나는 dangling pointer라하여 reference pointer가 모르는 사이데 사라져 버려 곤경에 처하는 것을 예방해 주기 때문이다.

    참고로 Object class에는 finalizer라는 method가 있어 GC 수행시점에 호출되는 method가 있지만 이것은 GC가 언제 수행될지 알 수 없으므로 과신하지 말아야 할 것이다.

    2.8 Java Pointer 결론 #

    2.8.1 "결국 Java에는 pointer가 있는 것인가, 없는 것인가?" #

    Java는 Heap내의 Object를 참조(reference)하고 있고, 참조는 결국 개념이 포인터와 같은 것이므로, "Java에는 pointer가 없다"는 것은 어불성설이다.
    // 이부분에 대해 Object를 이해하시면 족히 이런 문제는 사라질것으로 봅니다.
    // 클래스에 대한 인스턴스(object)들은 reference로 밖에 가질(참조될)수 없기 때문입니다.
    // 컴파일러 입장이 아닌 언어 자체의 사상을 가지고 쉽게 이해시키는 것이 좋을것 같습니다.

    JSR 4.3.1을 보면 다음과 같은 말이 나온다.

    "참조값(reference)은 객체의 pointer이거나, 또는 어떠한 객체도 참조하지 않는 특수한 null 참조가 된다"

    또한 java.sun.com의 Java programmer's FAQ에 "Java는 pointer가 없다고 하는데, linked list는 어떻게 만들어야 하나?"라는 질문에 다음과 같이 답변이 나와있다.

    (답변) Java에 관한 많은 오해중에서 이것이 가장 심각한 것이다. 포인터가 없기는 커녕 Java에 있어 객체지향 프로그래밍은 오로지 pointer에 의해 행해진다. 다시 말새 객체는 항상 포인터를 경유해서만 access되며 결코 직접적으로 access되지 않는다. pointer는 reference(참조)라고 불리며 당신을 위해 자동으로 참조된다.

    "Java에는 pointer가 없고 주장하는 모든 서적과 글들은 Java의 reference사양에 모순된다고 봐야 할 것이다."

    3 상속과 interface의 문제점 #

    3.1 상속 #

    3.1.1 상속에 있어서의 생성자(constructor) #

    "child의 default 생성자가 있으면 그 생성자에는 parent의 생성자(super()) 호출이 compile시 자동 삽입된다." 따라서 super() 이전에 다른 코드가 있으면 object가 생성되기 이전이므로 오류를 발생하게 된다.

    3.1.2 "down cast는 본질적으로 매우 위험하다" #

    down cast - child의 type으로 parent를 casting - 는 parent 형태의 type이 정말 child type인지 compile시에는 알 수 없다. 실행시에 type check가 이루어 지므로 runtime시에 ClassCastException이 발생할 가능성이 커진다.

    "프로그래밍시 오류는 가능한한 compile시에 처리하는것이 좋다."

    3.1.3 "추상클래스에 final이 있으면 compile error이다" #

    abstract method가 있는 클래스는 추상 클래스이고 추상클래스는 상속되지 않으면 아무런 의미가 없기 때문이다.

    3.2 interface #

    3.2.1 "interface는 interface일뿐 다중 상속의 대용품이 아니다." #

    interface를 method signature - 추상클래스와 같이 구현부는 없이 선언부만 있는 method - 의 용도로 생각하는것이 Java에서는 옳다. 즉, interface는 final field와 abstract method가 있는 클래스와 동일하긴 하지만 상속의 의미와는 그 용도가 다르다. 공통된 type을 정의하는것으로 보는것이 맞는 의미일 것이다.

    또한 interface는 클래스를 재이용하기 위해 상속을 사용하여 캡슐화의 파괴를 수반하는 것을 방지하는 기능이있다. 상속을 사용하면 모두 구현후 마치 소스 코드가 여기저기 천 조각을 주워 모아 만든 '누더기'같이 보이는 것에 한숨을 쉰 경험이 있을 것이다. 이 부분을 interface로 구현하면 보다 깔끔한 코드가 나오게 된다. 물론 public과 protected를 적절히 잘 사용해도 되긴 하지만 말이다.

    하지만 상속은 메소드 오버라이드한 경우 클래스를 마음대로 개조해 버린 셈이 되므로 어디선가 묘한 모순이 발생하게 될 가능성도 높아질뿐 아니라 추상클래스의 경우 실제 구현부가 어디에 위치하는지도 에매하게 느껴질 수 있어 불안한 코드가 되고 만다.

    3.3 상속 제대로 사용하기 #

    "그렇다면 제대로 된 상속은 어떻게 판단할 수 있을까?"

    상속은 'is a'관계가 성립해야 올바르다. 즉 '서브클래스(자식) is a 슈퍼클래스(부모)'가 성립해야 한다. 예를 들면 Red is a Color는 올바른 명제이지만 Engine is a Car는 'has a'관계이므로 상속이라고 볼 수 없다. "따라서 'has a'관계는 상속이 아니므로 composition과 delegation을 이용하면 된다."

    composition은 '객체를 field가 갖게 하는 방법'을 의하므로 'has a'관계가 정확히 성립한다. "상속 대신 composition과 delegation(조작이나 처리를 다른 객체에 위임)을 사용하면 다음과 같은 이점이 있다."

    1. 상속에서는 슈퍼클래스가 허용하고 있는 조작을 서브클래스에서 모두 허용해야 하지만, composition과 delegation에서는 조작을 제한할 수 있다.
    2. 클래스는 결코 변경할 수 없지만, composition하고 있는 객체는 자유롭게 변경할 수 있다. 예를 들면 학생 클래스가 영원이 학생이 아니라 나중에 취직을 하여 직장인 클래스가 될수 있다.
    상속을 composition과 delegation으로 변경하는 요령은? 여기서 Shape를 상속한 Polyline과 Circle을 변경한다면 다음과 같다.
    1. Shape(부모)의 공통된 내용을 구현한 구현 클래스(ShapeImpl)를 만든다.
    2. Polyline과 Circle 클래스에서 ShapeImpl을 composition하고 부모와 공통되지 않는 method를 각각 위임 받는다.
    3. ShapeImpl 클래스의 method를 추출한 ShapeIF interface를 작성하고 Polyline과 Circle에서는 implements 한다.

    4 package와 access 제어에 관한 이해 #

    4.1 package #

    4.1.1 "package는 '계층구조' 인가?" #

    처음 Java를 접하면서 package에 대해 이해할때 마치 파일시스템과 같은 계층구조라고 이해하게 되어 ' import /test/*.class '는 왜 안되는지 의아해 했던 기억이 있다. 그리고 부모 directory에 있는 클래스에서 왜 자식 directory에 있는 Class를 import없이 사용할 수 없는지도 이상했다.

    즉, package에서 동일 부모라도 서로 다른 package는 완전히 별개의 package였던 것이다. 이 부분에 관해서는 JLS 7.1 에서 다음과 같이 기술되어 있다고 한다.

    "package가 계층적인 이름 구조로 되어 있는 것은 관련된 package를 일정 규약에 따라 체계화하기 위해서이고, package 안에서 선언되어 있는 top-level형과 동일한 이름을 가진 서브 package를 갖는 것이 금지되어 있는 점을 제외하면 특별한 의미는 없다."

    즉, Java에서는 package이름을 계층적으로 명명할 수 있을뿐 package구조 자체에는 어떤 계층적인 의미 부여도 할 수 없는 것이다. 다시 말해서 Java에서는 package이릉을 계층적으로 명명할 수 있을 뿐 구조자체는 평평한 것이다. 실제로 바이트 코드의 내용을 보면 깨어진 내용중에 java/lang/String과 같이 완전한 한정이름을 class이름으로 사용됨을 알 수 있다.

    4.1.2 "compiler 가 인식하는 class검색 순서(소스코드내 클래스가 발견될 경우 그 클래스의 위치를 찾는 순서)" #

    1. 그 class자신
    2. 단일형식으로 임포트된 class
    3. 동일한 패키지에 존재하는 다른 class
    4. 온디멘드 형식(..* 형태) 임포트 선언된 class

    4.2 access 제어 #

    public은 다른 package에서 참조 가능하고, 무지정할 경우 동일한 package내에서만 참조 가능하다.

    4.2.1 "interfacde member의 access 제어" #

    interface의 member field/method는 모두 public이다. interface member에 protected나 private을 지정할 수는 없다. 또한 public을 지정할 필요도 없다. 지정해도 상관없지만 JLS 9.4에서는 다음과 같이 명시되어 있다.

    "interface의 method에 대하여 public 수식자를 지정하는 것이 허용되고 있지만, style로서는 전혀 권장할 수 없다."

    즉, interface member는 모두 public이라 되어 있는 것이다. 또한 James Gosling도 집필에 참가한 '프로그래밍 언어 Java 3판'에서는 다음과 같이 기술되어 있다고 한다.

    "public이 아닌 member를 interface에게 갖게 하는 것은 아무런 의미가 없다. interface의 member에 대한 access제어에 interface 자신의 access 제한을 적용하는 것이므로 이것은 아무런 의미가 없다."

    4.2.2 그렇다면 interface를 다른 package에 대하여 숨기고 싶으면 어떻게 하는가? #

    그것은 interface 자체 선언에 public이 아닌 것을 적용하면 되는 것이다. member별로 제어할 수 없어 불편한 면도 있지만, 나름대로 그럴 듯한 규칙이다. 하지만 이것은 정말 이상한 논리가 아닐수 없다. public이 아닌 interface에 public method가 무슨 의미가 있는지 알 수 없기 때문이다. 이 interface를 구현한 클래스에서도 method는 모두 public이 되어야 하는데, 이것도 아무래도 이상하다.

    5 기타 Java 기능 #

    5.1 Thread #

    5.1.1 "Multi Thread에서는 흐름은 복수이지만 data는 공유될 수 있다." #

    Multi processing에서는 흐름은 복수이지만 data는 독립되어 있다. 하지만 Multi Thread에서는 Heap과 static영역에 관한 한 2개 이상의 Thread 사이에 공유가 이루어 진다. 따라서 2개 이상의 Thread에서는 동일한 static field를 참조할 수 있고, 동일한 객체에 접근할 수도 있다. 그러나 stack만은 Thread별로 독립되어 있다. stack은 method에 들어가는 시점에 확보되고 빠져 나오는 시점에 확보되고 빠져 나오는 시점에 Free 되므로 2개 이상의 Thread에서 공유할 수는 없는 것이다.

    5.1.2 "Thread는 객체와 직교하는 개념이다." #

    Multi Thread는 어디까지나 Thread라는 처리 흐름이 여러 개 존재할 수 있다는 의미이다. 요약하면 다음 3가지 이다.
    1. Multi Thread에서는 Thread라는 처리 흐름이 2개 이상 존재할 수 있다.
    2. 어떤 Thread에서 움직이기 시작한 method가 다른 method를 호출 했을때 호출된 측의 method는 호출한 측의 method와 동일한 Thread에서 동작한다.
    3. Thread의 경계와 객체의 경계는 전혀 관계가 없다. 즉, Thread와 객체는 직교하고 있다.

    5.1.3 "Synchronized 의 이해" #

    Multi Thread 기반의 programming시에 synchronized를 놓쳐 자주는 일어나지 않으나 뭔가 잘못되어 가는것을 경험한 적이 있다. 즉, 이것이 원인이 되어 버그가 발생한 경우 그 버그는 재현성이 지극히 낮아지기 때문에 꽤 고생을 하게 된다. 이런 사태가 발생하게 되면 버그의 원인을 찾기가 어렵게 되고 해당 application은 언제 발생할지도 모르는 오류가 있는 상태 그대로 운영되기 때문에 심각성이 내포되어 있다고 할 수 있다.

    이러한 사태를 방지하기 위해서는 critical section을 2개 이상의 Thread가 동시에 실행되지 않도록 '배타 제어'를 해야한다. 그 키워드가 바로 synchronized이다.

    synchronized에는 synchronized(obj){} 형태와 method에 synchronized 를 붙이는 두가지 방법이 있는데, 이 둘은 범위만 같다면 같은 의미이다. 예를 들어 설명하면, 아래의 소스에서 method1()과 method2()는 동일하다.

            synchronized void method1(){            ...        }                void method2(){            synchronized(this){                ...            }        }

    이렇게 동일한 의미를 두가지로 만든것은 method단위로 synchronized를 걸 일이 그만큼 많다는 것을 의미한다. 많이들 오해하고 있는 부분이 위의 소스에서 알수 있듯이 method에 synchronized를 사용한다는 것은 '그 객체에 해한 조작은 동시에 하나의 Thread라는 것이지 method 호출이 하나의 Thread가 아닌것이다'

    그렇다면, Thread A가 obj의 Lock을 설정하고 있는 상태에서 다시 한번 Thread A 자신이 obj의 Lock을 설정하면 어떻게 될까? 이 경우 Thread A는 이미 이 obj에 대하여 Lock을 보유하고 있으므로 기다리지는 않아도 된다. 위의 소스에서 method1에서 method2를 호출한다면?

    method1에서 이미 obj의 Lock을 보유 했으므로 method2의 synchronized(this) 부분에서는 Lock을 기다리지 않아도 된다.

    즉, Lock의 기준이 특정Thread에 있어서 Lock의 기준이 method가 아닌 object인 것이다. 이 규칙 덕분에 synchronized method도 재귀호출이 가능해지고, synchronized method가 동일한 instance의 synchronized method를 호출할 수 있는 것이다.

    주의할 점은 static method에 synchronized가 있다면 static은 this참조가 없다고 위에서 설명하였으므로, 이 클래스의 Class 객체를 Lock하게 된다. 기준이 xx.Class가 되는 것이다.

    5.1.4 "Thread 사용법의 정석은?" #

    Thread 사용법에는 다음 두가지의 정석이 있다.
    1. Runnable을 implements하고 Thread의 참조를 보유(composition) 하는 방법. 이경우는 단지 Runnable만 implement함으로서 해결되는 경우가 대부분이긴 하지만, 그 class 내에서 해당 class의 Thread를 조작하게 된다면 composition한 Thread 객체에 delegation하면 된기 때문이다.
    2. Thread class를 상속하는 방법. JDK의 소스를 보면 Thread class에는 Runnable을 implements 하고 있다. 그리고 run method는 native method이다. 따라서 Thread를 상속한 모든 클래스는 사실 Runnable을 implements하고 있는 것이다. run method는 abstract가 아니므로 구현되어 있고 우리는 이를 오버라이드하여 사용하고 있다. 이 방식을 사용하면 Thread의 method를 안팍으로 자유롭게 호출할 수 이지만, 이미 다른 class를 상속하고 있다면 이 방식을 사용할 수는 없다.
    JDK API Reference의 Runnable에 과한 설명중에 다음과 같은 내용이 있다.

    "Thread class의 method중 run method만을 오버라이드하여 사용하는 경우는 Runnable interface만 implements하여 사용하면 된다. 왜냐하면, class의 기본적인 동작을 수정 또는 확장하지 않는한 그 class를 sub class화 하는 것은 바람직하지 않기 때문이다."

    그렇다면 위에서 언제나 1)번 방식을 사용하면 되는 것 아닌가 라는 의문이 생기게 된다. 왜 귀찮게 2)의 방법을 고민하는 것인가, 극단적이긴 하지만 만일에 사태에 이 클래스가 다른 클래스를 상속받게 되는 경우도 있을수 있는데.

    하지만 이것은 아닐것이다. 만약 이렇다면 Thread class가 Runnable을 implements할 필요가 없었을 것이기 때문이다. 또한 Thread는 생성자의 인수로 Runnable의 reference를 취득한 후 계속해서 그것을 보유한다는 것도 이상하다. Thread에 있어 Runnable이 필요한 것은 start() 때 뿐이므로 start()의 인수로 Runnable을 건네줘도 좋을 것이다.

    그럼에도 불구하고 굳이 Thread에서 계속적으로 Runnable을 보유하고 있는 것은 Runnable객체와 Thread를 강하게 결합시키려는 의도 때문이다. 이것은 의도적으로 위의 2)의 방법을 권장하는 듯한 느낌을 받게 하는듯 하다.

    그렇다면 API Reference의 말은 단지 상속을 피하라는 의미만 있는 것인가? 마지막으로 한가지 추정이 되는 부분은 Thread에는 suspend()나 stop()등과 같은 method가 현재 모두 deprecate되었다. 또한 sleep()이나 yield()는 모두 static method이므로 굳이 Thread 객체를 보유할 필요가 없다.

    그렇다면 위의 1)의 방법에서 Thread객체를 composition할 필요가 없어진다.

    "그렇다면 Thread를 아무도 보유하지 않고 Runnable만 implements한 방식이 최선인가?"

    무엇이 정답인지 도무지 알길이 없다. ^^;

    5.2 Exception #

    5.2.1 "finally 절은 반드시 어떠한 경우에도 실행되는가?" #

    try ~ catch 문의 finally 절은 'loop라면 break, method라면 return 절'을 만나도 뒤에 있는 finally절은 수행된다. 하지만 다음의 경우는 그렇지 않다.

            try{            ...            System.exit(1);        }catch(...){        }finally{            ... //이 부분은 실행되지 않는다.        }

    5.2.2 "예외의 종류 3가지 (Error, RuntimeException, 그밖의 Exception)" #

    5.2.2.1 Error #
    이에 관해선 JLS 11.2.1에 다음과 같이 기술되어 있다. "체크되지 않는 예외 클래스(Error와 그 Sub class)는 프로그램안의 다양한 위치에서 발생할 가능성이 있으며, 회복이 불가능하기 때문에 컴파일시 체크되지 않는 것이다. 이러한 예외를 프로그램에서 선언한다고 해도 난잡하고 무의미한 것이 될 뿐이다."

    Java의 클래스 librury에서 Error의 sub class를 살펴봐도 AWTError, LinkageError, ThreadDeath, VirtualMachineError 등 'catch해도 소용 없을 것' 들 뿐이다. (OutOfMemoryError는 VirtualMachineError 아래에 위치한다.)
    5.2.2.2 RuntimeException #
    위의 Error 이외의 Exception들은 application에서 catch할 가능성이 있는 예외들이다.(버그가 없으면 발생하지 않는 예외들) 그리고 RuntimeException은 '어디서든 발생할 가능성이 있는 예외'이다. RuntimeException의 sub class로는 NullPointerException, ArrayIndexOutOfBoundException, ClassCastException 등을 들 수 있다. '이러한 예외는 버그가 없는 한 발생하지 않으므로 일일이 throws 를 작성하지 않아도 된다.'

    프로그램에 버그가 없는 한 발생할 수 없는 예외가 발생한 경우 C 언어와 같이 영역 파괴가 일어나기 쉬운 언어라면 프로그램 전체를 종료시키는 것이 정답이겠지만, Java와 같이 영역파괴가 일어나지 않도록 실행시 체크(JVM Classloader의 formal verification process)를 하고 동적으로 프로그램을 load하는 언어에서는 국소적인 NullPointerException 때문에 프로그램 전체를 중지시켜서는 안 될 것이다.

    따라서, RuntimeException은 catch하지 않는 것이 바람직하다고 볼 수 있다. 버그가 있는 프로그램은 신속히 종료시키는 것이 대부분의 경우 최선의 방책이라 생각하기 때문이다.
    5.2.2.3 그밖의 Exception #
    위의 RuntimeException이외의 Exception의 sub class는 사용자의 잘못된 조작 등으로 인해 프로그램에 버그가 없어도 발생할 가능성이 있고 그에 대하여 프로그램이 확실히 대응해야 하는 경우에 사용된다. 예를 들면 FileNotFoundException등이다.

    그런데 개발하다 보면 이상하고 의아한 것이 하나 있다. 숫자 부분에 문자를 넣었을때 발생하는 NumberFormatException이다. 이것은 이상하게도 RuntimeException의 sub class이다. 이것은 RuntimeException이 아니었으면 하는데 NumberFormat체크는 Runtime시에만 가능한 모양이다.

    5.2.3 "OutOfMemoryError는 어떻게 처리해야 하는가?" #

    예전에 Swing에서 Tree구조를 이용하는 프로젝트를 한적이 있다. 이때 Tree에 branch와 node가 무수히 생기자 JVM은 OutOfMemoryError를 내뱉었다. 이에 급한 마음에 OutOfMemoryError를 catch하여 사용자에게 재시작을 요청하는 Dialog를 띄우도록 수정하였다면 이 Dialog가 과연 떳을까? 현재 메모리가 부족한 판에 Dialog를 띄울 메모리가 남아있질 않았던 것이다. 다행히 Dialog가 떴어도 작업은 계속되지 못했을 것이다. NullPointerException가 나기 때문이다.

    원인은 나중에 찾았는데, Tree구조에서 부모부터 자식들을 붙이는 순으로 Tree를 구성하는데 자식들을 줄줄이 붙여나가다가 메모리 부족현상이 발생하였고 NullPointerException은 자식이 없으니 클릭하는 순간 null을 반환하여 발생하였던 것이다.

    OutOfMemoryError의 가장 좋은 해결책은 불필요한 객체를 만들지 않는 것이었다. 그리고 Tree생성시에도 자식부터 만들고 부모를 만드는 순서로 프로그램을 수정하여 프로젝트를 정상적으로 마칠수 있었다.

    마지막에 드는 심정은 프로그램이 OutOfMemoryError를 일으키는 원인이 과연 이렇게 구성되어 발생했는지 어떻게 알수 있을까 하는 의문이다.

    5.3 Object Serialize #

    Java에서는 ObjectOutputStream의 writeObject() method에 데이타 구조 저장소의 참조만 건네주기만 하면 그 안에 있는 모든 객체를 1차원 stream으로 출력해 준다. (파일이나 ByteArrayOutputStream을 이용한 메모리로) 단, static field는 Serialize되지 않는데 이는 Serialize의 대상이 instance 객체뿐이기 때문이다.

    5.3.1 "Serialize를 위해서는 marker interface인 java.io.Serializable interface를 implements해야한다." #

    여기서 marker interface는 java.lang.Cloneable과 같이 method와 field의 정의는 없지만 객체 Type을 위한 interface이다. 예전에 Serialize를 이용하여 데이타를 유지하는 프로젝트를 한 적이 있는데 그때 생각했던것이 '모든 class들이 기본적으로 Serializable을 implements하고 있으면 편할텐데..'라는 생각이었다. 하지만 이것은 상당히 위험한 발상이었다.

    Serializable이 기본으로 implements되어 잇으면 엉뚱한 객체까지 Serialize되고 그것을 알아채지도 못하는 사태가 일어날 가능성이 높다. Serializable이 optional인 이유는 이러한 이유 때문이리라..

    5.3.2 "super class는 Serializable이 아닌데 sub class만 Serializable인 경우의 문제점" #

    Serialize을 이용하여 프로젝트를 할때 한번쯤 실수할 수 있는 부분이 상속된 class의 Serialize이다. 컴파일 에러도 없고 Deserialize도 잘 되었다. 하지만 키가 되는 값이 null과 0이었다. 영문을 몰라 다른곳을 헤매여도 보다가 결국 찾은 원인은 부모의 field는 Serialize되지 않는다는 것을 알게 되었다. transient와 마찬가지로 형식별 default 값으로 채워졌었다. 이는 컴파일과 실행시 아무런 오류없이 실행되어 나를 힘들게 하였기에 Java가 원망스러웠던 기분좋은 추억이다. ^^;

    5.3.3 "transient field의 복원(?)관련" #

    Serialize를 이용한 프로젝트를 할때는 writeObject와 readObject를 이용하여 기본적으로 제공하는 Serialize를 customizing할수있다.

    Serializable에 대한 API reference에도 다음과 같이 나와있다.

    "Serialize와 Deserialize에 대한 특별한 handling을 위해서는 다음 두개의 특별한 메소드를 구현하면 된다."

    private void writeObject(java.io.ObjectOutputStream out) throws IOException;private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

    이 두 method가 private으로 되어 있는 것을 보고 처음에는 의아해 했었던 기억이 있다. 이를 protected나 public으로 하면 제대로 동작하지 않는다. 이는 override가 이니기 때문이다. 사실은 속에서 reflectiond을 이용하여 강제적으로 호출되고 있는것이다. reflection에서는 private method까지 찾을 수 있기 때문이다.

    또한 private으로 한 가장 큰 이유는 Serialize를 객체자신이 직접 해야 안전하다는 의미도 있지 않을까 하는 생각도 든다. 다시 본론으로 들어가서 transient를 복원하는 것에 얘기를 하자면, 사실 transient는 Serialize대상에서 제외되는 것인데 복원을 할 수 있다는 말이 안된다. 하지만 프로젝트를 진행하다 보면 logic상 가능한 경우가 많이 있다.

    즉, 모든 field를 Serialize하지 않고 필요한 것만 하고 특정 field는 Serialize한 field들을 이용하여 복원하는 방법이다. 또한 Serialize당시의 객체 상태와 Deserialize시의 객체상태가 서로 다를 수 있는 field도 그것에 해당된다. cafeid만으로 나머지 field는 DB에서 읽어오게 한다면 나머지 field는 transient로 처리하고 Deserialize시 readObject()에서 복원하는 것이다.

    5.3.4 "Stack Overflow에 주의하라!" #

    Serialize를 하다보면 참조로 연결된 객체를 recursive하게 거슬러 올라가며 이것이 너무 깊어지면 Stack Overflow가 발생한다. 가령 linked list같은 경우이다. 이것을 Serialize하면 그 요소수만큼 recursive 호출이 발생한다. 과거(JDK1.3.0시절) 프로젝트 당시 JVM이 5111에서 Stack Overflow가 발생했던 기억이 있다.

    물론 실행시 java option에 -Xss 를 이용하여 statck 크키를 조절할 수 있지만 이것은 개발자가 아닌 실행하는 사람들에게 부담이었다. JDK의 LinkedList class의 소스를 보면 writeObject()와 readObject()를 다음과 같이 변경하고 있다.

            private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException {            s.defaultWrtieObject(); //이 코드는 무조건 들어가게 되는데 이곳 소스의 System.arraycopy()에서 overflow발생한다.                        s.writeInt(size);   //이부분이 실제 추가되어 Stack Overflow를 예방한다.                        for(Entry e = ...)                s.writeObject(e.element);            }            ...        }                //readObject()도 이와 같은 개념으로 변경되어 있다.

    5.4 "nested class / inner class / 중첩클래스" #

    5.4.1 "중첩클래스의 개념" #

    개인적으로 중첩클래스를 어떠한 경우는 사용하지 않으려 한다. 사용하기가 만만치 않고 코드 읽기가 힘들어 지기때문이다. 하지만 '어떤 클래스 내에서 은폐할 목적으로 사용하는 클래스가 있다면 이것을 사용해야 한다' 실제로 Java의 AWT 클래스 Event Handler를 비롯하여 많은 클래스에서 중첩클래스를 사용하고 있다. 또한 내부 class는 그것을 둘러싸는 class의 instance(enclosing object라고 하는)의 field를 참조 할수 있는것도 장점이다. 하지만 이는 내부클래스가 아닐경우 부부 클래스를 new해서 사용하는것과 별반 다를께 없지 않은가.

    5.4.2 "내부클래스는 부모의 참조를 몰래 보유하고 있다." #

    내부 클래스의 instance는 부모의 instance에 대한 참조를 몰래 보유하고 있기 대문에 위에서 얘기한 부모의 field를 참조할 수 있는 것이다. 그러므로 static method에서는 내부클래스를 생성할 수 없다. 다음 예를 보면 바로 알수 있다.

            class Test{            class InnerClass {                int i;                ...            }                        public static void main(String[] args){                InnerClass icls = new InnerClass();                ...            }        }

    이 소스를 compile하면 다음의 오류가 발생한다. "non-static variable this cannot be referenced from a static context..." main method는 static이므로 this를 참조할수 없다는 것이다. 이는 InnerClass가 new 되면서 외부 클래스 Test의 this를 보유해야 하는데 여기서 static을 만나니 오류를 표출시킨것이다. 물론 일반 instance method에서는 오류가 나지 않는다.

    5.4.3 "local inner class에 대하여" #

    local inner class라 함은 method내에서 선언된 inner class이다.

            public class OuterClass {            public int get(){                int i = 9;                int id = 99;                int id2 = 99;                final int id3 = 100000;                                class LocalInnerClass {                    int id = 100;                                        LocalInnerClass(){                        System.out.println("LocalInnerClass");                       }                                        int getId(){                        return id3 + id;                    }                }                                   LocalInnerClass lic = new LocalInnerClass();                return id + lic.getId();            }                           public static void main(String[] args){                OuterClass outer = new OuterClass();                System.out.println("id = " + outer.get());                  //결과 값은 "100000(id3) + 100(LocalInnerClass.id) + 99(OuterClass.get())" 인 100199가 나온다.            }                    }

    위 소스의 LocalInnerClass는 get() 이라는 method에서만 보이는 class이다. 그리고 특이할 만한 부분이 OuterClass의 get() method에서 final로 선언된 id3이 LocalInnerClass에서 참조 가능해 진다. id2를 참조하면 compile error가 나지만 final로 선언된 것은 오류가 나지 않는다.

    이는 local variable은 method에서 나오는 순간 사라지는데, local inner class는 local variable보다 수명이 조금더 길기 때문에 final만 허용한 것이다.

    5.4.4 "anonymous class(무명클래스)에 대하여" #

    무명 클래스는 말그대로 이름이 없는 클래스이다.

            class AnonymousTest {            private interface Printable {                void print();            }                        static void doPrint(Printable p){                p.print();            }                        public static void main(String[] args){                doPrint( new Printable(){                            public void print(){                                System.out.println("this is new Printable print()");                            }                         });            }        }

    위 소스의 "doPrint( new Printable(){" 부분이 무명클래스 이다. compile을 수행하면 AnonymousTest$Printable.class, AnonymousTest$1.class, AnonymousTest.class 세개의 클래스가 생긴다. 여기서 AnonymousTest$Printable.class는 Printable interface이고 AnonymousTest$1.class이 무명클래스이다.

    이 소스를 보면 처음에 드는 의심이 Printable interface를 new 했다는 것이다. 여기서 굳이super class(이 소스에서는 interface)를 저정해야 하는 이유는 아무것도 상속하지 않는 무명 클래스의 instance를 만들어 봐야 의미가 없기 때문에 이렇게 한듯하다.

    "무명클래스는 어떤 class나 interface를 상속/구현 해야만 그 instance를 사용할 수 있는 것이다" 이처럼 무명 클래스를 사용하면 어떤 절차(수행)를 다른 method의 인수로 건네줄 수 있게 된다. 하지만 간단한 로직만 구현처리해야 한다.

    "무명클래스는 조금만 복잡해져도 급격히 소스의 가독성이 떨어지게 되므로 남용하지 않는 것이 바람직하다"

    6 이래도 Java가 간단한가? #

    6.1 method overload 에서의 혼란? #

    6.1.1 "overload란 이름이 가고 인수가 다른 method에 compiler가 다른 이름을 붙이는 기능" #

    overload를 구현하면 bytecode로 변환시 다른 이름으로 method가 변환되어 별개의 method로 처리된다. 이를 JVM에서 method descripter라 하여 Oolong asembler로 변화시 다른 형태의 method가 된다. 예를 들어 "void get(double d, long l)" 은 "get(DJ)V"로 변경된다. 여기서 D는 double, J는 long, V는 void를 의미한다.

    그런데 여기서 "get(DJ)" 부분만 method 이름이므로 return type이 다른 동일 method는 overload 할 수 없다. 따라서 overload는 정적(compile시 결정)이라는 명제가 성립니다. 그래서 동적으로 사용되면 compile시 오류를 표출한다. 아래의 소스를 보자. 여기에는 IFS라는 interface와 이를 implements한 Impl1, Impl2 라는 class가 있다.

            //IFS.java        interface IFS {            public String getName();        }                //Impl1.java        class Impl1 implements IFS {            public String getName(){                return "Impl1";            }        }             //Impl2.java        class Impl2 implements IFS {            public String getName(){                return "Impl2";            }        }                   //main이 있는 OverloadTest.java        public class OverloadTest {                static void pr(int i){                System.out.println("pr_int : " + i);               }                        static void pr(String s){                System.out.println("pr_string : " + s);               }                        static void pr(IFS ifs){                System.out.println("pr_string : " + ifs.getName());            }                        static void pr_run(Impl1 i1){                System.out.println("pr_run : " + i1.getName());            }                        static void pr_run(Impl2 i2){                System.out.println("pr_run : " + i2.getName());            }                        public static void main(String[] args){                OverloadTest test = new OverloadTest();                test.pr(10);                test.pr("Jeid");                                   IFS ifs1 = new Impl1();                test.pr(ifs1);                                IFS ifs2 = new Impl2();                test.pr(ifs2);                                //pr_run(ifs1);                //pr_run(ifs2);            }        }

    위의 소스를 수행하면 정상적으로 compile이 될것인가?

    당연히 잘 된다. pr()은 overload를 잘 구현했다. 하지만 소스 하단의 두 주석문을 풀면 어떻게 될까? 이는 compile오류를 낸다.

            OverloadTest.java:36: cannot resolve symbol        symbol  : method pr_run (IFS)        location: class OverloadTest                pr_run(ifs1);                ^        OverloadTest.java:37: cannot resolve symbol        symbol  : method pr_run (IFS)        location: class OverloadTest                pr_run(ifs2);                ^        2 errors

    실제 위 둘의 pr_run method는 bytecode로 변환시 "pr_run(Lpackage_name.IFS)V"로 동일하게 생성된다. 따라서 compile시에 오류를 표출한다. 이 소스를 보면 알 수 있듯이 "method overload는 정적(compile시)으로 미리 결정되며, 동적(실행시판단)으로 사용할수 없다."

    6.1.2 "그렇다면 overload에서 실제로 혼동되는 부분은 무엇인가?" #

    다음 소스를 보고 실제로 수행되는 method를 찾아보라.

            class OverloadTest2 {            static int base(double a, double b){ ... }  //method A                        static int count(int a, int b){ ... }  //method B            static int count(double a, double b){ ... }  //method C                        static int sum(int a, double b){ ... }  //method D            static int sum(double a, int b){ ... }  //method E        }

    • base(3,4) 를 호출했을때 수행되는 method는? => 당연히 method A (3과 4는 정수라도 double이 되므로 정상적으로 수행됨)

    • count(3,4) 를 호출했을때 수행되는 method는? => B와 C중 갈등이 생긴다. 이럴경우 JVM은 가장 한정적(more specific)한 method를 찾는다. 여기서 3과 4는 정수형에 가까우므로 method B 가 호출된다.

    • count(3, 4.0) 을 호출했을때 수행되는 method는? => 이것은 4.0 이 double이므로 method C 가 더 한정적이므로 method C 가 호출된다.
    • sum(3,4.0) 을 호출했을때 수행되는 method는? => 이것은 당연히 type이 일치하는 method D.
    • sum(3,4) 를 호출했을때 수행되는 method는?? => 이런 코드가 소스내에 있으면 다음과 같은 compile 오류를 표출한다.

                 OverloadTest.java:48: reference to sum is ambiguous, both method sum(int,double)               in OverloadTest and method sum(double,int) in OverloadTest match                    System.out.println("sum(3,4) = " + sum(3,4));                                                       ^             1 error

    method D와 method E가 애매하다는 compile 오류이다. 이것은 둘중 어느것이 더 한정적인지 찾을 수 없으므로 bytecode 를 생성 할 수 없다는 것이다.

    "이렇듯 compiler에게 불필요한 오해(혼동)를 초래하는 overload는 사용하지 않는 것이 좋다. 개인적으로 overload를 가능한 사용하지 않으려 하고 필요하다면 인수의 개수가 다른 overload를 사용하는 편이다."

    6.1.3 (참고) 또다른 혼동, overload한 method를 override 하면? #

    overload란 compiler가 bytecode변환시 다른 이름을 붙이는 기능이라는 것을 위에서 설명했다. 따라서 super class에서 overload한 method를 상속하여 override하면 완전 별개의 method를 override한것처럼 JVM은 판단한다. 즉, overload와 override는 직교(전혀상관없는)하는 개념이다.

    6.2 상속/override/은폐 에서의 복잡함 #

    6.2.1 "Java class의 member 4 종류" #

    1. instance field
    2. instance method
    3. static field
    4. static method
    여기서 상속을 하였을 경우 runtime시 객체의 형식에 따라 선택되는 것은? 2번 instance method 뿐이다. 즉, 동명의 member를 sub class에서 선언했을 때 instance method만 override 되고 나머지는 완전 별개의 member가 된다. 따라서 위의 1,3,4는 sub class에서 동일하게 선언했을 경우 별개의 것으로 인식되며 compile시에 무엇을 access 할지 결정된다.

    즉, instance method는 override되지만 instance field/static field는 은폐된다. override는 실행시 객체의 형식에 따라 처리 할당되지만, 은폐의 경우는 compile시에 결정되고 만다.

    6.2.2 "override시 method 이름에 대한 함정" #

    과거에 코딩을 하던중 정말이지 어처구니 없는 경우를 당했다. override 하는 method이름을 잘못써서 황당한(?) 고생을 한적이 있다. super class의 writable()이라는 method를 writeable()이라고 override(?)하였는데 프로그램 수행 중에 writable()이 항상 false가 나오는 것이 아닌가? 그래서 소스를 추적추적 하다 몇시간을 허비했었던 기억이 있다.

    java를 접한지 얼마되지 않았고 요즘같이 eclipse같은 에디터도 없이 메모장에서 코딩하던 시절이라 더욱 고생했던것 같다. 한참 후에야 우연히 스펠링이 잘못된걸 알고 얼마나 황당했던지... 지금 생각하면 이것도 좋은 추억이리라.

    무조건 override 잘 되었을거라 생각 했던 나의 불찰도 있었지만 compile때나 runtime시 아무런 반응을 보이지 않던 Java도 원망스러웠다. 2003년도에 C#으로 프로젝트를 했는데 C#은 상속의 override에 대하여 "override void writalbe().."과 같이 정의시 override를 명시해야 된다는 것을 보고 상당히 마음에 들어 했던 기억이 있다. 가독성도 뛰어날 뿐더러 나의 몇시간동안의 헤메임도 없을 것이기 때문다. Java도 이렇게 확실한 명세였으면 정말 좋겠다.

    6.2.3 "또다른 나의(?) 실수 - 말도 안되는 오타" #

    위의 method이름을 잘못써서 고생하기 이전에 아주 비슷한 고생을 한적이 있다.

    '난 정말 바보인가'라는 생각을 들게 했던 문제였다. 초보 시절에는 왜이리도 오타가 많이 나던지... 요즘은 대충 키보드 두드려도 오타가 잘 안나는데 그 시절에 오타 때문에 느린 CPU에서 컴파일을 몇번을 했는지... 기억을 되살리면 소스는 다음과 같다.

            public class Member {            private int memberNo;                        public int getMemberNo(){                return this.memberNo;            }                        public void setMemberNo(int menberNo){                this.memberNo = memberNo;            }                        ......        }


    위 소스의 Member에는 다른 여러가지 member field가 있는데 DB의 member table에 memberid 컬럼이 memberno로 변경되면서 Member class의 memberId를 memberNo로 변경하게 되었다. 위와 같이 수정하여 배포해놓고 테스트를 하는데 시스템이 완전히 뒤죽박죽으로 돌아버리는 것이 아닌가. 이 경우도 method 이름처럼 몇시간을 헤매었다.

    이번에 argument의 오타로 인한 어처구니 없는 실수였다. setMemberNo(int menberNo)에서 문제가 발생되었던 것이다. 인수의 memberNo를 menberNo로 잘못친것이다. 그래서 memberNo에는 해당 member의 memberno가 아닌 0이 모두 들어갔어던 것이다. 시스템은 memberno를 기준으로 도는 부분이 너무나 많았기에 오류나는 부분도 많았으며 DB에서는 제대로 된 memberno을 읽어 왔으며, compile과 runtime시 아무런 반응도 없었기에, 초보자를 그렇게도 고생시켰나 보다.

    이것도 member field면 무조건 this를 붙이도록 하던지 Java가 인수는 'm_'와 prefix를 붙이도록 Coding Style을 정의- SUN사이트의 Java Coding 규약에는 "Variable names should not start width underscore_ or dollar sign $ characters, even though both are allowed." 와 같이 명시되어 있다 - 했더라면 발생하지 않았을 문제이다.

    또한 C언어나 C#에서 처럼 compile 경고레벨을 높여놓으면 "menberNo는 어디서도 사용하지 않습니다."와 같은 메세지를 보여 줬더라면 고생을 덜 하지 않았을까?

    6.2.4 "static member를 instance를 경유하여 참조해서는 안 된다." #

    예를 들어 ClassA 에 public static int AA 라는 static field가 있을 경우 ClassA.AA 로 접근해야 하는데, 다음과 같이 사용하는 실수를 범한다.(물론 오류는 없지만)

            ClassA a = new ClassA();         int i = a.AA;       //instance를 경유하여 접근        int j = ClassA.AA;  //올바르게 접근

    그럼 왜 굳이 ClassA.AA와 같이 instance가 아닌 class이름을 붙여야 할까?

    static member(static field/static method)는 compile시에 이미 어느것을 호출할 지 결정하기 때문에 위의 a.AA와 같은 것은 static이 아닌것 같은 오해와 혼란만 가져오기 때문이다. 심지어 개인적으로는 동일 class 내 - 위 소스에서 ClassA의 member method - 에서 ClassA.AA라고 사용하는 편이다.

    이는 local variable과 혼동될 염려도 없을뿐더러 AA라는 변수가 static이라는 것도 확실히 알 수 있기 때문이다. 물론 private static 의 경우는 ClassA.BB 와 같이 하지 않고 BB 라고 해도 무방하겠지만 말이다.

    6.2.5 "super keyword는 부모의 this" #

    Java 개발자 대부분은 'super' 에 대하여 그렇게 민감하지 않을 것이다. 그거 super() 나 super.method1() 과 같이 사용되지 그 이상에 대해선 깊이 생각하지 않게 된다. super를 한마디로 정리하면 다음과 같다.

    "super keyword는 instance method등에서 this를 사용할 수 있는 곳에서만 쓸 수 있다. this의 자리에 super라고 쓰면 현재 class의 member가 참조되는 대신 부모 class의 member가 참조되는 것이다."

    6.3 상속에 관한 또 다른 문제 #


    6.4 그밖의 함정 #

    6.4.1 "생성자에 void 를 붙인다면?" #

    생성자에 void를 붙인다면 그 class가 new 될때 그 생성자(?)가 실행될까?? 아래의 'Constuctor'라는 문자열은 출력될까?

            public class ConstructorTest{            void ConstructorTest(){                System.out.println("Constuctor");            }            .....        }

    출력되지 않는다. 물론 compile시 아무런 경고도 없었다. 즉, void가 붙은 ConstructorTest()는 생성자가 아니라 instance method일 뿐이었고 new시에는 default constructor가 실행 되었던 것이다.

    6.4.2 "if / switch 의 함정" #

    Java 개발자라면 대부분이 초보시절에 if 조건절에 '==' 대신 '='을 써본 기억이 있을것이다. 예를 들어 "if( isListenLecture == Student.STUDENT )" 를 "if( isListenLecture = Student.STUDENT )" 로 잘못 쓴 경우이다. 여기서 Student.STUDENT는 boolean type이다. 여기서 isListenLecture는 항상 Student.STUDENT 값을 갖게 되는 버그가 생긴다. 이는 compile시에 아무런 경고도 없다. 이렇게 한번 당하고 나면 앞으로는 '=='를 정확히 쓰게 되거나 아니면 다음과 같이 쓴다.

    "if( isListenLecture )" 또는 "if( !isListenLecture )" 라고 말이다. 이것이 더욱 간결하고 의미도 분명해 지기 때문이다. 또한 다음 소스와 같은 오류도 범하는 경우가 있다. 이는 잘못된 indentation으로 빚어지는 초보의 함정이다.

    이글을 읽는 분께 한가지 당부드리고 싶은것은 여기서 초보라고 다 그런건 아니라는 것이다.

            ....        if( a < 5 )            b = 3;            c = 10;   //이부분은 나중에 추가된 라인이다.                                if( isStudent )            if( isFemale )                sayHello("Hi~~");        else            sayHello("Hello Professor~");

    위의 소스중 c = 10; 이 if( a < 5 )의 참일때 수행된다고 오해할 수도 있고, sayHello("Hello Professor~"); 부분이 if( isStudent )의 else 부분이라고 오해 할 수도 있다. 이것은 전적으로 indentation(들여쓰기)의 불찰로 개발자가 잘못 읽을 수 있는 부분이다. Java Coding Style에서는 if문 다음에 한줄의 코드가 있더라도 {} 를 사용하길 권고한다. 그러면 첫번째 if문과 같은 오류를 방지할 수 있고 두번째 if문에서도 보다 가독성이 생길 것이다.

    이와 유사한 것으로 switch문의 case 절에서 break를 쓰지 않아 항상 동일하게 처리되는 버그도 경험해 보았을 것이다.

    7 Java 기능 적용 몇가지 #

    7.1 대규모 개발에서 interface 분리하기 #

    7.1.1 "interface 분리의 필요성" #

    Java와 같은 객체지향언어에서는 공개해야 할 method만을 public으로 하고, 공개할 필요가 없는 것은 private으로 하여 class의 상세한 내용을 은폐할 수 있게 되어 있다. 그런데 private 부분이 은폐되어 있는것 처럼 보이는가?

    소스를 보면 훤히 들여다 보이는데?

    대규모 개발은 하부 class부터 bottom-up으로 진행하는 것이 이상적인 형태일 것이다. 그런 형태로 개발하면 임의의 시점에서 테스트를 할 수도 있다. 그러나 현실적으로 단기간에 많은 수의 개발자가 붙어서 단시간에 개발을 진행하는 경우가 많다. 또한 서로 호응하는 관계에 있는 class들은 어느쪽이 하부인지 정의하기가 난감할때가 많다. 이런경우 우리는 흔히 package단위로 나누어 개발한다. 하지만 이럴경우 어느정도 코딩이 종료될때까지 테스트하기가 상당히 힘들어 진다. Java에서는 private member와 method 구현까지 하나의 파일에 코딩하는데 개발 중간에 공개하여 다른 개발자가 이용해야 하는 class를 배포할 수 없으므로 동시 개발이 까칠해 진다.

    이 상황에서 다른 package(개발자)에 공개해야 하는 class 부분을 interface로 공개하면 많은 부분 유연하게 된다. 이 interface를 다른 개발자는 개발을 하고 테스트가 필요하다면 TestImpl class를 만들어 하면된다. RMI나 CORBA에서도 Stub은 이런식으로 IDL을 정의한다.

    7.2 Java에서의 열거형 #

    Java에서는 열거형-C의 구조체, 공용체-이 없다. 열거형이 왜 필요하냐고 반문하는 개발자도 있을 것이다.

    하지만 열거형이 없어 곤란을 경험한 개발자도 꽤 있으리라 본다. 최근언어(특히 객체지향 언어) - Java, Eiffel, Oberon등 - 에는 열거형은 포함되어 있지 않다. C#에는 있긴 하지만.

    이런 이유로 Java AWT의 Label class는 다음과 같이 구현되어 있다.(텍스트의 정렬값관련)

            public static final int LEFT = 0;        public static final int CENTER = 1;        public static final int RIGHT = 2;        ...                label.setAlignment(Label.CENTER);        ...

    하지만 위의 소스에는 문제가 있다. setAlignment() method의 인자가 int인 것이다. 만약 위에 정의한 0, 1, 2가 아닌 다른 int 값이 들어가도 compile/runtime시 알수가 없다. 그래서 주석을 달게 되는데, 주석이라 함은 정말이지 최후의 수단이라고 봐야 한다.

    실제로 우리가 개발해 놓은 소스에도 이런부분이 있으리라 예상된다. 이 문제를 어떻게 하면 해결할 수 있을까? Java에서 열거형을 한번 만들어 보자.

            //LabelAlignment.java        public class LabelAlignment {            private LabelAlignment() {} //이는 생성자를 private으로 하여 다른데서는 만들지 못하도록 하기위함이다.                        public static final LabelAlignment LEFT = new LabelAlignment():            public static final LabelAlignment CENTER = new LabelAlignment():            public static final LabelAlignment RIGHT = new LabelAlignment():        }                //변형된 Label.java 의 일부..        public synchronized void setAlignment(LabelAlignment alignment){            if( alignment == LabelAlignment.LEFT ){                ...//왼쪽으로 맞추기..            }else if( ...                ...            }        }        ...

    위에서 작성한 소스는 잘 작동한다. 서로 다른 3개의 instance이므로 reference가 달라 '==' 연산도 가능하고, 훌륭하다.

    하지만 한가지 문제가 있다. LabelAlignment가 Serializable한 class에서 serialize되었다 deserialize 된다면?

    LabelAlignment alignment 는 새로운 instance가 되고 serialize전의 reference와 다른 참조 위치를 갖게 되어 '==' 연산은 버그를 발생시킨다. 그럼 이것만 해결하면 되겠는데, 어떻게 refactoring하면 될 것인가? '==' 연산 대신 equals로 변형하면 되겠는데.

            //LabelAlignment.java        public class LabelAlignment {            private int flag;            private LabelAlignment(int flag){                this.flag = flag;            }                         public static final LabelAlignment LEFT = new LabelAlignment(0):            public static final LabelAlignment CENTER = new LabelAlignment(1):            public static final LabelAlignment RIGHT = new LabelAlignment(2):                        public boolean equals(Object obj){                return ((LabelAlignment)obj).flag == this.flag;            }        }                //변형된 Label.java 의 일부..        public synchronized void setAlignment(LabelAlignment alignment){            if( LabelAlignment.LEFT.equals(alignment) ){                ...//왼쪽으로 맞추기..            }else if( ...                ...            }        }        ...

    하하, Serialize까지 잘 작동한다. ^^;

    여기서 Debug를 고려한다면 0, 1, 2 대신 문자열로 "LEFT", "CENTER", "RIGHT"로 한다면 더욱 명확하지 않을까?

    (주의) 위에서처럼 LabelAlignment.LEFT 라고 쓰기 싫어서 상수 interface를 만들어 그걸 implements 하여 그냥 LEFT 라고 쓰는 것을 뿌듯해 하며 쓰는 개발자들이 있다. 물론 Swing의 소스들을 보다보면 SwingConstants라는 interface에 LEFT를 비롯하여 온갖 잡다한 상수를 집어넣어놓고 여기 저기서 implements해서 사용하고 있다. 이런 코딩 스타일은 '내 스타일이야~' 가 아니라 냄새나는 코드이다.

    LEFT라는 것이 구현한 class에 이미 있을 수 있을 수 있을뿐아니라 구현한 모든 클래스에서 LEFT를 보유하여 SwingConstants.LEFT뿐 아니라 Impl.LEFT로도 사용되게 되어 온갖 혼란을 초래하게 된다. 입력량을 줄이기 위해 interface를 implements 해서는 안되지 않을까?

    7.3 Debug write #

    C에서는 다음과 같이 pre-process로 정의하면 DEBUG라는 식별자를 #define하지 않으면 컴파일후 해당 소스의 부분이 삭제된다.

            #ifdef DEBUG            fprintf(stderr, "error...%d\n", error);        #endif /* DEBUG */

    그럼 Java에서는?

    Java에서는 Pre-process가 없지만 다음과 같이 작성했을때 Debug.isDebug 가 final로 선언되어 있으면 compile후 아래 3줄 모두 삭제 된다.(단 Debug.isDebug 가 false 로 초기화 되었다면 제거된다.)

            if( Debug.isDebug ){            System.out.println("error..." + error);        }

    Java는 compile시 byte code 생성시 final은 정적으로 판단하여 미리 정의하기 때문에 위의 3줄은 삭제될 수 있다. if문과 함께 없어지게 되므로 처리 속도에 피해를 주지 않는다. 단, 주의해야 할 점은 Debug.isDebug 값이 변경되면 이 것을 사용하고 있는 측도 모두 함께 다시 compile해야 한다. bytecode를 다시 만들어야 하기 때문이다.

    그런데, 이 소스를 Debug.write()와 같이 static 으로 하여 이 method내에서 판단하게 하면 편리할텐데. 그리고 class별로 ON/OFF 처리를 할 수 있으면 좋을텐데, 어찌 하면 가능할 것인가?

    그럼 먼저 호출한 쪽의 class이름을 찾아보자. 접근은 Exception의 printStackTrace()로 부터 시작되었다. 하지만 이 소스에는 Exception 객체를 new한 시점에 결정되어 있다. 그래서 부모인 Throwable의 생성자를 확인해 보니 fillInStackTrace() 로 되어있는데 이 method는 native method였다.

    API Reference를 보면 Thread class에서는 dumpStackTrace()라는 method가 있었다. 소스를 보니, 그것도 생성시점이었다. 아무래도 예외방면에서 찾는건 무리인듯 했다.

    그래서 class의 호출계층을 나타내는 java.lang.SecurityManager의 getClassContext() method로 접근하였다. sample 소스는 다음과 같다.

            // 1. GetCallerSecurityManager.java        public final class GetCallerSecurityManager extends SecurityManager {            public Class[] getStackTrace(){                return this.getClassContext();               }        }                // 2. GetCallerClass.java        public final class GetCallerClass {            private static GetCallerSecurityManager mgr;                        static{                mgr = new GetCallerSecurityManager();                System.setSecurityManager(mgr);            }                        public static void writeCaller(String str){                Class[] stk = mgr.getStackTrace();                int size = stk.length;                for(int i = 0; i < size; i++){                    System.out.println("stk[" + i + "] = " + stk[i]);                   }                                   String className = stk[2].getName();                                System.out.println("className is " + className + " : " + str);            }        }                // 3. GetCallerClassMain1 : 호출하는 클래스 예제 1        public class GetCallerClassMain1 {            public static void main(String[] args){                GetCallerClass.writeCaller(", real is 1.");            }        }                // 4. GetCallerClassMain1 : 호출하는 클래스 예제 2        public class GetCallerClassMain2 {            public static void main(String[] args){                GetCallerClass.writeCaller(", real is 2.");            }        }

    위의 3번 주석과 4번 주석 부분을 수행하면 다음과 같은 결과가 나온다.

        className is GetCallerClassMain1 : , real is 1.    className is GetCallerClassMain2 : , real is 2.

    정확히 호출한 클래스를 표현하고 있다. 이것을 비교해서 클래스별 ON/OFF를 구현하면 된다.

    8 Java 5.0 Tiger 에 대하여 #

    Tiger에서는 새로운 개념의 적용이 많은 부분 시도 되었다. 이중 가장 기본이 되는 몇가지를 살펴보자.

    8.1 Working with java.util.Arrays #

    Tiger에서는 무엇보다도 Collection class들에 대해 많은 부분 정비하였다. 예를 들면 for/in 구문 지원과 Generic Type member와 Arrays Utility class 등이다. 그럼 Collection에 대한 static method들을 담고 있는 Arrays 에 대해 다음 example로 한눈에 살펴보자.

    package com.jeid.tiger;import java.util.Arrays;import java.util.Comparator;import java.util.List;public class ArraysTester {	private int[] arr;	private String[] strs;	public ArraysTester(int size) {		arr = new int[size];		strs = new String[size];		for (int i = 0; i < size; i++) {			if (i < 10) {				arr[i] = 100 + i;			} else if (i < 20) {				arr[i] = 1000 - i;			} else {				arr[i] = i;			}			strs[i] = "str" + arr[i];		}	}	public int[] getArr() {		return this.arr;	}	public String[] getStrs() {		return this.strs;	}	public static void main(String[] args) {		int size = 50;		ArraysTester tester = new ArraysTester(size);		int[] testerArr = tester.getArr();		int[] cloneArr = tester.getArr().clone();		String[] testerStrs = tester.getStrs();		String[] cloneStrs = tester.getStrs().clone();		// clone test		if (Arrays.equals(cloneArr, testerArr)) {			System.out.println("clonse int array is same.");		} else {			System.out.println("clonse int array is NOT same.");		}		if (Arrays.equals(cloneStrs, testerStrs)) {			System.out.println("clonse String array is same.");		} else {			System.out.println("clonse String array is NOT same.");		}		// 2부터 10까지 값 셋팅		Arrays.fill(cloneArr, 2, 10, new Double(Math.PI).intValue());		testerArr[10] = 98;		testerStrs[10] = "corea";		testerStrs[11] = null;		List<String> listTest = Arrays.asList(testerStrs);		System.out.println("listTest[10] = " + listTest.get(10));		System.out.println("------- unsorted arr -------");		System.out.println("Arrays.toString(int[]) = " + Arrays.toString(testerArr));		System.out.println("Arrays.toString(String[]) = " + Arrays.toString(testerStrs));		Arrays.sort(testerArr);		// Arrays.sort(testerStrs); //NullPointerException in sort method..(null이 없더라도 길이에 대한 크기 체크는 못함)		Arrays.sort(testerStrs, new Comparator<String>() {			public int compare(String s1, String s2) {				if (s1 == null && s2 == null) {					return 0;				} else if (s1 == null && s2 != null) {					return -1;				} else if (s1 != null && s2 == null) {					return 1;				} else if (s1.length() < s2.length()) {					return -1;				} else if (s1.length() > s2.length()) {					return 1;				} else if (s1.length() == s2.length()) {					return 0;				} else {					return s1.compareTo(s2);				}			}		});		System.out.println("------- sorted arr -------");		System.out.println("Arrays.toString(int[]) = " + Arrays.toString(testerArr));		System.out.println("Arrays.toString(String[]) = " + Arrays.toString(testerStrs));				System.out.println("------------------------------------------------");		String[][] mstrs1 = { { "A", "B" }, { "C", "D" } };		String[][] mstrs2 = { { "a", "b" }, { "c", "d" } };		String[][] mstrs3 = { { "A", "B" }, { "C", "D" } };		System.out.println("Arrays.deepToString(mstrs1) = " + Arrays.deepToString(mstrs1));		System.out.println("Arrays.deepToString(mstrs2) = " + Arrays.deepToString(mstrs2));		System.out.println("Arrays.deepToString(mstrs3) = " + Arrays.deepToString(mstrs3));				if( Arrays.deepEquals(mstrs1, mstrs2)) {			System.out.println("mstrs1 is same the mstrs2.");		}else {			System.out.println("mstrs1 is NOT same the mstrs2.");		}				if( Arrays.deepEquals(mstrs1, mstrs3)) {			System.out.println("mstrs1 is same the mstrs3.");		}else {			System.out.println("mstrs1 is NOT same the mstrs3.");		}				System.out.println("mstrs1's hashCode = " + Arrays.deepHashCode(mstrs1));		System.out.println("mstrs2's hashCode = " + Arrays.deepHashCode(mstrs2));		System.out.println("mstrs3's hashCode = " + Arrays.deepHashCode(mstrs3));	}}

    8.2 Using java.util.Queue interface #

    Queue를 이용하여 First In First OutOrdering한 Queue를 구현 가능하다.

    package com.jeid.tiger;import java.util.LinkedList;import java.util.PriorityQueue;import java.util.Queue;public class QueueTester {	public static void main(String[] args) {		System.out.println("---------- testFIFO ----------");		testFIFO();		System.out.println("---------- testOrdering ----------");		testOrdering();	}	private static void testFIFO() {		Queue<String> q = new LinkedList<String>();		q.add("First");		q.add("Second");		q.add("Third");		String str;		while ((str = q.poll()) != null) {			System.out.println(str);		}	}	private static void testOrdering() {		int size = 10;		Queue<Integer> qi = new PriorityQueue<Integer>(size);		Queue<String> qs = new PriorityQueue<String>(size);		for (int i = 0; i < size; i++) {			qi.offer(10 - i);			qs.offer("str" + (10 - i));		}				for (int i = 0; i < size; i++) {			System.out.println("qi[" + i + "] = " + qi.poll() + ", qs[" + i + "] = " + qs.poll());		}	}}

    8.3 java.lang.StringBuilder 사용하기 #

    StringBuffer가 synchronize하지 않은 method들로 구성된 듯한 StringBuilder를 사용하므로 성능 향상을 도모할수 있다. 사용법은 StringBuffer와 동일하다.

    package com.jeid.tiger;import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class StringBuilderTester {	public static void main(String[] args) {		List<String> list = new ArrayList<String>();		list.add("str1");		list.add("str2");		list.add("str3");		String ret = appendItems(list);		System.out.println("ret = " + ret);	}	private static String appendItems(List<String> list) {		StringBuilder sb = new StringBuilder();		for (Iterator<String> iter = list.iterator(); iter.hasNext();) {			sb.append(iter.next()).append(" ");		}		return sb.toString();	}}

    8.4 Using Type-Safe Lists #

    Collection에 type을 명시하여 type-safe 하게 처리 가능. 아래에서 type을 명시하지 않을 경우 compile error가 남을 보여준다. tip으로 Number를 이용하여 byte, short, int, long, double, float 동시 사용하는 부분 참조.

    package com.jeid.tiger;import java.util.Iterator;import java.util.LinkedList;import java.util.List;public class ListTester {	public static void main(String[] args) {		List<String> list = new LinkedList<String>();		list.add("str1");		list.add("str2");		list.add(new Integer(123));  // <-- String이 아니므로 compile error!!				//Iterator에 String type을 명시하므로 정삭작동됨.		for (Iterator<String> iter = list.iterator(); iter.hasNext();) {			String str = iter.next();			System.out.println("srt = " + str);		}				//Iterator에 String type을 명시하지 않았으므로 아래 A 부분에서 compile 오류 발생!!		for (Iterator iter = list.iterator(); iter.hasNext();) {			String str = iter.next(); //A			System.out.println("srt = " + str);		}				//byte, short, int, long, double, float 동시 사용		List<Number> lstNum = new LinkedList<Number>();		lstNum.add(1);		lstNum.add(1.2);		for (Iterator<Number> iter = lstNum.iterator(); iter.hasNext();) {			Number num = iter.next();			System.out.println("num = " + num);		}	}}

    8.5 Writing Generic Types #

    class 나 interface keyword에 type을 명시하여 동일 타입 명시 가능. 주의 할 점은 any type은 static 일 수 없다.(동적으로 type이 정해지므로)

    class AnyTypeList<T> {//class AnyTypeList<T extends Number> {  // <-- 이는 Number를 상속한 type은 허용하겠다는 의미.	private List<T> list;	//private static List<T> list;  // <-- 이는 정적이므로 compile error 발생!!! 	public AnyTypeList(){		list = new LinkedList<T>();	}		public boolean isEmpty(){		return list == null || list.size() == 0;	}		public void add(T t){		list.add(t);	}		public T grap(){		if (!isEmpty() ) {			return list.get(0);		} else {			return null;		}	}}

    8.6 새로운 static final enum #

    예제를 통해 알아보자.

    package com.jeid.tiger;import com.jeid.BaseObject;import com.jeid.MyLevel;public class EnumTester extends BaseObject {	private static long start = System.currentTimeMillis();		public static void main(String[] args) {		try {			test();			enum1();		} catch (Exception e) {			e.printStackTrace();		}		printEllapseTime();	}		private static void test() throws Exception {		byte[] b = new byte[0];		System.out.println(b.length);	}	private static void enum1() {		//enum TestEnum { A, B };  //enum cannot be local!!!				for(MyVO.TestEnum te: MyVO.TestEnum.values()){			System.out.println("Allow TestEnum value : " + te);		}		System.out.println("---------------------------------------");				MyVO vo = new MyVO();		vo.setName("enum1");		vo.setLevel(MyLevel.A);		System.out.println(vo);		System.out.println("isA = " + vo.isA() + ", isGradeA = " + vo.isLevelA()+ ", isValueOfA = " + vo.isValueOfA());		System.out.println("getLevelInKorean = " + vo.getLevelInKorean());	}	private static void printEllapseTime() {		System.out.println("==> ellapseTime is " + (System.currentTimeMillis() - start) + " ms.");	}}package com.jeid.tiger;import com.jeid.BaseObject;import com.jeid.MyLevel;public class MyVO extends BaseObject {	enum TestEnum {		A, B	}; // this is same public static final	private int id;	private String name;	private MyLevel grade;	// private List<T> list;	public MyLevel getLevel() {		return grade;	}	public void setLevel(MyLevel grade) {		this.grade = grade;	}	public boolean isA() {		return "A".equals(this.grade);	}	public boolean isValueOfA() {		return MyLevel.valueOf("A").equals(grade);	}	public boolean isLevelA() {		return MyLevel.A.equals(this.grade);	}	//A,B,C..대신 0,1,2... 도 동일함.	public String getLevelInKorean() {		switch(this.grade){		case A:			return "수";		case B:			return "우";		case C:			return "미";		case D:			return "양";		case E:			return "가";		default:			return "없음";		}	}	public int getId() {		return id;	}	public void setId(int id) {		this.id = id;	}	public String getName() {		return name;	}	public void setName(String name) {		this.name = name;	}}

    8.7 Using java.util.EnumMap #

    java.util.Map과 동일하나 key가 enum type이어 한다. 예제로 살펴보자.

    package com.jeid.tiger;import java.util.EnumMap;public class EnumMapTester {	private enum MyEnum {		A, B, C	}; // this is same the static final..	public static void main(String[] args) {		MyEnum[] enums = MyEnum.values();		System.out.println("MyEnum is " + enums[0] + ", " + enums[1] + ", " + enums[2]);		EnumMap<MyEnum, String> em = new EnumMap<MyEnum, String>(MyEnum.class);		em.put(MyEnum.A, "수");		em.put(MyEnum.B, "우");		em.put(MyEnum.C, "미");		em.put(MyEnum.B, "가"); //key 중복은 HashMap과 동일하게 overwrite임.		for (MyEnum myEnum : MyEnum.values()) {			System.out.println(myEnum + " => " + em.get(myEnum));		}	}}

    8.8 Using java.util.EnumSet #

    java.util.Set과 동일하나 value가 enum type이어 한다. 예제로 살펴보자.

    package com.jeid.tiger;import java.util.EnumSet;public class EnumSetTester {	private enum MyEnum {		A, B, C, a, b, c	}; // this is same the static final..	public static void main(String[] args) {		MyEnum[] enums = MyEnum.values();		System.out.println("MyEnum is " + enums[0] + ", " + enums[1] + ", " + enums[2]);		EnumSet<MyEnum> es1 = EnumSet.of(MyEnum.A, MyEnum.B, MyEnum.C);		EnumSet<MyEnum> es2 = EnumSet.of(MyEnum.a, MyEnum.b, MyEnum.c);		EnumSet<MyEnum> es3 = EnumSet.range(MyEnum.a, MyEnum.c);		if (es2.equals(es3)) {			System.out.println("e2 is same e3.");		}		for (MyEnum myEnum : MyEnum.values()) {			System.out.println(myEnum + " contains => " + es1.contains(myEnum));		}	}}

    8.9 Convert Primitives to Wrapper Types #

    int, short, char, long, double등 primitive와 이들의 Object Wrapper 인 Integer, Shrt, Char등 간의 converting에 있어 자동으로 처리해주는 boxing과 unboxing이 지원 됨에 따라 type에 대한 유연한 처리가 가능해졌다. 예제로 살펴보자.

    package com.jeid.tiger;public class AutoBoxingTester {	public static void main(String[] args) {		int i = 0;		Integer ii = i; // boxing. JDK 1.4에서는 incompatible type error가 발생 했었으나 Tiger에서는 괜찮다.		int j = ii; // unboxing		for (ii = 0; ii < 5; ii++) { // Integer인데도 ++ 연산자 지원.		}		i = 129;		ii = 129;		if (ii == i) {			System.out.println("i is same ii.");		}		// -128 ~ 127 사이의 수는 unboxing이 되어 == 연산이 허용되지만,		// 그 범위 외의 경우 Integer로 boxing된 상태므로 equals를 이용해야함.		// 이는 버그가 발생했을 경우 찾기 쉽지 않은 단점도 내포하고 있다.!!		checkIntegerSame(127, 127); // same		checkIntegerSame(128, 128); // Not same		checkIntegerEquals(128, 128); // equals		checkIntegerSame(-128, -128); // same		checkIntegerSame(-129, -129); // Not same		checkIntegerEquals(-129, -129); // equals				System.out.println("--------------------------------------------");		Boolean arriving = false;		Boolean late = true;		String ret = arriving ? (late ? "도착했지만 늦었네요." : "제시간에 잘 도착했군요.") : 								(late ? "도착도 못하고 늦었군요." : "도착은 못했지만 늦진 않았군요.");		System.out.println(ret);				StringBuilder sb = new StringBuilder();		sb.append("appended String");		String str = "just String";		boolean mutable = true;		CharSequence chSeq = mutable ? sb : str;		System.out.println(chSeq);	}	private static void checkIntegerSame(Integer ii, Integer jj) {		if (ii == jj) {			System.out.println("ii = " + ii + ", jj = " + jj + " ==> jj is same ii.");		} else {			System.out.println("ii = " + ii + ", jj = " + jj + " ==> jj is NOT same ii!!");		}	}	private static void checkIntegerEquals(Integer ii, Integer jj) {		if (ii.equals(jj)) {			System.out.println("ii = " + ii + ", jj = " + jj + " ==> jj is equals ii.");		} else {			System.out.println("ii = " + ii + ", jj = " + jj + " ==> jj is NOT equals ii!!");		}	}}

    8.10 Method Overload resolution in AutoBoxing #

    int가 127을 초과할 경우 boxing이 이루어 질듯 하지만, method overload에 있어서는 boxing이 이루어 지지 않아 JDK1.4와 동일한 결과를 얻는다. 예제로 살펴보자.

    package com.jeid.tiger;public class OverloadTester {	public static void main(String[] args) {		double d = 10;		Integer ii = new Integer(10);		doSomething(10);		doSomething(1000);		doSomething(ii);		doSomething(d);	}	private static void doSomething(Integer ii) {		System.out.println("This is doSomething(Integer)");	}	private static void doSomething(double d) {		System.out.println("This is doSomething(double)");	}}

    8.11 가변적인 argument 개수 ... #

    인수가 가변적일 경우 인수의 개수가 없는것 부터 다수개까지 모두 지원. 예제로 살펴보자.

    package com.jeid.tiger;public class VarArgsTester {	public static void main(String[] args) {		setNumbers(1, 2);		setNumbers(1, 2, 3, 4);		setNumbers(1);		// setNumbers(); //해당 되는 method가 없어 compile error!!		System.out.println("==============================================");		setNumbers2(1, 2, 3, 4);		setNumbers2(1);		setNumbers2();	}	// this is same setNumbers(int first, int[] others)	private static void setNumbers(int first, int... others) {		System.out.println("-----------setNumbers()----------- : " + first);		for (int i : others) {			System.out.println("i = " + i);		}	}	// this is same setNumbers(int[] others)	private static void setNumbers2(int... others) {		System.out.println("-----------setNumbers2()----------- : "				+ (others != null && others.length > 0 ? others[0] : "null"));		for (int i : others) {			System.out.println("i = " + i);		}	}}

    8.12 The Three Standard Annotation #

    @Override - sign the override from superclass.

        //정상적인 사용    @Override    public int hashCode(){        return toString().hashCode();    }        //스펠링이 틀려 compile error!!    @Override    public int hasCode(){   //misspelled => method does not override a method from its superclass error!!        return toString().hashCode();    }

    @Deprecated deprecated 주석과 동일하나 부모의 method가 deprecated되면 자식의 method를 사용해도 deprecated로 나온다.

    package com.jeid.tiger;public class AnnotationDeprecateTester {	public static void main(String[] args){		DeprecatedClass dep = new DeprecatedTester();		dep.doSomething(10);    //deprecated	}}class DeprecatedClass {	@Deprecated	public void doSomething(int ii){    //deprecated		System.out.println("This is DeprecatedClass's doSomething(int)");	}		public void doSomethingElse(int ii){		System.out.println("This is DeprecatedClass's doSomethingElse(int)");	}}class DeprecatedTester extends DeprecatedClass {	@Override	public void doSomething(int ii){		System.out.println("This is DeprecatedTester's doSomething(int)");	}}

    @SuppressWarnings SuppressWarnings에 인자는 String[] type으로 여러개를 배열형태로 쓸수 있다.

    package com.jeid.tiger;import java.util.ArrayList;import java.util.List;public class AnnotationSuppressWarningsTester {	@SuppressWarnings({"unchecked", "fallthrough"} )	private static void test1(){		List list = new ArrayList();		list.add("aaaaaa");	}		@SuppressWarnings("unchecked")	private static void test2(){		List list = new ArrayList();		list.add("aaaaaa");	}		//warning이 없는 소스.	private static void test3(){		List<String> list = new ArrayList<String>();		list.add("aaaaaa");	}}

    8.13 Creating Custom Annotation Types #

    나만의 annotation을 정의할 수 있는데 키워드는 @interface이 각 method정의가 member라고 보면 된다. 간단한 예를 보면 다음과 같다.

    package com.jeid.tiger;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Documented@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotation {	String columnName();	String methodName() default "";}//사용하는 쪽..public class AnnotationTester {	@MyAnnotation(columnName = "test", methodName = "setTest")	private String test;	@MyAnnotation(columnName = "grpid")	public String grpid;    ....}//위의 test 멤버의 경우 다음과 같이 접근 가능하다.Field testField = cls.getDeclaredField("test");if (testField.isAnnotationPresent(MyAnnotation.class)) {    Annotatioin anno = testField.getAnnotation(MyAnnotation.class);    System.out.println(anno.columnName() + ", method = " + anno.methodName());}

    9 The for/in Statement #

    9.1 for/in 의 자주 사용되는 형태 #

    for/in은 무엇보다 다양한 유형의 예제를 보는것이 제일 빠를것이다. 형태별 사용 예제를 살펴보면 다음과 같다.

    //1. 가장 단순한 형태인 배열(array)String[] strs = { "aaa", "bbb", "ccc" };for (String str : strs) {    System.out.println(str);}//2. List by using IteratorList<Number> lstNum = new LinkedList<Number>();lstNum.add(1);lstNum.add(1.2);for (Iterator<Number> iter = lstNum.iterator(); iter.hasNext();) {	Number num = iter.next();	System.out.println("num = " + num);}//3. List를 바로 사용List<String> lst = new LinkedList<String>();lst.add("aaaaa");lst.add("bbbbb");lst.add("ccccc");lst.add("ddddd");for (String str : lst) {	System.out.println("str = " + str);}// 4. List of ListList[] lists = { lst, lst };for (List<String> l : lists) {	for (String str : l) {		System.out.println("str = " + str);	}}

    10 Static Import #

    10.1 static member/method import #

    Tiger에서는 다른 클래스의 member와 method를 import 할수 있다. 단, static 일 경우만 가능하다.

    //예를 들어 System.out.println() 이라는 것을 사용하기 위해서는 다음의 import 문이 필요하다.import java.lang.System;   //물론 java.lang 이기에 import 문이 필요없지만 예를 들자면 그렇다는 것이다.&^^//허나, Tiger에서는 다음과 같이 사용할수 있다.import static java.lang.System.out;...out.println(...);// method를 import 한다면..import static java.lang.System.out.println;...println(...);

    Posted by 1010
    90.개발관련문서2008. 8. 12. 11:45
    반응형
    Welcome  

    Welcome to Google Doctype. Written by web developers, for web developers.

    What do you want to learn today?

    Read HOWTO articles on

    Dive into DOM objects, including

    Style your pages with CSS

    Do you need help with an HTML element? We have an HTML reference from <a> to <xmp>.

    Posted by 1010
    90.개발관련문서2008. 8. 12. 11:06
    반응형
    퍼온곳 : http://kwon37xi.egloos.com/ 

    ------------------------------------------------------------------------------------------

    초보 Java 웹 개발자들을 위한 학습 로드맵
    OKJSP에 자주 가서 요즘 자바 개발자들이 어떻게 살아가나를 보는 편인데, 아주 많이 반복적으로 올라오는 질문이 "대체 뭘 공부해야 하나요? 프레임워크는 Spring을 해야 할까요? iBATIS를 해야 할까요?" 하는 식의 질문들이다(이 질문은 사실 말이 안된다. 왜 그런지 읽다보면 나온다).

    Java는 웹 관련 프레임워크들이 너무 다양하고, Ruby나 Python 같은 경우에는 RubyOnRailsDjanog 처럼 하나의 프레임워크 안에 기능별 프레임워크들도 모두 다 All in one 형태로 들어 있어서 혼란을 주지 않는 반면, Java는 각 영역별로 프레임워크가 모두 다르고, 또한 각 영역별로 존재하는 프레임워크들의 종류도 많아서 초보 개발자들에게 극심한 혼란을 주고 있다.

    그래서 나름대로 Java Web 개발자들을 위한 학습 로드맵을 정리해 보았다.

    1. Java 그 자체
    많은 웹 개발자들이 마치 JSP 코드를 짤 줄 알면 그걸로 Java 웹 개발을 할 줄아는 것이라 생각하고 Java 그 자체를 소홀히 하는 것을 본다.
    말도 안되는 소리이다. Java를 모르고서 Java 웹 개발을 제대로 한다는 것은 어불 성설이다. Java 그 자체를 먼저 공부하라.

    특히 Java 5 문법을 숙지하길 권한다. 이제 우리나라도 점차 Java 5가 대세가 되어 가고 있다. 대부분의 프레임워크들과 WAS(JSP와 서블릿을 구동하는 서버)도 모두 Java 5를 기준으로 바뀌었으며, JVM 자체도 버전이 높을 수록 성능이 더 좋다.

    2. JSP와 Servlet 그리고 Model 1
    모델 1은, JSP 하나에 DB에 접속해서 쿼리를 날리는 등의 모든 업무적인 기능(Business Logic)을 넣고, 그 아래에 HTML 코드를 박아 넣는 식으로 개발하는 것을 의미한다.
    아직도 많은 개발자들이 여기에 길들여져 있는데, 일단 JSP 자체에 대한 기본기를 익힌 뒤로는 재빨리 버려야 할 습관이다.

    그리고 많은 개발자들이 Servlet을 무시하고 JSP만 하는 것을 보곤 하는데, Servlet에 대한 학습이 제대로 이뤄지지 않으면 더 나은 웹 개발이 곤란하다. Servlet에 대한 기초 개념을 확실히 잡길 권한다.

    3. Model 2 - 프레임워크의 등장
    JSP로 열심히 개발을 하다보니 프로젝트 규모도 커지기 시작하고, JSP 파일 크기도 수천줄에 달하는등 엄청나게 커진다.
    그런데 이 JSP에다 두서없이 모든 기능을 다 때려 넣다보니 JSP마다 똑같은 기능들이 Copy&Paste로 들어가고, JSP 안에 들어 있는 Java 코드들에서 에러가 발생하면 찾아내서 디버깅 하는 일이 지옥같이 느껴지기 시작한다.

    여기서 Model 2가 구원자로 등장한다.

    Model 2는 말만 멋드러졌지 실제로는 간단한 개념이다.

    JSP에서 수행하던 DB 쿼리 등의 작업을 Servlet에게 넘겨주고 JSP에서는 오로지 화면 출력만 담당하는 것이다.

    Servlet에서 DB 쿼리등 화면 출력과는 상관없는 비지니스 로직을 일단 먼저 모두 수행하고, 그 결과를 request.setAttribute("key",결과객체);로 담은 다음 JSP 페이지로 포워딩(forward)을 하면 JSP에서는 request.getAttribute("key")로 그 객체를 받아서 화면에 뿌려주기만 한다.
    이런 업무 수행단/화면 출력단의 철저한 역할 분리가 Model 2이다.

    여기서 이러한 각 역할을 "MVC - Model View Controller" 라고 한다. 그래서 Model 2는 MVC와 동일한 의미로 사용하기 도 한다. MVC의 의미는 공부하면서 찾아보라.

    이게 뭐가 좋냐고? 개발 기간이 좀 길어지고 프로젝트 규모가 쬐끔 커지고, 기존 프로젝트를 유지보수를 해보면 얼마나 좋은지 몸소 뼈져리게 느끼게 된다.

    Model 2의 기능을 정형화해서 쉽게 구현하게 해주는 것이 MVC Framework들의 역할이다.
    가장 유명한 Model 2 웹 프레임워크들은 다음과 같은 것들이 있다.

    * 스트럿츠 1 - Struts 1
    * 스트럿츠 2 - Struts 2
    * 스프링 MVC - Spring MVC
    * 기타 덜 유명한 Wicket, Stripes, JSF, Tapestry 등.

    Struts 1은 MVC의 효시라고 할 수 있다. 우리에게 MVC라는 축복을 주기는하였으나, 나온지 오래된 만큼 낡은 개념들이 많이 녹아있고 쓸데 없이 복잡하고 배우기도 어려운 편이다.

    오히려 Struts 2와 Spring MVC가 더 배우기 쉬울 것이며, 개발도 더 쉽다. 현재 추세는 Struts 2와 Spring MVC이다. 대형 포탈이나 SI 업체들도 Spring/Struts 2를 주로 채택하는 추세로 가고 있는 것으로 알고 있다.

    둘 중 하나의 개념만 확실히 이해해도 다른 것을 배우는데 어려움이 별로 없으므로 그냥 둘중에 골라서 배우길 권한다. 나는 Spring을 선호한다.

    그리고 MVC 프레임워크를 사용하기 시작하면서 View를 만드는 JSP에 대해서도 재조명이 시작된다. 기존에 Java 코드를 JSP에 직접 넣던 관행을 버리고 JSTL과 태그 라이브러리를 사용하거나 아예 JSP를 버리고 다른 템플릿 엔진으로 만들기도 한다. 이에 관해서는 맨 마지막에.

    4. 퍼시스턴스 프레임워크 : JDBC 반복 작업에 짜증이 나기 시작하다.
    현대 웹 개발에서 가장 큰 역할을 차지하는 것은 뭐니뭐니해도 단연 Database 작업이다.
    지금까지는 아마도 JDBC에서 DB 커넥션을 맺고, 쿼리를 날리고 그 결과 ResultSet을 JSP로 넘겨주어서 출력하는 식으로 했을 것이다.
    이미 다들 알고 있겠지만 JDBC를 사용하면 똑같은 코드가 굉장히 많이 반복해서 나온다. 한마디로 "삽질"의 전형이 JDBC 작업이다.
    이것을 깨달은 많은 개발자들이 조금 어정짱하게 반복작업을 해결해주는 Util 클래스들을 프로젝트별로 만들어서 사용하곤 한다.
    하지만, 물론 이에 대해 정형화하고 깔끔하고 훨씬 더 사용하기 쉬게 만들려는 노력이 이미 수년에 걸쳐 이루어졌다.

    이렇게 DB관련된 작업을 정형화한 것들을 Persistence Framework 라고 한다.

    * 아이바티스 - iBATIS : SQL Mapper - JDBC보다 더 쉽게 배우고, 더 편하게 사용한다.
    * 하이버네이트 - Hibernate : 객체지향을 객체지향답게, 개발 기간을 엄청나게 단축시켜주다.

    퍼시스턴스 프레임워크의 양대 산맥은 iBATIS와 Hibernate이다. 이 둘 모두 우리나라에 책이 나와 있다.
    iBATIS는 SQL Mapper의 한 종류이고, Hibernate는 ORM의 한 종류이다.

    이 둘의 차이는 iBATIS는 개발자가 SQL 쿼리를 직접 작성한 것을 객체에 매핑시켜주는 것이고, ORM은 DB 스키마와 객체간의 관계를 설정파일로 만들면 자동으로 쿼리를 만들어주는 것이다.

    자, 이 둘을 보면 미국에서는 Hibernate가 인기가 좋고, 우리나라에서는 iBATIS가 사실상 SI 업계를 평정했다.
    그러니까, 일단은 우리나라에서는 iBATIS를 공부하면 된다고 보면 된다.

    이렇게 말하니까 마치 이 둘이 경쟁자 같은데, 사실 이 둘은 경쟁 상대라기 보다는 보완해주는 역할을 한다. SI에서 처럼 DB 테이블이 정규화 되어 있지 않은 경우에는 Hibernate같은 ORM을 사용하면 프로젝트를 말아먹을 수 있다.

    iBATIS는 테이블 정규화에 무관하게, 개발자가 작성한 SQL을 객체로 매핑하기 때문에 DB 스키마가 마구 꼬여 있는 상황에서도 유연하게 작동하고, 개발자가 직접 SQL 튜닝을 할 수 있다는 장점이다.

    그리고 Hibernate는 배우기가 굉장히 어려운 프레임워크이고 튜닝이 매우 어렵다. Hibernate책을 보면 캐싱을 통해 성능을 향상시키라고 하지만 캐싱은 iBATIS도 못지않게 잘 지원한다. 하지만 일단 배우면, 그로인한 코딩 생산성이 iBATIS가 감히 넘볼 수 없을 정도록 급격히 향상된다.

    Hibernate는 DB 정규화가 잘되어 있는 웹 포탈 업체나 패키지 소프트웨어 제작시에 강력히 권장할만 하다.

    5. IoC와 DI - 객체의 생성주기와 의존성을 관리하고 싶어지다
    사실 내가 경험한 SI를 보면 4단계 까지만 가도 막장은 아닌 프로젝트라고 본다. 아직도 신규 프로젝트를 하면서도 Model 1에 JDBC로 코딩하는 것을 많이 보았기 때문이다.

    앞서, MVC라는 형태로 웹 애플리케이션의 역할을 철저하게 분할해서 처리하라고 했었다.

    이제 여기서 좀 더 역할을 분할하기 시작한다.

    Database를 관장하는 코드(DAO)와 Database 처리 결과를 가지고 그외 비지니스 로직을 추가로 수행하는 코드(Service), 그리고 웹으로 들어온 요청을 받아서 비지니스 로직을 호출하고, 그 결과를 다시 웹(HTML 등)으로 내보내는 코드(Controller)로 분할을 하면 유지보수가 더 쉽고, DB가 Oracle에서 DB2 로 변경되는 식의 중대 변화가 있을 때도 DAO만 바꾸면 되는 식으로 변화에 대한 대처가 유연해 진다는 것을 깨닫기 시작한다.

    이제는 각 역할별로 클래스를 분할하고 컨트롤러 객체는 서비스 객체에 서비스 객체는 DAO 객체에 의존해서 작동하도록 코드를 바꾸기 시작한다. 그리고 객체의 생성과 파괴 주기도 관리해야만 하게 된다. 객체를 하나만 생성하면 되는데 불필요하게 매번 new를 할 필요는 없으니까.

    이렇게 객체의 생성/파괴 주기를 관리하고 객체간의 의존성을 관리해주는 프레임워크를 IoC 컨테이너라고 부른다.

    1. Spring Framework
    2. EJB 3.0

    사실상 대세는 Spring Framework로 굳어졌다. EJB 3.0은 내가 안써봐서 뭐라 말은 못하겠다.

    Spring MVC는 이 Spring Framework의 일부분이다.

    Spring은 또한 AOP도 지원한다.

    AOP 의 개념이 상당히 어려운 편이라서 개념 자체를 확실히 한마디로는 표현하지 못하겠다. 어쨌든 개발자들에게 가장 쉽게 다가오는 표현으로 하자면, AOP는 동일한 패턴으로 반복적으로 해야하는 일을 설정을 통해 자동으로 해주는 것이다.
    이에 관한 가장 보편적인 예가 바로 트랜잭션이다.
    지금까지는 아마도 비지니스 로직이 시작될 때 트랜잭션이 시작되고, 비지니스 로직이 끝날 때 트랜잭션을 종료하는 코드를 매번 작성해서 넣었을 것이다.
    AOP를 사용하면, 비지니스 로직의 역할을 하는 메소드가 무엇인지 설정파일에 넣어주기만 하면 자동으로 메소드가 시작될 때 트랜잭션을 시작시키고, 메소드가 끝날 때 트랜잭션을 종료시켜준다. 물론 예외가 발생하면 트랜잭션을 rollback도 해준다. 따라서 Spring을 사용한 프로젝트에서는 트랜잭션 관련 코드를 볼 수 없을 것이다.

    Spring 프레임워크는 기본적으로 IoC 컨테이너 역할을 하는 것이 핵심이다. 따라서 Spring을 사용한다고 해서 꼭 Spring MVC를 사용할 필요는 없다. Struts 2 + Spring + iBATIS 나 SpringMVC + Spring + Hibernate 등... 어떠한 조합이라도 가능하다.

    6. 그 외
    ◈ Template Engine : JSP 보다 더 간결하면서 강력한게 필요해!
       * JSP + JSTL : Sun이 지정한 산업표준이다. JSTL은 당연히 쓰고 있으리라 믿는다.
       * Freemarker : 가장 권장할 만하다.
       * Velocity : 굉장히 배우기 쉽다. JSTL보다 더 빨리 배워서 쓸 수 있다. 가독성도 좋다. 그러나 Freemarker 만큼 편하고 강력하지는 못하다.
    많은 사람들이 Java 웹 개발을 그냥 "JSP 개발"이라고도 부르는데, MVC가 도입되고, Freemarker 같은 다른 템플릿 엔진을 사용하게 되면 더이상 JSP는 코빼기도 안보이게 된다. 그러므로.. JSP 개발이라는 말은 쓰지 않았으면 좋겠다.

    ◈ Layout Engine
       * Sitemesh : 헤더 푸터 처럼 동일 패턴이 반복되는 레이아웃을 관리해준다.

    ◈ XML 도우미 : W3C DOM은 너무 어렵고 난잡하다. 좀 더 편한 XML관련 개발을 원한다면..
       * JDOM : Java 표준으로 지정됐다고 한다.
       * DOM4J
    둘 다 비슷하게 편한거 같다. 예전엔 JDOM을 썼었는데, 나 같은 경우 현재 프로젝트에서는 DOM4J를 사용한다. Hibernate가 DOM4J를 사용하기 때문에, 별도의 라이브러리 더 넣는게 귀찮아서.

    ◈ 단위 테스트
       * jUnit : 코드를 철저하게 테스트하자.

    ◈ 소스코드 버전관리
       * CVS
       * Subversion : 현재 대세는 Subversion
    내가 최고 막장으로 꼽는 프로젝트는 아직도 FTP로 소스 관리하는 프로젝트이다. 이런 프로젝트에는 절대로 참여하지 않을 것이라고 굳게 맹세하고 또 맹세했다. --;
    소스 코드 버전관리는 여러 개발자들이 동시에 개발할 때 소스코드를 저장하고 충돌을 관리해주며, 소스 변경 내역을 계속해서 추적해서 과거 소스로 언제든지 돌아갈 수 있도록 도와준다.
    현재 대세는 Subversion이지만 CVS로도 버전관리의 이점을 충분히 만끽할 수 있다. 그리고.. 사실 CVS가 사용법을 익히기는 더 쉽다.

    ◈ 자동 빌드
       *
    Ant : Ant 면 만사 Ok!
       *
    Maven
    아직도 javac 로 컴파일하고 있고, FTP로 파일 올려서 복사하고 있다면.. 이 모든일을 자동으로 명령 한방에 처리하도록 해야 실수도 적고, 퇴근도 일찍한다.
    Ant로 빌드와 배포를 자동화 하자.

    결론

    내가 권하는 조합은
    * SI 업체에서 일하는 경우 : Struts 2 혹은 SpringMVC + iBATIS + JSP/JSTL + 가능하다면 Spring Framework
    * 웹 포털등과 같은 업계, 패키지 소프트웨어 제작 업체 : Struts 2 혹은 Spring MVC + Hibernate + Spring Framework + Freemarker + Sitemesh
    Posted by 1010
    01.JAVA/Java2008. 8. 12. 10:33
    반응형

    어제 제가 개발한 시스템에 대해 윗분과 이야기 하는데

    총 코딩 파일 라인수가 몇줄이냐고 물어보시는데 -_-;;

    몰라서 당황했다는;; 전 나중에 산출물 작성할때나 알아볼까 했는데 흠.

    암튼 이클립스에 라인수 측정하는 곳이 안보여서

    예전에 만들어뒀던 소스 이용하여 약간 발전시켜 만들어 봤습니다.

    이클립스에 이미 있는 기능이라면 저 또 뻘짓한 것이지만서도 ㅎㅎㅎ

    뭐 공부용으로도 함 봐보셔도 괜찮을듯 싶습니다.





    - Finder.java


    /*
     * @Finder.java Created on 2006. 08. 24
     *
     * Copyright (c) 2005 GHLab.com All rights reserved.
     */

    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.InputStreamReader;
    import java.text.NumberFormat;
    import java.util.ArrayList;
    import java.util.Locale;

    /**
     * 파일 총 라인수 구하기.
     *
     * @version 1.0
     * @author 김건호
     */
    class Finder {
        public static long totalFileSize = 0; // 전체 파일 사이즈 저장

        public long fileSize = 0; // 파일 사이즈 저장

        public static int totalFileLineCount = 0;

        public int fileLineCount = 0;

        // 파일 정보 저장
        public ArrayList<FileDTO> fileList = new ArrayList<FileDTO>();

        public final static int TOTAL_FIND_CODE = 0; // 전체 검색 코드

        public final static int FILENAME_FIND_CODE = 1; // 파일명 검색 코드

        public final static int EXTENSION_FIND_CODE = 2; // 확장자 검색 코드

        public int findTypeCode = TOTAL_FIND_CODE; // 검색 코드

        // 검색 파일명
        public String findFileName = "*.*";

        // 하위디렉토리 검색 여부
        public boolean isFindUnderDir = false;

        // 대소문자 구분
        public boolean isUpperLower = false;

        public static void main(String[] args) {
     if (args.length == 0 || args.length > 3) {
         StringBuffer sb = new StringBuffer();

         sb
          .append("사용법을 갈켜드리겠습니다~\n")
          .append(
           "예) Windows 폴더 내의 모든 파일의 용량 및 라인수를 추출하고 싶다면 아래처럼~\n\n")
          .append("java Finder c:\\windows\n\n")
          .append("파일을 지정하고 싶다면(기본 전체 파일 검색) 아래와 같은 옵션을 붙이면 됩니다.\n")
          .append("확장자 지정 검색 -e, 파일 지정 검색 : -f\n")
          .append("예) Windows 폴더 내의 확장자가 exe 인 파일을 검색\n\n")
          .append("java Finder c:\\windows -e exe\n\n")
          .append("하위 폴더를 포함하여 검색하고 싶다면 s 옵션을 붙이면 됩니다.\n")
          .append(
           "예) Windows 폴더와 하위 폴더의 모든 파일 중 파일명이 notepad.exe인 파일을 검색\n\n")
          .append("java Finder c:\\windows -sf notepad.exe\n\n")
          .append("파일명을 대소문자 구분하여 검색하려면 u 옵션을 붙이면 됩니다.\n")
          .append(
           "예) Windows 폴더와 하위 폴더의 모든 파일 중 확장자가 exe인 파일을 검색\n\n")
          .append("java Finder c:\\windows -sue exe");

         System.out.println(sb.toString());

         System.exit(1);
     }

     Finder f = new Finder();

     // 검색 경로
     String path = null;

     // 옵션
     String option = "";

     if (args.length > 0)
         path = args[0]; // 검색 경로 저장

     // 인자 갯수에 따른 분류
     if (args.length > 1) {
         // 옵션 저장
         option = args[1];

         // 검색 파일명 또는 확장자 저장
         f.findFileName = args[2];
     }

     // 옵션에 의한 데이터 셋팅
     if (option.indexOf("s") != -1)
         f.isFindUnderDir = true;

     // 대소문자 구분하지 않을 경우
     if (option.indexOf("u") != -1)
         f.isUpperLower = true;

     // 확장자 검색일 경우
     if (option.indexOf("e") != -1)
         f.findTypeCode = Finder.EXTENSION_FIND_CODE;

     // 파일명 검색일 경우
     if (option.indexOf("f") != -1)
         f.findTypeCode = Finder.FILENAME_FIND_CODE;

     // 확장자 검색과 파일명 검색은 함께 쓸수 없다.
     if (option.indexOf("e") != -1 && option.indexOf("f") != -1)
         System.out
          .println("확장자 검색과 파일명 검색 옵션 모두 입력하셨습니다.\n파일명 검색 모드로 자동 설정됩니다.");

     // 디렉토리 존재 여부
     File file = new File(path);
     if (file.isDirectory() == false) {
         System.out.println("정확한 경로를 입력하여 주십시오.");
         System.exit(1);
     }

     // 디렉토리 끝에 \ 또는 / 이 없으면 붙임
     String lastChar = path.substring(path.length() - 1, path.length());
     if (lastChar.equals(File.separator) == false) {
         path = path + File.separator;
     }

     try {
         f.getSize(path);
     } catch (Exception e) {
         System.out.println("에러!! : " + e.getMessage());
         e.printStackTrace();
     }

     System.out.println("검색된 파일 갯수  : " + f.fileList.size());

     // 검색된 파일 갯수가 있을 경우에 정보 출력
     if (f.fileList.size() > 0) {
         System.out.println("총 파일 사이즈 : " + byteTranslater(totalFileSize));
         System.out.println("총 라인수 : " + totalFileLineCount + "라인");

         System.out.print("검색된 파일 정보를 보시겠습니까?(Y/N) : ");

         String choice = null;

         try {
      BufferedReader in = new BufferedReader(new InputStreamReader(
       System.in));
      choice = in.readLine();

         } catch (Exception e) {
      choice = "N";
         }

         choice = choice.toUpperCase();

         if (choice.equals("Y")) {
      // 파일 정보 추출
      FileDTO[] fdto = f.getFileList();

      for (int i = 0; i < fdto.length; i++) {
          System.out.println("파일 : " + fdto[i].getFilePath()
           + fdto[i].getFileName() + ", 사이즈 : "
           + byteTranslater(fdto[i].getFileSize())
           + ", 라인수 : " + fdto[i].getFileLineCount());
      }
         }
     }

     System.exit(1);
        }

        /**
         * 해당 경로에서 파일을 찾아 용량, 라인수를 측정
         *
         * @param path
         *                파일을 찾을 경로
         * @param findFileName
         *                모든 파일 찾기(*.*), 확장자로 찾기(*.java), 파일명 찾기(test.java)
         * @param isFindUnderDir
         *                하위디렉토리 검색 여부
         * @throws Exception
         */
        public void getSize(String path) throws Exception {
     File dir = new File(path);

     String[] list = dir.list();

     File file;

     BufferedReader br = null;

     String fileName = null; // 파일명 저장

     String filePath = null; // 파일 경로

     String findFileName = this.findFileName;

     for (int i = 0; i < list.length; i++) {
         file = new File(path + list[i]);

         fileSize = 0; // 해당 파일 크기 0으로 초기화
         fileLineCount = 0; // 해당 파일 총 라인수 0으로 초기화

         // 파일만 계산
         if (file.isFile()) {
      fileName = file.getName(); // 파일명 추출

      // 대소문자 구분 하지 않는다면 모두 대문자로 변경
      if (this.isUpperLower == false) {
          fileName = fileName.toUpperCase(Locale.KOREAN);
          findFileName = findFileName.toUpperCase(Locale.KOREAN);
      }

      filePath = file.getPath(); // 파일경로 추출

      // 검색 형태에 따른 조건 설정
      if ((findTypeCode == TOTAL_FIND_CODE)
       || (findTypeCode == EXTENSION_FIND_CODE && fileName
        .substring(fileName.lastIndexOf(".") + 1,
         fileName.length()).equals(findFileName))
       || (findTypeCode == FILENAME_FIND_CODE && fileName
        .equals(findFileName))) {
          br = new BufferedReader(new InputStreamReader(
           new FileInputStream(filePath)));

          // 라인수 추출
          while (br.readLine() != null) {
       fileLineCount++;
          }

          fileSize = file.length(); // 파일 크기 추출

          FileDTO fdto = new FileDTO();

          fdto.setFileSize(fileSize);
          fdto.setFileName(file.getName());
          fdto.setFilePath(filePath.substring(0, filePath
           .lastIndexOf(File.separator) + 1));
          fdto.setFileLineCount(fileLineCount);

          fileList.add(fdto);
      }
         }

         // 해당 폴더내의 모든 파일의 크기 추출을 위한 연산
         totalFileSize += fileSize;
         // 해당 폴더내의 모든 파일의 라인수 추출을 위한 연산
         totalFileLineCount += fileLineCount;

         // 하위 디렉토리가 있고, 옵션을 하위 디렉토리 검색을 지정했을 경우 재귀호출
         if (file.isDirectory() && isFindUnderDir)
      getSize(file.getPath() + File.separator);
     }
        }

        /**
         * ArrayList 에 담겨진 파일 정보 DTO를 배열로 전환하여 받아옴
         *
         * @return 파일 정보 목록
         */
        public FileDTO[] getFileList() {
     FileDTO[] result = new FileDTO[fileList.size()];

     for (int i = 0; i < result.length; i++) {
         result[i] = new FileDTO();

         result[i] = fileList.get(i);
     }

     return result;
        }

        /**
         * 파일 사이즈 단위 변환
         *
         * @param size
         *                long 형 타입의 파일 사이즈
         * @return KB, MB, GB 로 변환한 사이즈
         */
        public static String byteTranslater(long size) {
     NumberFormat nf = NumberFormat.getIntegerInstance();

     java.text.DecimalFormat df = new java.text.DecimalFormat("#,##0.00");

     int intSize = 0;

     int kbyteSize = 1024;

     double doubleSize = 0;

     String returnSize = null;

     // 파일 사이즈가 1000, 2000 형식이므로 기가는 1024 가 아닌 1000을 기준으로.
     if (size >= (1000 * 1024 * 1024)) {
         intSize = new Long(size / (1000 * 1024 * 1024)).intValue();

         return nf.format(intSize) + "GB";
     } else if (size > (kbyteSize * 1024)) {
         intSize = (int) (((double) size) / ((double) (kbyteSize * 1024)) * 100);

         doubleSize = (double) (((double) intSize) / 100);

         returnSize = df.format(doubleSize);

         if (returnSize.lastIndexOf(".") != -1) {
      if ("00".equals(returnSize.substring(returnSize.length() - 2,
       returnSize.length()))) {
          returnSize = returnSize.substring(0, returnSize
           .lastIndexOf("."));
      }
         }

         return returnSize + "MB";
     } else if (size > kbyteSize) {
         intSize = new Long(size / kbyteSize).intValue();

         return nf.format(intSize) + "KB";
     } else {
         // return nf.format(size) + "Byte";
         return "1KB";
     }
        }
    }

    /**
     * 파일 정보 저장 객체
     *
     * @version 1.0
     * @author 김건호
     */
    class FileDTO {
        public int fileLineCount;

        private long fileSize;

        private String filePath;

        private String fileName;

        public FileDTO() {
     fileSize = 0;
     filePath = null;
        }

        /**
         * @return the fileLineCount
         */
        public int getFileLineCount() {
     return fileLineCount;
        }

        /**
         * @param fileLineCount
         *                the fileLineCount to set
         */
        public void setFileLineCount(int fileLineCount) {
     this.fileLineCount = fileLineCount;
        }

        /**
         * @return the fileName
         */
        public String getFileName() {
     return fileName;
        }

        /**
         * @param fileName
         *                the fileName to set
         */
        public void setFileName(String fileName) {
     this.fileName = fileName;
        }

        /**
         * @return the filePath
         */
        public String getFilePath() {
     return filePath;
        }

        /**
         * @param filePath
         *                the filePath to set
         */
        public void setFilePath(String filePath) {
     this.filePath = filePath;
        }

        /**
         * @return the fileSize
         */
        public long getFileSize() {
     return fileSize;
        }

        /**
         * @param fileSize
         *                the fileSize to set
         */
        public void setFileSize(long fileSize) {
     this.fileSize = fileSize;
        }

    }

    Posted by 1010