It’s me

July 12, 2006

로또

Filed under: JAVA — rothmans @ 3:29 pm

class Lotto
{
      public static void main(String[] args)
      {
            int[] r = new int[45];       // 45개의 정수값을 저장하기 위한 배열 생성.

            for(int i=0; i < r.length; i++)       // 배열의 각요소에 1~45의 값을 저장한다.
                  r[i] = i+1;

            int temp=0;             // 두 값을 바꾸는데 사용할 임시변수
            int j = 0;             // 임의의 값을 얻어서 저장할 변수
   // 배열에 저장된 값이 잘 섞이도록 충분히 큰 반복횟수를 지정한다.
            // 배열의 첫번째 요소와 임의의 요소에 저장된 값을 서로 바꾸서 값을 섞는다.

            System.out.println(“===========================”);
            System.out.println(“오늘의 Lotto 당첨번호는? 두둥~”);
   System.out.println(“===========================”);

   for(int i=0; i < 10000; i++) {      
                  j = (int)(Math.random() * 45);   // int는 “버림” 의미함. (예) 1.5->1, 1.9->1, 2.5->2   
      // System.out.print(” “+j);
      // 배열요소의 범위(0~44)에서 임의의 값을 얻는다.
                  // r[0]과 r[j]의 값을 서로 바꾼다.
                  temp = r[0];
                  r[0] = r[j];
                  r[j] = temp;  // 배열요소 자리수 변경하기 (랜덤하게 자리수 바꾸기)
            }

            // 배열 r의 앞에서 부터 5개를 얻는다.
             for(int i=0; i <=5; i++)
                  System.out.print(r[i]+” “);
   
      }
}

HTTPURLCONNECTION를 사용하여 웹 페이지 액세스하기

Filed under: JAVA — rothmans @ 3:27 pm

HTTPURLCONNECTION를 사용하여 웹 페이지 액세스하기

이 글은 HttpURLConnection 과 이의 서브클래스인 HttpsURLConnection 을 사용하여 보안 웹 페이지에 액세스하는 방법을 보여준다. 또한 비보안 페이지(non-secure page)에서 보안 페이지(secure one)로의 리다이렉트를 쉽게 할 수 있는 방법도 볼 수 있다. HTTP 와 HTTPS에 관한 정보는 HTTP 1.1 RFC 2616HTTPS RFC 2818를 참고하기 바란다.
첫번째 예로, 주어진 URL에 접속하기 위해 다음 WebPageReader 프로그램의 HttpURLConnection 를 이용해 보자. 그리고 페이지의 내용을 스탠다드 아웃(standard out)에 출력하자.

   import java.net.URL;
 import java.net.MalformedURLException;
 import java.net.URLConnection;
 import java.io.IOException;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
 public class WebPageReader {
 private static URLConnection connection;
 private static void connect( String urlString ) {
 try {
 URL url = new URL(urlString);
 connection = url.openConnection();
 } catch (MalformedURLException e){
 e.printStackTrace();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 private static void readContents() {
 BufferedReader in = null;
 try {
 in = new BufferedReader(
 new InputStreamReader(
 connection.getInputStream()));
 String inputLine;
 while (
 (inputLine = in.readLine()) != null) {
 System.out.println(inputLine);
 }
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 public static void main(String[] args) {
 if (args.length != 1) {
 System.err.println("usage: java WebPageReader "
 + "<url>");
 System.exit(0);
 }
 connect(args[0]);
 readContents();
 }
 }

만약 현재의 위치가 방화벽 뒤라면, 다음과 같이 설정된 proxyHostproxyPort 변수들이 필요하다.

   http.proxyHost=webcache
 http.proxyPort=8080
 https.proxyHost=webcache
 https.proxyPort=8080

커맨드 라인에 -D플래그를 이용해서 값을 직접 입력하거나 프로그램 상에서 System.setProperty()를 호출함으로써 변수를 구할 수있다. WebPageReader 를 컴파일한 후에 이하와 같은 커맨드로 Core Java Technologies Tech Tips 홈 페이지의 내용을 열거해 볼 수 있다.

   java WebPageReader
 http://java.sun.com/developer/JDCTechTips/

URLConnection는 추상 클래스이다. URL클래스의 openConnection() 메소드는 지정된 URL을 읽을 수 있도록 적절하고 구체적인 서브클래스를 리턴한다. http나 https URL을 입력하면 이는 HttpURLConnectionHttpsURLConnection의 서브클래스가 될 것이다. 만약 다음을 openConnection()를 호출하는 라인에 추가하면,

   System.out.println(connection.getClass());

HttpURLConnection의 숨겨진 구현 클래스의 인스턴스를 리턴하는 커넥션(connection)을 보게 된다. 예를 들면, 다음과 같다.

   class sun.net.www.protocol.http.HttpURLConnection

이와 유사하게 보안 페이지를 읽기 위해 동일한 WebPageReader코드를 사용할 수가 있다.

   java WebPageReader https://today.dev.java.net

후자의 경우, 커넥션은 HttpsURLConnection의 서브클래스인 HttpURLConnection타입이라는 것을 알아야 한다. 보다 명확하게 말하자면 다음과 같은 숨어있는 구현 클래스가 있다는 것을 인식할 수 있어야 한다.

   class sun.net.www.protocol.https.HttpsURLConnectionImpl

일반적으로 브라우저에 URL을 입력할 때, 이동하고자하는 페이지가 보안 페이지인지 아닌지는 알 수가 없다. 다시 말하면, today.dev.java.net 페이지를 보기 위해서는 http://today.dev.java.net를 입력한다. 필요하다면 브라우저가 리다이렉트할 것이고 사용자를 https://today.dev.java.net로 연결하기 위해 적절한 신호변경을 수행한다. WebPageReader프로그램이 요청된 리다이렉션을 수행하는지를 살펴보자.

   java WebPageReader http://today.dev.java.net

원하는 페이지로 리다이렉트되는 대신에 다음과 같은 메시지를 보게 된다.

   <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
 <HTML><HEAD>
 <TITLE>301 Moved Permanently</TITLE>
 </HEAD><BODY>
 <H1>Moved Permanently</H1>
 The document has moved
 <A href="https://today.dev.java.net/" mce_href="https://today.dev.java.net/">here</A>.<P>
 <HR>
 <ADDRESS>
 Apache/1.3.26 Server at today.dev.java.net Port 80
 </ADDRESS>
 </BODY></HTML>

이 정보로부터 무언가를 읽어내는 것은 어렵지만 문제는 리다이렉션에 관한 것이 아니다. URL http://linux.java.net으로 프로그램을 실행하면, 프로그램은 http://community.java.net/linux으로 적절히 이를 리다이렉트하고 사용자는 원하는 컨텐츠를 볼 수가 있다. 어떤 일이 일어나는지를 자세히 살펴보려면 HttpURLConnection를 명시적으로 이용해야 한다. 스탠다드 아웃에 웹 페이지의 컨텐츠를 출력하는 코드를 삭제해서 일을 간단히 해보자. RedirectingReader프로그램이다.

   import java.net.URL;
 import java.net.MalformedURLException;
 import java.net.HttpURLConnection;
 import java.io.IOException;
 public class RedirectingReader {
 private static HttpURLConnection connection;
 private static void connect( String urlString ) {
 try {
 URL url = new URL(urlString);
 connection
 = (HttpURLConnection)url.openConnection();
 System.out.println(connection.getURL());
 System.out.println(
 connection.getResponseCode() +
 " " + connection.getResponseMessage());
 System.out.println(connection.getURL());
 } catch (MalformedURLException e){
 e.printStackTrace();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 public static void main(String[] args) {
 if (args.length != 1) {
 System.err.println(
 "usage: java WebPageReader "
 + "<url>");
 System.exit(0);
 }
 connect(args[0]);
 }
 }

다음과 같이 리다이렉트하는 URL을 입력하여 RedirectingReader 를 컴파일하고 실행해 보자.

   java RedirectingReader http://linux.java.net/

다음과 같은 출력값을 보게 될 것이다.

   http://linux.java.net/
 200 OK
 http://community.java.net/linux/

첫번째 라인이 재빨리 나타난다. String값으로 URL을 생성하고 HttpURLConnection객체를 실행하자. 그러면 http://linux.java.net를 요청하는 동안에 잠깐의 멈춤이 생긴다. 페이지가 리다이렉트되는 이 시간동안 받게 되는 응답 코드와 메시지는 리다이렉트된 페이지로부터 온 것이다. 이 리다이렉트된 페이지는 출력값의 3번째 라인에 열거된다. URL url = new URL(urlString);아래의 라인을 추가해서 리다이렉션을 허용하지 않았을 때 발생하는 현상을 보자.

   HttpURLConnection.setFollowRedirects(false);

출력값은 다음과 같다.

   http://linux.java.net/
 302 Found
 http://linux.java.net/

이는 302 에러가 발생했다는 것을 나타낸다. 곧 리다이렉트되지 않고 URL이 그대로 유지되었다는 것이다. 이를 처리하기 전에, 좀 전에 추가했던 라인을 지우고 리다이렉션이 다시 작동하는지를 확인하기 위해 RedirectingReader 프로그램을 실행시켜보자. RedirectingReader를 다시 한번 실행시키고, http://today.dev.java.net 를 입력하자. 다음과 같은 출력값을 보게 된다.

   http://today.dev.java.net
 301 Moved Permanently
 http://today.dev.java.net

위는 이 프로그램이 “Moved Permanently” 에러를 처리할 수 없어서 리다이렉트 했다는 것을 말한다. 이것이 바로 원했던 디폴트 속성이다. 보안문제 때문에 http 과 https간에는 리다이렉트할 수 없다. 어디로 리다이렉트할 것이냐하는 정보는 이 응답의 헤더 부분에 있다. 이전의 요청에 대한 전체 응답 헤더는 다음과 같다.

   HTTP/1.1 301 Moved Permanently
 Date: Tue, 03 Feb 2004 01:38:43 GMT
 Server: Apache/1.3.26 (Unix) mod_ssl/2.8.10
 OpenSSL/0.9.6b
 mod_jk/1.2.1
 Location: https://today.dev.java.net/
 Content-type: text/html; charset=iso-8859-1

HttpURLConnection 내의 getResponseCode()getResponseMessage() 메소드가 이 응답의 첫번째 라인에 포함된 정보를 리턴하는 것을 본 적이 있을 것이다. 혹은 getHeaderField() 메소드를 이용해서 헤더 필드의 이름에 스트링값을 넣어주어도 된다. 가령, getHeaderField("Location")는 https://today.dev.java.net/값을 리턴한다.
이 글의 시작부분에서 언급했던 HTTP 1.1 과 HTTPS RFCs에서 요청과 응답 헤더의 포맷에 관해 자세히 살펴 볼 수 있다. 300 레벨 응답에서, “Location:”는 리다이렉션을 위한 위치값을 제공해야만 한다. 301나 302 에러를 발생하는지를 확인하기 위해 다음 사항을 추가하자.

   private static boolean mustRedirect(int code){
 if (code == HttpURLConnection.HTTP_MOVED_PERM ||
 code == HttpURLConnection.HTTP_MOVED_TEMP)  {
 System.out.println("Received error " + code +
 ", trying secure redirect");
 return true;
 } else return false;
 }

리다이렉트가 자동적으로 처리되지 않는다면 사용자는 301 나 302 응답 코드만을 받게 된다는 것을 기억하자. 지금까지 이것은 리다이렉트가 HttpURLConnection가 아닌 HttpsURLConnection가 필요하다는 것을 의미했다. “Location:” 필드에 제공된 정보를 따르자. 다음과 같이 새로운 정보를 이용해서 url 값과 커넥션을 재설정하자.

   url = new URL("https://"+ url.getHost()
 + url.getFile());
 connection
 = (HttpsURLConnection)url.openConnection();

마지막으로, 웹 리더를 받기 위해 위의 모든 사항을 모으고 필요할 때 보안 페이지로 리다이렉트될 수 있도록 하자.

   import javax.net.ssl.HttpsURLConnection;
 import java.net.URL;
 import java.net.MalformedURLException;
 import java.net.HttpURLConnection;
 import java.io.IOException;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
 public class RedirectingReader {
 private static HttpURLConnection connection;
 private static URL url;
 private static void connect(String urlString) {
 try {
 url = new URL(urlString);
 connection
 = (HttpURLConnection) url.openConnection();
 int code = connection.getResponseCode();
 if (mustRedirect(code))
 secureRedirect(
 connection.getHeaderField("Location"));
 readContents();
 } catch (MalformedURLException e) {
 e.printStackTrace();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 private static boolean mustRedirect(int code) {
 if (code == HttpURLConnection.HTTP_MOVED_PERM ||
 code == HttpURLConnection.HTTP_MOVED_TEMP) {
 return true;
 } else
 return false;
 }
 private static void secureRedirect(String location)
 throws IOException {
 System.out.println(location);
 url = new URL(location);
 connection
 = (HttpsURLConnection) url.openConnection();
 }
 private static void readContents() {
 BufferedReader in = null;
 try {
 in = new BufferedReader(
 new InputStreamReader(
 connection.getInputStream()));
 String inputLine;
 while ((inputLine = in.readLine()) != null) {
 System.out.println(inputLine);
 }
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 public static void main(String[] args) {
 if (args.length != 1) {
 System.err.println("usage: java WebPageReader "
 + "<url>");
 System.exit(0);
 }
 connect(args[0]);
 }
 }

업데이트된 RedirectingReader 를 컴파일하고 동작이 제대로 이루어지는지 확인하기 위해 몇 개의 다른 입력값을 넣어 실행해보자. 예를 들면 http://today.dev.java.nethttp://linux.java.net를 넣어보자. 리다이렉트되지 않는 보안페이지와 비보안 페이지를 입력해서 RedirectingReader를 실행해도 동작이 적절하게 이루어져야 한다. 이제 다시 뒤로 돌아가서 최초 WebReader 프로그램에 모든 println() 메소드를 제거하고 readContents()를 추가할 수 있다

Java 성능개선을 위한 Programming 기법 |

Filed under: JAVA — rothmans @ 3:27 pm

Java 성능개선을 위한 Programming 기법

JDK1.3버전 이후로 지원되는 HotSpot VM은 기본적으로 Hip에 동적으로 할당된 Object는 거의 회수할 수 있다고 한다. 하지만 이 기능으로 인해서 VM은 엄청난 OverHead를 가지게 된다. 무리한 Object의 생성은 생성 당시에도 많은 OverHead를 초래하지만, 생성된 Object를 회수하기 위해서는 더 많은 작업이 요구된다. 이를 해결하기 위한 몇 가지 Tip이 있는데, Avoiding Garbage Collection, Object 재사용, static instance variable의 사용에 의한 단일 클래스 인스턴스 구현 방법 등이 그것이다. 핵심은 가능한 Object 생성을 피하자는 것이다.

▶ Avoiding Garbage Collection(static method사용)
예제1)
String string=”55″;
int theInt=new Integer(string).intValue();

예제2)
String string=”55″;
int theInt=Integer.parseInt(string);

예제1)에서는 Integer 클래스를 생성한 다음 string에서 정수값을 추출해 냈다. Object를 생성하고 초기화하는 과정은 상당한 cpu 집약적인 작업이고, Hip 영역에 Object가 생성되고 수집되기 때문에 가비지 컬렉터 작업을 수반한다. 가능한 Object의 Instance가 필요 없는 static 메소드를 사용한다. 예제2) 참조.

▶ Avoiding Garbage Collection(임시 Object 생성 금지)
가장 흔한 예로 String Object의 append를 위해서 (+) 연산을 사용하는 것을 들 수 있다. (+) 연산자를 사용해서 String Object를 append할 경우 우리가 생각하는 것 보다 훨씬 더 많은 임시 Object가 생성되고, 가비지 컬렉터에 의해 다시 수집된다. String Object의 append연산을 위해서는 StringBuffer 클래스를 사용한다. 예제1)과 예제2) 참조

예제1)
String a=”Hello”;
a=a+”World”;
System.out.println(a);

예제2)
StringBuffer a=new StringBuffer();
a.append(“Hello”);
a.append(“World”);
System.out.println(a.toString());

어떤 메소드는 Object의 복사본을 반환하는 경우가 있다. 대표적인 예로 스트링 클래스의 trim() 메소드를 들 수 있다. trim()메소드가 수행되면 기존의 Object는 수집되고, 기존 Object의 복사본이 사용되게 된다. 임시 Object 생성과 복사본을 반환하는 메소드가 루프 안에서 사용될 경우 무수히 많은 Object가 생성되고 수집되기 때문에 심각한 문제를 야기하게 된다. 루프 내부에서 Object를 생성하는 것은 가급적 피해야 한다.

예제3)
for(i=0;i<1000;i++){
Date a=new Date();
:
}

예제4)
Date a;
for(i=0;i<1000;i++){
a=new Date();
:
a=null;
}

예제3)과 예제4)는 현재 날짜와 시간을 얻어오기 위해 루프 안에서 Object를 생성했다. 보통 set으로 시작하는 메소드는 Object의 Instance 값을 재정의 한다. API를 충분히 참조한 다음 지원 메소드가 없을 경우, 클래스를 상속받아 요구에 적합한 메소드를 만드는 방법도 고려할 필요가 있다. 기존 API를 이용한 Object 초기화 방법은 아래 Object 재사용(메소드를 사용한 Object 초기화) 부분 참조.

▶ Avoiding Garbage Collection(primitive data type 사용)
Date 클래스나 String 클래스의 값들중 int나 long 형으로 표현하여 사용할 수 있는 경우가 있다. 예를 들어

String a=”1492″;
String b=”1997″;

과 같을 경우 a와 b는 굳이 String 형으로 표현하지 않아도 된다. 하지만 여기서 주의할 점은 Object의 값을 기본 데이타형으로 Casting 하는 작업이 오히려 시간이 더 많이 걸릴 수도 있는 것이다.
클래스의 인스턴스 변수로 기본 데이타형을 사용하면 Object의 크기도 줄어들고, Object 생성 시간도 줄일 수 있다.

▶ Object 재사용(Pool Management)
Object 재사용 기법으로 흔히 Pool Management 기법을 사용한다. 이는 임의 갯수의 Object를 미리 생성해 두고 이를 Vector 클래스를 사용해서 관리하는 방법이다. 해당 Object를 사용하기 위해서 Pool의 Vector에 있는 Object를 하나 가져오고, 다 사용하고 나면 다시 Pool에 반납한다. 이는 기존에 공개되어 있는 Hans Bergsten의 Connection-Pool의 PoolManager클래스에서 사용되고 있다.(Professional JAVA Server Programming. 정보문화사. 2000.4.4. 제9장 연결풀링 부분 참조) Object Pool을 사용할 경우, 반환되는 Object가 초기화되어 반환되지 않을 경우 다음에 Pool에서 Object를 가져와서 사용하게 되면 문제를 야기할 수 있기 때문에 초기 클래스 Design시 꼼꼼하게 따져 봐야 한다.

▶ Object 재사용(메소드를 사용한 Object 초기화)
예제1)
StringBuffer sb=new StringBuffer();
sb.append(“Hello”);
out.println(sb.toString());
sb=null;
sb=new StringBuffer();
sb.append(“World”);
out.println(sb.toString());

예제1)과 같이 사용할 경우 하나의 인스턴스 변수를 사용하기는 하지만, 두 번의 초기화 과정을 거치게 된다.

예제2)
StringBuffer sb=new StringBuffer();
sb.append(“Hello”);
out.println(sb.toString());
sb.setLength(0);
sb.append(“World”);
out.println(sb.toString());

위와 같이 각 클래스에서 지원해 주는 메소드를 사용하여 Object를 재사용 할 수 있다.

▶ static instance variable의 사용에 의한 단일 클래스 인스턴스 구현
다음은 Hans Bergsten의 PoolManager 클래스 코드 중 일부다.

public class PoolManager{
static private PoolManager instance;
:
private PoolManager(){
init();
}
static synchronized public PoolManager getInstance(){
if (instance == null){
instance = new PoolManager();
}
:
return instance;
}
private void init(){
:
}

PoolManager형의 인스턴스가 static으로 선언되어 있다. getInstance() 메소드는 현재 생성되어 있는 PoolManager의 Object를 조사하고 만약 Object가 있으면 Object를 반환하고 없으면 생성자를 호출해서 PoolManager의 Object를 생성한 후 반환한다. 결국 JVM 내부에는 하나의 PoolManager Object가 존재하게 된다. 단일 클래스 인스턴스 기법을 사용할 경우 하나의 인스턴스를 사용하기 때문에 해당 인스턴스의 무결성 부분이 문제가 된다. 이를 위해 다양한 Synchronization 기법을 사용하게 된다. 아래 I/O 퍼포먼스 개선 부분 참조.

▶ clone() 메소드 사용으로 Object 생성에 따른 OverHead를 피함

private static int[] data = new int[2][2];
int[] someMethod(){
int[] a = (int[])this.data.clone();
return a;
}

대부분의 클래스들에는 clone() 메소드가 존재한다. clone() 메소드가 호출되면 Object의 복사본을 반환하는데, 대신 클래스의 생성자를 호출하지 않기 때문에 생성자 호출에 의한 OverHead를 피할 수 있다. clone() 메소드를 사용할 때의 trade-off 문제는 다음에 예제를 참조 할 것.

static int[] Ref_array1={1,2,3,4,5,6,7,8,9};
static int[][] Ref_array2={{1,2},{3,4},{5,6},{7,8}};

int[] array1={1,2,3,4,5,6,7,8,9}; //faster than cloning
int[] array1=(int[])Ref_array1.clone(); //slower than initializing

int[][] array2={{1,2},{3,4},{5,6},{7,8}}; //slower than cloning
int[][] array2=(int[][])Ref_array2.clone(); //faster than initializing

▶ Method Inline에 의한 method호출 감소
예제1)
public class InlineMe{
int counter=0;
public void method1(){
for(int i=0;i<1000;i++){
addCount();
System.out.println(“counter=”+counter);
}
public int addCount(){
counter=counter+1;
return counter;
}
public static void main(String args[]){
InlineMe im=new InlineMe();
im.method1();
}
}

예제1)에서 addCount() 메소드를 다음과 같이 수정하면

public void addCount(){
counter=counter+1;
}

위와 같이 수정할 경우 addCount() 메소드는 컴파일시 Inline 되어서 실제 메소드를 호출하지 않고 같은 결과를 반환한다. 즉 method1()이 실제 수행될 때는 다음과 같이 수행.

public void method1(){
for(int i=0;i<1000;i++){
counter=counter+1;
System.out.println(“counter=”+counter);
}

▶ 생성자 설계
예제1,2,3) 모두 같은 역할을 하는 생성자들로 구성되어 있다. 하지만 퍼포먼스 측면에서 보면 예제3)이 가장 효율적이다. 하지만 클래스를 설계 할 때 예제1)과 같이 해야 할 때도 있다. 클래스가 요구하는 조건에 따라 생성자의 설계방법이 다르겠지만, Object를 생성할 때 가능한 생성자를 적게 호출하는 방법을 사용하는 것이 퍼포먼스 면에서 좋다는 것은 당연한 일이다.

예제1)
예제2)
예제3)
class SlowFlow{
private int someX, someY;
SlowFlow(){
this(777);
}
SlowFlow(int x){
this(x,778);
}
SlowFlow(int x, int y)
someX=x;
someY=y;
}
}
class SlowFlow{
private int someX, someY;
SlowFlow(){
this(777,778);
}
SlowFlow(int x){
this(x,778);
}
SlowFlow(int x, int y)
someX=x;
someY=y;
}
}
class SlowFlow{
private int someX, someY;
SlowFlow(){
someX=777;
someY=778;
}
SlowFlow(int x){
someX=x;
someY=778;
}
SlowFlow(int x, int y)
someX=x;
someY=y;
}
}

▶ “extends” VS “implements”
예제1) extends
import java.awt.event.*;
import java.awt.*;
public class MyWindowAdapter extends WindowAdapter{
public void windowClosing(WindowEvent we){
Container source=(Container)we.getSource();
source.setVisible(false);
}
}

예제2) implements
import java.awt.event.*;
import java.awt.*;
public class MyWindowListener implements WindowListener{
public void windowClosing(WindowEvent we){
Container sourve=(Container)we.getSource();
source.setVisible(false);
}
public void windowClosed(WindowEvent we){}
pubic void windowActivated(WindowEvent we){}
public void windowDeactivated(WindowEvent we){}
public void windowIconified(WindowEvent we){}
public void windowDeiconified(WindowEvent we){}
public void windowOpened(WindowEvent we){}

“implements”의 경우에는 특정 메소드를 구현하고 인터페이스에 정의된 모든 메소드를 코딩해야 하기 때문에 코드의 낭비를 초래하는 반면, “extends”의 경우에는 슈퍼 클래스에 정의된 메소드들 중 필요한 메소드만 overriding 하면 된다. ==> 설계 시 추상클래스를 사용할 것인지 인터페이스를 사용할 것인지 고려.

▶ 클래스 집약
예제1)
public class Person{
private Name name;
private Address address;
}
class Name{
private String firstName;
private String lastName;
private String[] otherNames;
}
class Address{
private int houseNumber;
private String houseName;
private String streetName;
private String town;
private String area;
private String greaterArea;
private String country;
private String postCode;
}

예제1)에서 정의된 Person 클래스는 Name형의 name과 Address형의 address, 두 개의 인스턴스 변수를 가진다. 클래스를 설계할 때 가능한 클래스의 수를 줄여서 수행 시 동적으로 생성되는 Object의 수를 줄일 수도 있다. 예제1)에서 정의된 세 개의 클래스는 하나의 클래스로 집약될 수 있다.

예제2)
public class Person{
private String firstName;
private String lastName;
private String[] otherNames;
private int houseNumber;
private String houseName;
private String streetName;
private String town;
private String area;
private String greaterArea;
private String country;
private String postCode;
}

▶ I/O 퍼포먼스 개선
자바에서는 자료를 읽거나 쓰기 위해 stream을 사용한다. 자바는 두가지 형태의 stream을 지원한다. Readers and Writers와 Input and Output stream이 그것이다. Reader and Writers는 high-level의 I/O(예. String)를 지원하고 Input and Output stream은 low-level의 I/O(byte)를 지원한다. 속도 향상을 위해서는 Buffered stream을 사용한다. Buffered stream을 사용할 경우 버퍼의 기본값은 2K이다. 이 값은 조정될 수 있으나, 자료의 용량이 클 경우 메모리가 많이 필요하기 때문에 Buffered stream을 사용할 경우 여러 가지 사항을 고려해야 한다.

예제1) Simple file copy
public static void copy(String from, String to) throws IOException{
InputStream in=null;
OutputStream out=null;
try{
in=new FileInputStream(from);
out=new FileOutputStream(to);
while(true){
int data=in.read();
if(data==-1)
break;
out.write(data);
}
in.close();
out.close();
}finally{
if(in!=null){in.close();}
if(out!=null){out.close();}
}
}

Buffered stream을 사용하지 않고 I/O를 했을 경우의 예제이다. 370K의 JPEG 파일을 복사하는데 10800ms.

예제2) Faster file copy
public static void copy(String from, String to) throws IOException{
InputStream in=null;
OutputStream out=null;
try{
in=new BufferedInputStream(new FileInputStream(from));
out=new BufferedOutputStream(new FileOutputStream(to));
while(true){
int data=in.read();
if(data==-1)
break;
out.write(data);
}
}finally{
if(in!=null){in.close();}
if(out!=null){out.close();}
}
}

Bufferd stream을 사용해서 퍼포먼스를 개선한 예제이다. 예제1)과 같은 파일을 복사하는데 130ms.

예제3) Custom buffered copy
public static void copy(String from, String to) throws IOException{
InputStream in=null;
OutputStream out=null;
try{
in=new FileInputStream(from);
out=new FileOutputStream(to);
int length=in.available();
byte[] bytes=new byte[length];
in.read(bytes);
out.write(bytes);
}finally{
if(in!=null){in.close();}
if(out!=null){out.close();}
}
}

while루프를 사용하지 않고 배열을 사용함으로서 퍼포먼스를 개선한 예제이다. 예제1)과 같은 파일을 복사하는데 33ms. 하지만 예제3)은 byte배열로 선언되는 메모리 버퍼의 크기가 실제 파일의 크기와 동일해야 한다. 이에 따라 두 가지의 문제점이 발생할 수 있다. 첫 번째는 파일의 용량이 클 경우 상당한 메모리 낭비를 초래한다는 점이다. 두 번째 문제점은 copy()메소드가 수행될 때마다 new byte[]에 의해 버퍼가 새로 만들어진다는 점이다. 만일 파일의 용량이 클 경우 버퍼가 만들어지고 Garbage Collector에 의해 수집될 때 상당한 OverHead를 초래할 수 있다.

예제4) Improved custom buffered copy
static final int BUFF_SIZE=100000;
static fianl byte[] buffer=new byte[BUFF_SIZE];
public static void copy(String from, String to) throws IOException{
InputStream in=null;
OutputStream out=null;
try{
in=new FileInputStream(from);
out=new FileOutputStream(to);
while(true){
synchronized(buffer){
int amountRead=in.read(buffer);
if(amountRead==-1)
break;
out.write(buffer,0,amountRead);
}
}finally{
if(in!=null){in.close();}
if(out!=null){out.close();}
}
}

크기가 100K인 byte 배열을 임시버퍼로 지정하고, 이를 static으로 선언함으로서 퍼포먼스를 개선했다. 예제1)과 같은 파일을 복사하는데 22ms. static buffer의 사용으로 I/O작업을 수행할 경우 발생할 수 있는 문제점을 해결하기 위해 synchronized block을 사용했다. 비록 synchronization을 사용함에 따라 성능 저하를 초래하지만, 실제 while 루프에 머무는 시간이 극히 짧기 때문에 퍼포먼스에 문제는 없다. 테스트에 의하면 synchronized 버전과 unsynchronized 버전 모두 같은 시간에 수행을 완료했다.

▶ 웹 환경에서 Caching을 이용한 자바 퍼포먼스 개선
웹 환경에서 Object caching 기법은 주로 DB나 파일에서 동일한 내용을 가져오는 루틴에서 사용된다. DBMS에 sql문을 던지고 결과를 받아오는 부분의 내용이 거의 변동이 없을 경우 요청 시마다 매번 sql문을 실행시켜서 결과를 받아오는 것이 아니라 최초 실행된 값을 그대로 반환하는 기법이다. 그리고 시간 Check 기법을 이용해서 특정 시간 경과 후 요청이 들어오면 이전 요청에 의해 수행되어진 값을 갱신해서 반환한다. 결과에 의한 반환값이 메모리에 부담이 되지 않을 정도로 크지 않은 경우, 실시간으로 변경된 정보를 반환값으로 사용하는 루틴이 아닐 경우 유용하게 사용될 수 있다.

▶ Performance CheckList
. 임시로 사용하기 위해 Object를 생성하는 것을 피하라. 특히 Loop에서..
. 빈번하게 호출되는 메소드에서 Object를 생성하는 것을 피하라.
. 가능한 Object를 재사용 하라.
. 임시 Object의 생성을 줄이기 위해 데이타 타입 컨버전 메소드를 재정의 하는 방법을 고 려하라.
. 메소드 설계 시 데이타를 유지하고 있는 Object를 반환하는 메소드보다 데이타로 채워진 재사용 가능한 Object에 접근하는 메소드를 정의하라.
. string이나 Object를 integer 로 대치하라. Object 비교를 위해 equal() 메소드를 호출하지 말고 기본 데이타 타입의 == 연산자를 사용하라.
. 인스턴스 변수로 기본 데이타 타입을 사용하라.
. 단지 메소드 호출을 위해 Object를 생성하는 것을 피하라.
. String 연속 연산자 (+)를 사용하는 것보다 StringBuffer 클래스를 사용하라.
. 복사본을 생성하는 메소드 보다 Object를 직접 수정하는 메소드를 사용하라.
. 생성자는 간단하게… 상속 계층은 얕게…
. 인스턴스 변수를 초기화 하는 것은 한 번 이상 하지 말 것.
. 생성자 호출을 피하기 위해 clone() 메소들 사용할 것.
. 간단한 배열일 경우에는 초기화를.. 복잡한 배열일 경우에는 clone() 메소드 호출.
. 프로그램 내부에서 Object 생성 시기를 조절해서 Object 생성에 따른 bottlenecks를 없앤 다.
. 어플리케이션 내부에서 여분의 시간이 허용된다면 가능한 Object를 빨리 생성하라. 생성 된 Object를 내부적으로 유지하고 있다가 요청이 발생하면 할당하라.
. 사용 가능성이 희박하거나, 분산처리에 의해 Object를 생성할 경우 Object는 생성 시기를 늦춰라.

기본적인 코딩 표준

Filed under: JAVA — rothmans @ 3:26 pm

기본적인 코딩 표준

1.클래스명
  : 첫문자는 대문자로 시작하고, 구분되는 부분도 대문자로 시작한다.

2.메소드명
:첫문자는 소문자로 시작하고, 구분은 대문자로 짓는다.메소드명에는 대조할 수 있는
  이름을 붙인다.

3.섀도우 필드는 작성하지 않는다.
  :섀도우 필드, 즉 부모 클래스의 필드명과 동일한 필드명은 버그의 원인이 되므로,
   가능한 한 사용하지 않는다. 언어의 사양에 따라 컴파일러는 통과할 수 있다.

4.외부 리소스는 반드시 해제한다.
  :파일 입출력(I/O)과 데이터베이스의 커넥션,커서는 확실하게 close메소드를 실행하지
   않으면 자원이 해제되지 않는다. 반드시 finally에서 close 메소드를 실행하도록 한다.

ex) Connection = con =null;
     try
     {
     //처리
     }
     finally
     {
      if(con !=null)
      {
        con.close();
      }
     }

5.catch한 예외는 반드시 처리한다.
  :catch한 예외에 대해 아무런 대처도 하지 않으면 디버그를 곤란하게 만든다. 반드시 로그
   출력을 하거나 throw한다. 이유가 있어서 무시하게 될 경우는 무시하는 이유을 주석으로
   붙인다.

String의 + 와 StringBuffer에 대해…

Filed under: JAVA — rothmans @ 3:26 pm

많은 곳에서 String의 + 보다 StringBuffer를 사용하는것이 성능에 더 좋다고 얘기한다.
(String의 + 연산은 내부적으로 StringBuffer로 처리한 후 toString()한다.)
그리고 이를 본 일부 개발자는 무조건 StringBuffer로만 코딩을 하려고 한다.

StringBuffer의 사용은 성능에는 좋을지 모르나 소스의 가독률은 낮아진다.

내가 지금까지 코딩을 하면서 긴문장의 String을 많이 사용했던 곳이 쿼리를 작성할때였다.

아래와 같은 코드일 것이다.

—————————————————-
  String a = “AAA”;

  String query = “select A, B, C, D, E, F  \n”
               + “from TABLE               \n”
               + “where A=’” + a + “‘      \n”;
—————————————————-

공백을 넣어 줄을 맞춘 이유는 소스에서 쿼리를 복사하기가 편하고 보기에도 좋다.
\n을 넣은 이유는 로그를 보기 편하게 하기 위해서다.
(프로젝트를 하면서 소스나 로그에서 쿼리를 따서 직접 던져보는 작업을 많이 한다.
이때 에디터의 컬럼모드 기능을 사용하면 편리하다.)

이를 javap로 확인하면 결과는 다음과 같다.
(javap는 class가 실행될때 VM이 실제로 어떻게 동작하는지를 보여준다.
사용 : javap -c 클래스명)

—————————————————-
   0:        ldc        #2; //String AAA
   2:        astore_1
   3:        new        #3; //class StringBuffer
   6:        dup
   7:        invokespecial        #4; //Method java/lang/StringBuffer.”<init>”:()V
   10:        ldc        #5; //String select A, B, C, D, E, F  \nfrom TABLE               \nwhere A=’
   12:        invokevirtual        #6; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
   15:        aload_1
   16:        invokevirtual        #6; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
   19:        ldc        #7; //String ‘      \n
   21:        invokevirtual        #6; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
   24:        invokevirtual        #8; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
   27:        astore_2
   28:        return
—————————————————-

10: 를 보면
  ”select A, B, C, D, E, F  \n”
+ “from TABLE               \n”
+ “where A=’”
가  

“select A, B, C, D, E, F  \nfrom TABLE               \nwhere A=’”

로 1개의 String으로 만들어지는걸 볼수 있다.
(class를 jad로 역컴파일한 java소스에는 하나로 합쳐져 있다.
아마 컴파일단계에서 합치는걸로 보여진다.)

위 코드의 javap의 결과가 비슷한 StringBuffer형태는 다음과 같다.

—————————————————-
  String a = “AAA”;

  StringBuffer buf = new StringBuffer();
  buf.append(“select A, B, C, D, E, F  \nfrom TABLE               \nwhere A=’”)
     .append(a).append(“‘      \n”);

  String query = buf.toString();
—————————————————-

StringBuffer형태의 코드를 보기 좋게 만들어보면

—————————————————-
  StringBuffer buf = new StringBuffer();
  buf.append(“select A, B, C, D, E, F         \n”);
  buf.append(“from TABLE                      \n”);
  buf.append(“where A=’”).append(a).append(“‘ \n”);

  String query = buf.toString();
—————————————————-

이를 javap로 확인하면
“select A, B, C, D, E, F         \n”
“from TABLE                      \n”
“where A=’”
가 3개의 String으로 처리된다.

——————————————————
  String query = “select A, B, C, D, E, F  \n”
               + “from TABLE               \n”
               + “where A=’” + a + “‘      \n”;
——————————————————
  StringBuffer buf = new StringBuffer();
  buf.append(“select A, B, C, D, E, F         \n”);
  buf.append(“from TABLE                      \n”);
  buf.append(“where A=’”).append(a).append(“‘ \n”);
  String query = buf.toString();
——————————————————
이 두 코드는 첫번째 코드가(String +)가 여러모로 더 좋은 결과를 가져온다.

하지만,
  String query = “”;

  query += “select A, B, C, D, E, F  \n”;
  query += “from TABLE               \n”;
  query += “where A=’” + a + “‘      \n”;

이러한 코딩을 한다면 얘기는 달라진다.

StringBuffer는 +가 있는 한문장(;)마다 새로 만들어질것이다.
위는 총 3개가 만들어진다.

  String query = “”;
  for (int i=0; i<size; i++) {
    query += “”;
  }  

이런 코드는 size가 크면 클수록 많은 성능 차이를 보일것이다.
(많다고 해봐야 몇십초가 더 걸리는것도 아니다. 하드웨어가 좋아져서…)

결론
여러곳의 String의 + 와 StringBuffer의 비교 글에서 “A” + “B” 가 “AB”로 되는것에 대한 언급이 없고
javap도 한번 사용해보라고 이 글을 썻습니다.
자기만의 코딩스타일이 있고 이것이 프로그램에 더 효율적인 방식이라 하더라도
코딩하기 편하고 가독률이 좋은 코드라면 약간의 성능저하는 무시할 수도 있지 않나 생각합니다.

System.in.read()를 이용하여 구구단 출력

Filed under: JAVA — rothmans @ 3:26 pm
import java.io.IOException;public class  Gugudan
{
  public static void main(String[] args)         {

  char ch = 0;
  int num = 0;
  int temp = 0;

  try{
    System.out.print(“Enter a number >> “);
        
    ch = (char)System.in.read ();
        
    Character chObj = new Character(ch);
        
    String numStr = chObj.toString();
        
    num = Integer.parseInt(numStr);

    System.out.println(“– GuGuDan Start –”);

    while(temp < 9){
      temp++;
      System.out.println(num + ” * ” + temp + ” = ” + num*temp);
    }
    System.out.println(“– GuGuDan End –”);
  }catch(IOException e){}
}
}

 

[소스]파일 이어 쓰기

Filed under: JAVA — rothmans @ 3:25 pm

기존파일에 덧붙여서 내용을 저장하려면, RandomAccessFile 클래스를 사용해야 합니다.

아래는 간단한 예제 입니다.

—————————————————————————
import java.io.IOException;
import java.io.RandomAccessFile;

public class UsingFile {
  public UsingFile() {
  }

  public static void main(String[] args) {
    try {
      String name = “c:\\tmpfile.txt”;
      RandomAccessFile raf = new RandomAccessFile(name, “rw”);
      raf.seek(raf.length());
      raf.writeBytes(“\r\n append”);
    }
    catch (IOException e) {
      System.out.println(“Error opening file: ” + e);
    }
  }
}

System의 유효한 드라이브명(루트패스) 얻기

Filed under: JAVA — rothmans @ 3:25 pm

현재 자신의 시스템에서 유효한 루프패스(드라이브명) 얻기

소스)
File a[] = File.listRoots();
System.out.println(“Drive Count : ” + a.length);
for(int i=0; i < a.length; i++){
   System.out.println(“Drive Name : ” + a[i].getPath());
}

결과)
Drive Count : 6
Drive Name : A:\
Drive Name : C:\
Drive Name : D:\
Drive Name : E:\
Drive Name : F:\
Drive Name : H:\

Log4j란?

Filed under: JAVA — rothmans @ 3:25 pm
프로그램을 개발하는 사람이라면 누구나 로그(log)를 남기게 마련이다. 자바로 프로그램을 하거나 C++을 사용하거나 비주얼 베이직을 사용하더라도 마찬가지이다. 어떠한 형태로던지 로그를 남겨서 자신이 작성한 프로그램이 정상적으로 작동하는지, 혹은 실행 도중에 문제가 생겼을 때에도 로그를 참고해서 어느 부분에 문제가 있는지 찾아보곤 한다.개발자가 아닌 시스템을 관리하는 경우에도 로그는 매우 중요하다. 보안 담당자의 경우 누가 시스템에 불법적(?)으로 접근하는지 못된 짓(?)을 하는지 감시할 때에도 로그가 중요한 역할을 한다. 이렇게 다양한 사람들과 용도로 사용하는 로그를 여러분은 어떻게 생성하고 관리하고 있을까? 자바 프로그래머라면 System.out.println 을 사용할 수도 있고 C 프로그래머라면 printf 를 사용할지도 모른다. 단순한 로그라면 상관 없겠지만 로그의 형태도 다양하고 여러 조건이 주어져야 한다면 이런 단순한 방법을 사용해서는 원하는 목표를 달성하기 힘들 것이다.

서울에서 부산까지 가는 방법에는 여러 가지가 있다. 고속 철도를 이용하거나, 기차, 자가용, 자전거, 심지어 걸어서 가는 방법도 있다. 여러분은 어떤 방법을 선택하겠는가? 당연히 일반적인 경우라면 좀더 편하고 빠른 방법을 택할 것이다. 로그를 남기는 방법에도 마찬 가지로 좀 더 편하고 다양한 기능과 성능까지 보장하는 방법이 있다면? 그렇다. 바로 log4j 를 사용하는 것이다.

Log4j는 이런 시대의 다양한 욕구를 충족시키기 위해 등장한 것이다. 이미 log4j 프로젝트가 시작 된지 몇 년이 지났고 그 동안 충분히 검증되었으며 업계에서도 표준처럼 굳어져 있어서 많은 상용 제품에도 포함되어 있다. JDK 1.4 버전부터는 일부 로깅 API가 추가되었지만 log4j 처럼 막강하지는 않는 듯 하다. 지금부터 몇 가지 예제를 통해서 log4j 의 편하고 강력한 기능을 살펴 보기로 하자.

* log4j 에 소개에 대한 한글 사이트는 아래 링크를 참고하자.
http://jakarta.apache-korea.org/log4j/

1. Hello log4j

역시나 처음 프로그램을 시작할 때 빠지지 않는 단골인 Hello 프로그램으로 시작하자.
Log4j 의 설치나 다운로드는 아래 주소나 다른 참고 문헌을 살짝 훔쳐 보도록 하자.

http://logging.apache.org/log4j/docs/
Ant 와 log4j 활용

설치가 정상적으로 이루어졌다면 다음 소스 코드를 보자. 소스에 대한 설명은 주석을 참고하면 되겠다.

import org.apache.log4j.Logger;
import org.apache.log4j.BasicConfigurator;

public class SimpleLog {
  
  // Logger 클래스의 인스턴스를 받아온다.
  static Logger logger = Logger.getLogger(SimpleLog.class);

  public SimpleLog() {
  }

  public static void main(String[] args) {
    
    /*
    콘솔로 로그 출력 위한 간단한 설정,
    이 설정이 없다면 경고 메세지가 출력되면서 실행이 중단된다.
    */
  
    BasicConfigurator.configure();

    while (true) {
      /*
      로그 레벨에는 아래의 5가지가 있다.
      적당한 레벨을 지정해두면 나중에 여러 가지로 손발이 편하다.
      */
      logger.debug(“Hello log4j.”);
      logger.info(“Hello log4j.”);
      logger.warn(“Hello log4j.”);
      logger.error(“Hello log4j.”);
      logger.fatal(“Hello log4j.”);

      try {
        // 잠시 쉬자 ^^
        Thread.sleep(500);
      }
      catch (Exception e) {
      }
    }
  }

}

소스를 컴파일하고 실행하면 아래와 같이 실행된다.

맨 앞에 등장하는 숫자는 로깅 호출로부터 얼마만큼의 시간이 지났는지를 밀리세컨드 단위로 보여준다. 이 정보는 별로 유용할 것 같지는 않다. 다음에 보이는 [main] 은 호출한 쓰레드의 이름을 나타낸다. 다음에 나타나는 DEBUG , INFO , WARN 등은 로그 레벨이다. 소스 코드에서 지정한 레벨을 그대로 출력해주고 있다. 그 뒤에 나오는 것은 보시다시피 클래스의 이름과 실제 로그 메시지를 보여준다. 특별히 유용한 예제는 아니지만 사용하는 방법에 따라 System.out.println 보다는 그럴 듯 해 보인다. 예제에는 포함되어 있지 않지만 로그를 출력할지를 결정하는 플래그 값을 사용하거나 레벨로 제한을 하도록 수정하면 금상첨화일 것이다.

2. 하루에 하나씩

앞서 살펴본 Hello 프로그램으로 log4j 에 대한 허탈감을 느낀다면 아직 이르다. 이번에 소개할 예제는 실제 업무에 많이 사용되는 것이다.
로그를 콘솔로 출력한다면 스크롤 되어 지나간 로그에 대해 아무 것도 할 수 없으므로 대략 낭패가 되겠다. 그런 문제 때문에 대부분의 로그는 파일로 저장하는 것이 일반적인데 파일로 저장하는 것에도 문제점이 있다. 바로 파일 사이즈 문제이다. 요즘 아무리 하드 디스크 가격이 싸다고 하지만 심한 경우 하루에도 로그 파일의 사이즈가 몇 백 메가바이트를 넘어 가는 경우도 있기 마련이다. 그런 파일이 일주일, 한 달이 지나면… 파일 하나가 수 기가 바이트에 이르게 된다. 이 파일을 열어서 원하는 것을 검색한다면 이 또한 낭패가 아닐 수 없다. 로그 파일 사이즈가 너무 커서 부담스러운 것이다. 그래서 대부분의 경우 로그 파일을 하루 단위로 저장을 하여 사이즈도 너무 커지지 않고 일정 기간이 지나서 필요 없다고 판단되면 과감히 지울 수 있도록 한다. 본인이 현재 수행하는 프로젝트에서도 로그 파일을 앞서 설명한 방식으로 저장을 하고 있다. 이제부터 소스 코드를 살펴보자.

import java.io.IOException;

import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;

public class DailyLog {
  
  static Logger logger = Logger.getLogger(DailyLog.class);
  
  static void doIt() {
    logger.info(“Test info”);
  }

  public static void main(String[] args) {
    // 로그 파일의 내용에 대한 패턴을 정의한다. 설명은 결과를 보면서 ^^
    String pattern = “[%d{yyyy-MM-dd HH:mm:ss}] %-5p [%l] – %m%n”;
    PatternLayout layout = new PatternLayout(pattern);
    
    // 처음 생성될 로그 파일의 이름
    String filename = “DailyLog.log”;
    
    // 날짜 패턴에 따라 추가될 파일 이름
    String datePattern = “.yyyy-MM-dd”;
    
    DailyRollingFileAppender appender = null;
    try {
      appender = new DailyRollingFileAppender(layout, filename, datePattern);
    } catch (IOException ioe) {
      ioe.printStackTrace();
    }
    
    logger.addAppender(appender);
    
    while (true) {
      logger.debug(“Hello log4j.”);

      try {
        Thread.sleep(1000 * 60);
      }
      catch (Exception e) {
      }
      doIt();
    }

  }
}

소스 코드를 컴파일하고 실행하면 디렉토리에 DailyLog.log 파일이 생성되며 파일의 내용은 아래와 같다.

[2004-06-02 02:37:30] DEBUG [simplelog.DailyLog.main(DailyLog.java:32)] – Hello log4j.
[2004-06-02 02:38:30] INFO [simplelog.DailyLog.doIt(DailyLog.java:14)] – Test info
[2004-06-02 02:38:30] DEBUG [simplelog.DailyLog.main(DailyLog.java:32)] – Hello log4j.
[2004-06-02 02:39:30] INFO [simplelog.DailyLog.doIt(DailyLog.java:14)] – Test info
[2004-06-02 02:39:30] DEBUG [simplelog.DailyLog.main(DailyLog.java:32)] – Hello log4j.

파일의 내용은 소스 코드에서 정의된 패턴인 “[%d{yyyy-MM-dd HH:mm:ss}] %-5p [%l] – %m%n” 에 따라 생성된 것이다. 출력된 것과 대조해보면 대충 어떤 의미인지 유추가 가능하다. 첫번째 내용은 날짜를 나타낸다. 날짜를 나타내는 형태도 SimpleDateFormat 을 사용하는 것처럼 원하는 형태로 가능하다. 다음에 보이는 것은 로그 레벨이며 다음에 보이는 것은 [패키지 이름.클래스 이름.메소드 이름(소스 파일 이름:소스 라인 넘버)] 이다. 그리고 마지막으로 실제 로그 내용을 보여준다. 정말 대단하지 않은가? 간단한 코딩 몇 줄만으로 이렇게 아름다운(?) 로그가 출력되다니 감동이 파도처럼 밀려온다. 출력 패턴은 이외에도 많은 것들이 있는데 자세한 사항은 log4j API 문서를 참고하도록 하자.

이 예제 프로그램의 기능은 여기서 멈추지 않는다. 프로그램을 계속 실행하면서 밤 12시 정각이 지나면 조금 전까지 로그를 출력하던 파일은 백업이 되면서 다시 새로운 로그를 쌓기 시작한다. 예를 들어 2004년 6월 2일 11시부터 프로그램이 시작되었다. 이때 처음 생성된 로그 파일 이름은 DailyLog.log 이다. 잠시 후 밤 12시가 지나고 6월 3일이 되면 2일까지 출력된 로그들은 DailyLog.log.2004-06-02 라는 파일로 저장이 되며 3일의 로그는 DailyLog.log 에 출력된다. 이런 식으로 DailyLog.log.2004-06-03 , DailyLog.log.2004-06-04 파일이 생성되며 항상 그날의 로그는 DailyLog.log 파일에 저장이 되는 것이다. 이런 놀라운 기능에 더 이상 말이 필요 없다.

여기서 잠깐! 여러분 중에 이런 궁금증을 가질 수도 있겠다. “프로그램이 종료되었다 다시 시작되면 그전까지 쌓였던 로그의 내용은 어떻게 되나?” 이미 테스트 해보았다 ^^. 로그 파일의 이전 로그에 덧붙여서 다시 로그를 슬그머니 출력하는 것이었다. 그럼 이전의 로그 내용을 지울 수도 있을까? 당연히 가능할 것이다. 이것은 여러분 숙제!!!

3. 프로퍼티 파일을 사용하자

이번에 살펴 볼 예제는 처음에 보았던 Hello 예제를 살짝 변경한 것이다. Log4j의 설정 부분에서 BasicConfigurator 를 사용하지 않고 PropertyConfigurator 를 사용한 것이다. 프로퍼티 파일에 원하는 설정 정보를 넣어두고 사용하면 소스 파일을 다시 컴파일하거나 배포하지 않고 프로퍼티 파일의 내용만 변경하여 사용 가능하므로 시스템 관리자들은 아주 편할 것이다. 게다가 log4j에서는 변경된 프로퍼티에 대해서 자동으로 다시 설정을 하고 동작하게 하는 기능이 있으므로 프로그램을 종료하고 다시 시작하는 일이 필요 없이 실행 도중에도 원하는 설정이 가능한 것이다. 이제 프로그램과 프로퍼티 파일의 내용을 둘러보자.

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class LogProperty {
  
  static Logger logger = Logger.getLogger(LogProperty.class);
  
  public static void main(String[] args) {
    
    /*
    프로퍼티 파일로부터 설정을 하며,
    1초마다 변경 사항이 있는지 감시한다
    */
    PropertyConfigurator.configureAndWatch(args[0], 1000);
    
    while (true) {
      logger.debug(“Hello log4j.”);
      try {
        Thread.sleep(1000);
      }
      catch (Exception e) {
      }
    }
    
  }

}

프로퍼티 파일의 이름은 “log.properties” 이라고 하자. 그리고 파일의 내용은 다음과 같다.

#log4j.rootLogger=DEBUG, A1
log4j.rootLogger=OFF, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] – %m%n

자세한 내용은 API 문서를 참고하기 바라며 간단한 설명만 하고 넘어가자.
roorLogger 다음에 나오는 값은 레벨과 Appender의 이름이다. 레벨은 OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL 의 값이 가능하다. A1 appender 는 콘솔에 출력하는 것으로 정의되었고 출력 패턴은 이전 예제의 일부를 그대로 사용한 것이다. 이제 컴파일을 하고 실행을 해보도록 하자. 클래스 이름 뒤의 값은 프로퍼티 파일을 지정하는 것이다.
콘솔에서 아래처럼 입력하면 처음에는 아무 것도 콘솔에 출력되지 않는다.

java LogProperty log.properties

그러나 프로퍼티 파일에서 레벨을 아래처럼 OFF 에서 DEBUG로 살짝 바꿔치기 하면…

log4j.rootLogger=DEBUG, A1
#log4j.rootLogger=OFF, A1

콘솔 화면에 DEBUG 레벨의 메시지가 출력되기 시작한다.

이외의 다른 설정 값들도 가능하며 여러분이 직접 API 문서를 참고해서 다양하게 사용할 수 있다.

4. 마치면서

몇 가지 예제를 살펴 보았지만 이것으로 log4j 의 모든 것을 보여주지는 못한다고 생각하지만, 이런 간단한 예제들도 적절히 조합해서 응용하면 상당히 멋진 로그 기능을 제공한다.
여러분도 log4j 를 사용하면서 좋은 정보가 있으면 다른 개발자들과 공유하고 널리 알려주기 바란다. 게다가 log4j는 다른 많은 언어로도 포팅(porting) 되어 있으므로 다른 프로그래밍 언어를 사용하는 개발자와도 일관성 있는 인터페이스를 통해 공감대를 형성할 수 있을 것이다.

 

[Log4j] 여러파일에 로그를 남기는 방법에 대하여

Filed under: JAVA — rothmans @ 3:24 pm

Log4j에서 여러파일에 로그를 남기는 방법에 대해서 잠시 언급하겠습니다.

아래 부분에서는 존칭을 뺀 단어를 사용합니다.

  

!—————————————————————————–!  
! category(logger) 설정                                                                                    !  
!—————————————————————————–!  
log4j.debug=true
#log4j.disable=INFO
log4j.rootLogger=DEBUG, CONSOL, SYSTEM

  

  

!—————————————————————————–!  
! appender(log destinations/targets) 와 옵션들을 설정                                         !  
!—————————————————————————–!  

! FILE에 로그 쓰기, 지정한 크기를 넘어서면 파일을 교체  
log4j.appender.CONSOL=org.apache.log4j.ConsoleAppender

! 로그메세지들이 전혀 버퍼되지 않는 것을 의미하며 대부분의 상황에 적당하다.  
log4j.appender.CONSOL.ImmediateFlush=true  

! 이 appender 는 여기 명시된 priority 와 같거나 높은 메세지만 로깅한다
log4j.appender.CONSOL.Threshold=DEBUG

! Layout 형식 : TTCCLayout, HTMLLayout,  XMLLayout, PatternLayout, SimpleLayout  
! PatternLayout, SimpleLayout – 자바의 Throwable 에러들과 예외를 무시한다
log4j.appender.CONSOL.layout=org.apache.log4j.PatternLayout

! %l – 소스코드의 위치정보를 출력한다. %C. %M(%F:%L) 의 축약형이다
log4j.appender.CONSOL.layout.ConversionPattern=%-5p %l %x =>%m%n

  

  

  

log4j.appender.SYSTEM=org.apache.log4j.DailyRollingFileAppender
log4j.appender.SYSTEM.File=C:/logs/system.html

! 매일 자정에 로그파일을 교체하며 기존파일은 xx.log_2004.07.12
log4j.appender.SYSTEM.DatePattern=’.'yyyy-MM-dd
log4j.appender.SYSTEM.Threshold=DEBUG

! 자바의 Throwable 에러들과 예외를 포함하기 위해 HTMLLayout을 사용한다.
log4j.appender.SYSTEM.layout=org.apache.log4j.HTMLLayout

! [%d{yyyy-MM-dd}형식은 프로그램의 실행속도를 느리게 함으로 SimpleDateFormat 형식지정한다.
log4j.appender.SYSTEM.layout.DateFormat=ISO8601
! [YYYY-MM-DD HH:MM:SS, mm] 형식을 뜻한다.
log4j.appender.SYSTEM.layout.TimeZoneID=GMT-8:00  

! %l – 소스코드의 위치정보를 출력한다. %C. %M(%F:%L) 의 축약형이다
log4j.appender.SYSTEM.layout.ConversionPattern=[%d] %-5p %l – %m%n

  

  

  

! 각 업무 상위패키지를 지정하여 logging 셋팅한다.

log4j.logger.rkhwang.biz.protoss=DEBUG, PROTOSS
log4j.logger.rkhwang.biz.teran=DEBUG, TERAN
log4j.logger.rkhwang.biz.zerg=DEBUG, ZERG

  

  

log4j.appender.PROTOSS=org.apache.log4j.DailyRollingFileAppender
log4j.appender.PROTOSS.File=C:/logs/protoss.log
log4j.appender.PROTOSS.DatePattern=’.'yyyy-MM-dd
log4j.appender.PROTOSS.Threshold=DEBUG
log4j.appender.PROTOSS.layout=org.apache.log4j.PatternLayout
log4j.appender.PROTOSS.layout.DateFormat=ISO8601  
log4j.appender.PROTOSS.layout.TimeZoneID=GMT-8:00  
log4j.appender.PROTOSS.layout.ConversionPattern=[%d] %-5p %l – %m%n

  

log4j.appender.TERAN=org.apache.log4j.DailyRollingFileAppender
log4j.appender.TERAN.File=C:/logs/teran.log
log4j.appender.TERAN.DatePattern=’.'yyyy-MM-dd
log4j.appender.TERAN.Threshold=DEBUG
log4j.appender.TERAN.layout=org.apache.log4j.PatternLayout
log4j.appender.TERAN.layout.DateFormat=ISO8601  
log4j.appender.TERAN.layout.TimeZoneID=GMT-8:00  
log4j.appender.TERAN.layout.ConversionPattern=[%d] %-5p %l – %m%n

  

log4j.appender.ZERG=org.apache.log4j.DailyRollingFileAppender
log4j.appender.ZERG.File=C:/logs/zerg.log
log4j.appender.ZERG.DatePattern=’.'yyyy-MM-dd
log4j.appender.ZERG.Threshold=DEBUG
log4j.appender.ZERG.layout=org.apache.log4j.PatternLayout
log4j.appender.ZERG.layout.DateFormat=ISO8601  
log4j.appender.ZERG.layout.TimeZoneID=GMT-8:00  
log4j.appender.ZERG.layout.ConversionPattern=[%d] %-5p %l – %m%n

  

C:/logs/ 에 해당 패키지(업무)별로 로그를 따로 관리가 용이해 진다.

log4j를 사용할 경우 SYSTEM Log가 많이 발생하여 디버그, 에러 메세지를 보기가 힘듭니다.

위와 같이 할 경우, 해당 로그에는 system에서 발생하는 log는 존재하지 않습니다.

시스템에서발생하는 로그는 systeml.html파일을 보시면 됩니다.

Older Posts »

Blog at WordPress.com.