'01.JAVA/Java'에 해당되는 글 199건

  1. 2009.02.18 개발자가 놓치기 쉬운 자바의 기본원리
  2. 2009.01.30 정적 쿼리와 동적 쿼리프로그래밍
  3. 2009.01.29 팩토리얼 값 얻기 예제: factorial Method Example
  4. 2009.01.28 JDBC에 사용예제... 2
  5. 2009.01.28 Java로 OpenID Consumer 서비스 구현하기 #4 : OpenID4Java로 추가정보 요청하기
  6. 2009.01.28 cos.jar로 파일 업로드 하기
  7. 2009.01.28 JDBC와 ODBC로 Java에서 MS Access(.mdb) 연결하기
  8. 2009.01.28 StringBuffer 객체 초기화하기
  9. 2009.01.28 PreparedStatement 객체 재사용하기
  10. 2009.01.28 JDBC Driver 리스트
  11. 2009.01.19 동적 서블릿 이미지 메이킹을 마음대로!
  12. 2009.01.19 썸네일 이미지 만들기
  13. 2009.01.14 [java]Calendar 클래스 이용 날짜계산...
  14. 2008.12.22 google Chart Types
  15. 2008.12.22 -Chaos Graph API Ver 1.2
  16. 2008.12.15 RMI_원격서비스 만드는법
  17. 2008.12.15 Java - RMI통신 기본개념 이해
  18. 2008.12.15 [RMI] 간단한 계산식
  19. 2008.12.04 apache의 common-email 라이브러리를 사용하여 메일을 보내봅시다.
  20. 2008.12.01 JVM GC와 메모리 튜닝
  21. 2008.11.28 Extract All Classes Loaded in the JVM into a Single JAR
  22. 2008.11.27 JAVA 환경변수 설정, TOMCAT 환경변수 설정, Servlet 환경변수 설정
  23. 2008.11.19 [본문스크랩] Java 보안과 암호화
  24. 2008.11.19 PDF 파일 만들기 - fop 0.93
  25. 2008.11.19 엑셀을 자바로 읽을(쓰는것 포함) jxl 방법
  26. 2008.11.19 Orinoco - A Java API to generate PDF documents
  27. 2008.11.19 Java Excel API - A Java API to read, write and modify Excel spreadsheets
  28. 2008.11.19 java(자바)로 excel(엑셀) 파일 활용 (jxl api) 정리
  29. 2008.11.19 자바 JXL API 를 이용한 Excel 파일 만들기
  30. 2008.11.19 java의 excel 컨트롤 - jxl, poi
01.JAVA/Java2009. 2. 18. 12:32
반응형

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

  • 전성호(커뮤니티본부 커뮤니티개발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 Iterator
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);
}

//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 List
List[] 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
01.JAVA/Java2009. 1. 30. 10:50
반응형

정적 쿼리와 동적 쿼리는 저도 이번에 공부하면서 처음 알게된 것인데 (;;)
사실 닷넷 프레임워크 공부할 때 계속 동적 쿼리를 사용한 것 같군요.
하지만 이게 동적 쿼리인지는 몰랐답니다. 컥 ;;

정적 쿼리 방식 : 변수가 바뀔 때마다 새로운 SQL 을 생성 하여 DB쿼리 수행
동적 쿼리 방식 : 변수가 바뀔 때마다 파싱을 하지 않고 변수 바인딩 과정을 통해 DB 쿼리 수행

insert into person values (null, '홍길동', '888888-1111111', '2002-02-02');
이것이 정적 쿼리입니다.

insert into person values (null, ?, ?, ?);
쿼리문을 이런 식으로 해놓고 ? 부분을 변수 바인딩을 통해 입력받는 것을 동적 쿼리라고 합니다.

정적 쿼리와 동적 쿼리는 각각 장단점이 있습니다.

동적 쿼리문을 사용한 간단한 예제를 보겠습니다.

  1. import java.sql.*;  
  2. import java.util.*;  
  3.  
  4. public class Exam_04  
  5. {  
  6.     public static void main(String[] ar)  
  7.     {  
  8.         try 
  9.         {  
  10.             Class.forName("com.mysql.jdbc.Driver");  
  11.         }  
  12.         catch( ClassNotFoundException e ) {}  
  13.           
  14.         Connection conn = null;  
  15.         Statement stmt = null;  
  16.         PreparedStatement pstmt = null;  
  17.         ResultSet rs = null;      
  18.           
  19.         String name = "fff";  
  20.         String jumin = "555555-5555555";  
  21.         String birth = "2003-06-15";  
  22.           
  23.         String query = "insert into person values (null, ?, ?, now())";  
  24.           
  25.         try 
  26.         {  
  27.             conn = DriverManager.getConnection("jdbc:mysql://localhost/java", "root", "abc123");  
  28.             //stmt = conn.createStatement();              
  29.             //stmt.executeUpdate(query);  
  30.             //stmt.close();  
  31.             pstmt = conn.prepareStatement(query);  
  32.             pstmt.setString(1, name);  
  33.             pstmt.setString(2, jumin);  
  34.             pstmt.executeUpdate();  
  35.             conn.close();  
  36.             System.out.println("업데이트 성공");  
  37.         }  
  38.         catch( SQLException e )  
  39.         {  
  40.             System.err.println("error = " + e);  
  41.         }  
  42.     }  
  43. }  
사용자 삽입 이미지
Posted by 1010
01.JAVA/Java2009. 1. 29. 15:56
반응형
자바로 0에서 100까지 숫자의 팩토리얼 값을 구하는 소스입니다.

작은 숫자라도 팩토리얼 값을 구하면 어마어마한 숫자가 나오기 때문에, 자바의 long 형 즉 64비트 정수로는 팩토리얼 값을 제대로 담을 수가 없습니다.

그래서 사실상 무한대의 정수를 저장할 수 있는 BigInteger 를 사용합니다.


팩토리얼 값 얻기 예제: factorial Method Example


소스 파일명: Foo.java
import java.math.BigInteger;

public class Foo {
  public static void main(String[] args) {

    for (int i = 0; i <= 100; i++)
      System.out.format("%3d! = %d%n", i, factorial(i));

  }




  public static BigInteger factorial(int n) {
    BigInteger fac = BigInteger.ONE;

    for (int i = 1; i <= n; i++)
      fac = fac.multiply(BigInteger.valueOf(i));

    return fac;
  }


}


factorial()이라는 메소드(함수)에서 팩토리얼을 계산하여 반환합니다.


BigInteger.ONE 은 1이라는 뜻입니다.

multiply 는 곱하기라는 뜻이고,

BigInteger.valueOf(i) 이것은, int(32비트 정수)인 i의 값을 BigInteger 로 변환하는 것입니다.



컴파일 및 실행 결과 화면:
(숫자가 너무 커서 일부만 실었습니다.)
D:\Z>javac Foo.java && java Foo
  0! = 1
  1! = 1
  2! = 2
  3! = 6
  4! = 24
  5! = 120
  6! = 720
  7! = 5040
  8! = 40320
  9! = 362880
 10! = 3628800
 11! = 39916800
 12! = 479001600
 13! = 6227020800
 14! = 87178291200
 15! = 1307674368000
 16! = 20922789888000
 17! = 355687428096000
 18! = 6402373705728000
 19! = 121645100408832000
 20! = 2432902008176640000
...
이하 별첨


위와 같이 나옵니다. (0! 은 1 로 간주됨)

출력 결과에는 100까지 나오지만 숫자가 너무 크기에 모두 표현하기 힘듭니다. 다음 게시물에 100까지의 모든 결과가 있습니다: ▶▶ 수학] 팩토리얼 계산 표; Factorial Table 1~100 (0에서 100)까지 계승(階乘)



▶▶ 팩토리얼 구하기(Factorial) 프로그램 소스, 팩토리얼(계승)이란? [수학



BigInteger 로 거대 정수 사칙연산하는 법: ▶▶ Java/자바] 거대 무한 정수 사칙연산, BigInteger 클래스 사용 법; Big Int Plus Minus

자바 강좌와 각종 예제
Posted by 1010
01.JAVA/Java2009. 1. 28. 16:38
반응형
JDBC에 사용예제...

이클립스에서 작성하였기 때문에 package 사용하였슴.
각자환경에서 사용하려면 com.ksic.util에서 StudentsUtil의 DB부분을 설정에 맞게 수정해 주어야 함.

오라클 9i와 연동하여 작성한 것임.

DB테이블 명은 hkstudents이고 컬럼은 모두 varchar2타입으로 id, name, addr 3개가 있음.

테이블에 대한 DTO가 따로 하나 있고... DAO는 연결과 클로즈부분만 추상클래스로 따로 구성됨.

나름 주석 열심히 달아서 보면 이해가 될지도..



JDBC에 대한 설명

자바에서 오라클을 연결해서 사용할때 총 6단계의 과정을 거친다.

1. 드라이버 로딩
Class.forName("oracle.jdbc.driver.OracleDriver");

Class.forName으로 JDBC 드라이버를 로딩해 준다. 이 부분은 ClassNotFoundException 핸들링을 해주어야 하므로 try-catch문으로 감싸주어야 한다.

2. DB 연결
Connection conn = null;
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "scott", "tiger");


Connection을 사용하기 위해서는 java.sql.Connection 을 임포트 해주어야 한다. conn이라는 커넥션을 만든 후에 DriverManager의 getConnection을 통해서 DB를 연결해 준다. DriverManager를 사용하려면 java.sql.DriverManager를 import해주어야 한다.

첫 매계변수는 DB연결쪽이다. jdbc:oracle:thin:@부분은 동일하게 사용하고 localhost는 위치를 말한다. 다른PC이거나 할 경우는 IP를 입력해 주고 1521은 해당포트이며 ORA92는 오라클의 SID이다. 자신에 맞게 입력해준다.
두번째와 세번째는 오라클의 아이디와 패스워드이다.

이 연결부는 SQLException예외처리로 Try-Catch로 감싸 주어야 한다.

3. 쿼리 준비
PreparedStatement psmt = null;
psmt = conn.prepareStatement("DB쿼리문");
혹은
Statement stmt = null;
stmt = conn.createStatement("DB쿼리문");


를 사용한다. 각각 java.sql.PreparedStatement이나 java.sql.Statement을 임포트 해주어야 한다. Statement가 PreparedStatement보다 상위클래스이다.

실행하기 전에 DB쿼리를 준비하는 단계인데 둘의 차이는 PreparedStatement는 쿼리문에 ?를 사용해서 추가로 ?에 변수를 할당해 줄수 있다는 것이다.
psmt = conn.prepareStatement("select * from TableName where id=?");
psmt.setString(1, id);
와 같이 사용이 가능하다. setString(혹은 setInt 등등)을 이용해서 1번째 ?위치에 id를 할당할 수 있어서 유연성있게 쿼리준비를 할 수 있다.

4. 쿼리 실행
psmt.executeUpdate();
로 쿼리를 실행한다. insert, update, delete 등 값을 받아 오지 않는 쿼리문은 executeUpdate를 이용해서 실행해 준다.

5. 데이터 가져오기
select등 쿼리실행후에 값을 가져와야 하는 쿼리문의 경우는
ResultSet rs = null;
rs = psmt.executeQuery();


java.sql.ResultSet을 이용해서 쿼리실행 한 것을 ResultSet으로 받아들인다. 이때 쿼리실행은 executeQuery()를 사용한다.

그 후에

while(rs.next()) {
    int i=1;
    String id = rs.getString(i++);
    String name = rs.getString(i++);
    String addr = rs.getString(i++);
   }


rs.next()를 이용해서 한 줄씩 읽어들인다. 처음에는 시작위치에 있고 rs.next()를 실행하면 받아온 데이터의 첫줄 위치로 이동하게 된다. while문 혹은 if문을 통해서 rs.next()가 null인지를 확인하면서 반복문을 돌린다.
rs.get을 이용해서 값을 가져와 변수 혹은 dto객체등에 저장해서 사용한다. DB에 여러줄을 가져올때는 dto객체로 받아서 컬렉션 계열로 저정해야 한다.

6. 커넥션 닫기
if (rs!=null) {
   try {
      rs.close();
   } catch (SQLException e) {
   }
}
 
if(stmt!=null) {
   try {
      stmt.close();
   } catch (SQLException e) {
   }
}
 
if(conn!=null) {
   try {
      conn.close();
   } catch (SQLException e) {
   }
}

닫을때는 Open한 반대의 순서로 닫아주어야 한다. Statement가 PreparedStatement 보다 상위클래스이기 때문에 닫는 메서드를 만들때 Statement로 받을어도 PreparedStatement을 정상적으로 받을 수 있다. 닫을때는 습관적으로 null이 아닌지를 확인한 뒤에 받도록 만들면 생각지 못한 오류를 막을 수 있다.


이것이 JDBC의 기본적인 구조인데 실제적으로 사용할때는 제공되는 커넥션풀(ConnectionPool)등을 사용해서 이미 만들어진 커넥션을 사용하기 때문에 1번 과정은 pass하고 2번과정부터 커넥션풀에서 커넥션을 얻어오는 방식으로 사용하게 되는 경우가 많다.
Posted by 1010
01.JAVA/Java2009. 1. 28. 16:24
반응형
Java로 OpenID Consumer 서비스 구현하기 #1 : OpenID란?
Java로 OpenID Consumer 서비스 구현하기 #2 : OpenID4Java로 인증요청하기
Java로 OpenID Consumer 서비스 구현하기 #3 : OpenID4Java로 인증확인하기



이제 간단하게 인증을 요청하고 인증확인을 구현하였는데 실제 서비스를 하려면 추가정보를 받아야 한다. 로그인처리를 하려면 추가정보가 필요하다. 오른아이디를 통해서 회원가입 처리를 하려고 해도 그렇고 아이디야 당연히 넘어오지만 이름, 닉네임, 전화번호등등의 정보를 받아와야 한다.

openid4java에 들어있는 SampleConsumer를 보면 아래와 같은 코드들이 있다.(앞에서 만든 SampleConsumer말고...)
FetchRequest fetch = FetchRequest.createFetchRequest();
fetch.addAttribute("email", "http://schema.openid.net/contact/email", true);
authReq.addExtension(fetch);
이 코드가 추가정보를 요청하는 코드인데 이 코드는 Attribute Exchange 스펙을 이용하는 코드이다. 스펙에 이름에서 알 수 있듯이 추가적인 정보를 교환하는 스펙이다. 나도 스펙들을 대충 보고 당연히 이걸 이용한다고 생각하고 열심히 삽질을 했는데 어찌된 일인지 정보를 받아올 수 있었다. 의외로 추가정보를 Attribute Exchange를 이용하는 것이 아니라 Simple Registration Extension 스펙(영문스펙 )을 이용한다. Simple Registration Extension을 이용해서 유저의 추가정보를 받아 올 수가 있다.(Attribute Exchange가 왜 안되는지는 나도 잘 모르겠다. 안된다기 보다는 내가 못하는 거겠지만... 이래서 스펙을 볼 줄 알아야 하는건데....)


어쨌든 Simple Registration Extension를 이용해서 정보를 요청하자.

  1. SRegRequest sreq = SRegRequest.createFetchRequest();  
  2. sreq.addAttribute("email", true);  
  3. sreq.addAttribute("nickname", true);  
  4. sreq.addAttribute("fullname", true);  
  5. sreq.addAttribute("dob", true);  
  6. sreq.addAttribute("gender", true);  
  7. authReq.addExtension(sreq);  

일단 인증을 요청할 때 추가정보도 같이 요청해야 하기 때문에 authRequest 메서드안에

AuthRequest authReq = manager.authenticate(discovered, returnToUrl, trustRoot);

아래쪽에 위의 코드를 추가해 준다. (SRegRequest를 AuthRequest객체에 추가해 주어야 하므로..) 그리고 SReqRequest를 사용하려면 org.openid4java.message.sreg.SRegRequest를 import 해주어야 한다. 이렇게만 하고 다시 실행을 하고 인증을 요청하면

사용자 삽입 이미지

위의 사진처럼 OP측에서 사용자의 추가정보를 넘겨주는 것을 사용자에게 보여준다. 당연히 End-User는 여기서 정보를 변경해 줄 수가 있다. End-User가 승인을 하면 OP는 인증정보와 함께 요청한 추가정보를 같이 담아서 RP측이 받을 수 있도록 해준다. 요청하는 정보는 Simple Registration Extension의 스펙을 확인하면 되고 당연히 OP측에거 가지고 있는 정보만 요청해서 받을수 있다.





추가정보를 요청하고 OP가 정보를 넘겨주었으니 그럼 이제 받아보자.

  1. if (verified != null) {                 
  2.     AuthSuccess authSuccess = (AuthSuccess) verification.getAuthResponse();  
  3.  
  4.     if (authSuccess.hasExtension(SRegMessage.OPENID_NS_SREG))  
  5.     {  
  6.         MessageExtension ext = authSuccess.getExtension(SRegMessage.OPENID_NS_SREG);  
  7.  
  8.         if (ext instanceof SRegResponse)  
  9.         {  
  10.             SRegResponse sregResp = (SRegResponse) ext;  
  11.  
  12.             String nickName = sregResp.getAttributeValue("nickname");  
  13.             String email = sregResp.getAttributeValue("email");  
  14.             String dob = sregResp.getAttributeValue("dob");  
  15.             String gender = sregResp.getAttributeValue("gender");  
  16.  
  17.             httpReq.setAttribute("nickName", nickName);  
  18.             httpReq.setAttribute("email", email);  
  19.             httpReq.setAttribute("dob", dob);  
  20.             httpReq.setAttribute("gender", gender);  
  21.         }  
  22.     }  
  23.  
  24.     return verified;  // success             
  25. }  

기존 코드의 verifyResponse 메서드 안에 있는 if (verified != null) {} 의 부분을 위의 코드로 바꾸어준다. 앞에서는Simple Registration Extension의 정보를 확인하는 부분이 없었기 때문에...(다시 한번 말하지만 소스의 세세한 부분은 이해하지 못하고 있다.) 간단히 설명하자면 SRegMessage가 있는지 확인하고 있으면 SRegResponse로 받아서 각 정보를 받아온다. 나는 이 메서드를 호출한 JSP파일에서 다시 정보를 받아야 하기 때문에 verifyResponse를 호출할 때 파라미터로 넘겨준 Request에 각 정보를 다시 심어주는 방식을 택했다.

의 코드를 사용하려면 org.openid4java.message.sreg.SRegMessage 와 org.openid4java.message.sreg.SRegResponse 를 import 해주어야 한다.



이제 JSP에서 받아오자. resultOpenid.jsp를 다음과 같이 변경하자.

  1. // resultOpenid.jsp  
  2. <%@ page contentType="text/html; charset=utf-8" %>  
  3. <% request.setCharacterEncoding("utf-8"); %>  
  4. <% response.setContentType("text/html; charset=utf-8"); %>  
  5. <%@ page import="org.openid4java.discovery.Identifier" %>  
  6. <%@ page import="kr.test.openid.SampleConsumer" %>  
  7. <%  
  8.     SampleConsumer sc = (SampleConsumer)application.getAttribute("cm");  
  9.     Identifier ver = sc.verifyResponse(request);  
  10.  
  11.     if (ver != null) {  
  12.         out.println(ver.getIdentifier() + "님 환영합니다.<br>");  
  13.         out.println("닉네임 : " + request.getAttribute("nickName") + "<br>");  
  14.         out.println("이메일 : " + request.getAttribute("email") + "<br>");  
  15.         out.println("생년월일 : " + request.getAttribute("dob") + "<br>");  
  16.         out.println("성별 : " + request.getAttribute("gender") + "<br>");  
  17.     } else {  
  18.         out.println("로그인에 실패하였습니다.");  
  19.     }  
  20. %>  

정상적으로 돌아간다면 위의 정보를 다 받아오고 화면에 출력해 준다.

사용자 삽입 이미지

근데 이상한건 fullname의 경우는 받아오지를 못한다. myid측에서는 인코딩의 문제점을 제기했는데 시간의 압박으로 그부분까지 확인해 보지는 못했다. 나같은 경우에는 fullname이 들어가면 인증자체가 실패해서 일단 fullname은 제외해 놓은 상태이다.(이름만 한글이 넘어왔으므로 인코딩 문제라는데 심증이 가기는 한다.) 참고로 End-User가 "승인"을 하면("이번만 승인"이 아닌..) 로그인 할 때 마다 요청한 정보가 넘어온다. 정보를 갱신하고자 한다면 로그인처리를 할 때 갱신에 대한 처리를 하면 End-User가 OP측에서 개인정보를 수정하면 새 정보를 받을 수 있을 것이다.

이것외에도 해야할 일은 많겠지만 일단 필요한 기능은 간단하게 다 구현했다. 난 여기까지만.... 소스가 많지 않아서 내용만 보아도 이해가 되겠지만 혹 더 도움이 될까봐 위의 결과로 나온 소스를 첨부로 올린다.



그리고 이부분은 해결은 안되었는데 다음과 같은 에러가 나는 경우가 있다.

사용자 삽입 이미지

URI is not absolute
라는데 URI가 절대경로가 아니라는건데 이건 엄청 찾아봤는데 죽어도 이유를 모르겠다. 절대경로인데 절대경로가 아니라니.. ㅡ..ㅡ 더군다나 java.lang에서 나는것처럼 보이니 이유를 모르겠다. 나같은 경우에는 Jeus환경에서 나타났고 웹에서 찾아보니 다른 분은 Resin환경에서 같은 문제가 발생했었다. 나는 자연스럽게 환경이 tomcat으로 바뀌면서 더이상 이문제를 가지고 고민할 필요가 없어지긴 했는데 일단 이문제는 tomcat에서는 발생하지 않는다.

그럼 이제 DB에 대한 부분이 남는다. DB구성을 어떻게 할 것인가. OpenID의 개념상에는 한명이 여러 OpenID를 사용할 수도 있고 그것들은 같은 유저로서 인식되어야 하는 것 같고 거기에 OpenID를 추가해고 삭제할 수도 있어야 하는데 사이트마다 적용하는 범위는 다를 수 있다. 어쨌든 A Recipe for OpenID-Enabling Your Site 라는 글에 DB에 대한 내용이 자세히 나와있다. 이곳에서는 기존유저가 있다는 가정하에 OpenID를 추가하는 것을 전제로 하고 있지만 내가 찾아본 자료중에 DB구성에 대해서 자세히 나와 있는 포스팅은 이곳말고는 보지 못했다.

그리고 OpenID Book 이라는 프로젝트가 있는데 OpenID에 대한 내용을 정리하는 것 같다. 해당 사이트에서 무료로 다운로드를 받을 수 있고 사이트에 가기 귀찮으신 분을 위해서 올려둔다. PDF파일로 되어 있으면 250page정도 되는데 아마 이걸 읽으면 거진다 이해할 수 있을듯 하다. 분량이 분량이니 만큼....(당연히 영문이고 나도 안읽었다. ㅎ)




이제 필요한 처리는 다 했다. 로그인처리도 했고 정보도 받아왔으니 이제 자신의 사이트에서 필요한 처리를 하면 된다. 로그인처리를 하려면 세션에 로그인정보를 심는다든가 회원가입을 받으려면 약관동의를 하고 디비에 넣는다든가 하는 각자 원하는 처리를 해주면 된다.

이제 정책적인 문제가 남는데 인증 확인후 회원가입등의 절차나 추가정보를 요청할 수 있다. 하지만 OpenID의 취지는 자신을 증명하는 것이기 때문에 사이트가 필요해서 요청하는 절차에 대해서 강제사항이 되지 않아야 한다. 즉 인증을 받은 뒤에 회원가입 요청을 한다고 하였을 때 회원가입을 하지 않는다 하더라고 로그인처리를 해주어 OpenID사용자가 사이트 사용에 무리가 없도록 해야한다. 다 구현하고 나서도 이때문에 고민을 많이 했는데 로그인과 회원가입을 같은 개념으로 두지 않아야 한다. OpenID는 회원가입을 하지 않아도 자신이 누군지를 증명해서 사이트를 이용할 수 있게 되는 것이다.(이부분은 비디오가게를 비회원으로 이용할 수도 있는 것과 같다.)

다음 문제는 로그아웃의 문제이다. 로그아웃은 어떻게 해야되는가... 여기서의 고민은 로그인이 RP와 OP에 둘다 된다는 것에 문제가 있다. RP측은 정보를 받아왔으니 RP에서 일반 회원이 로그인처리를 하는 것과 동일하게 처리하면 된다. 보통이면 세션에 정보를 심어서 처리할텐데 로그아웃할때는 세션을 날려버리면서 로그아웃을 처리한다. 하지만 테스트해보면 RP에서 로그아웃을 해도 OP쪽에는 로그인상태로 남아있는데 그런 RP가 로그아웃정보를 OP로 날려야 하는가 그럼 어떻게 날려야 하는가의 고민이 남았는데 다음 글을 참고하면 답을 얻을 수 있다.

http://cs.myid.net/issues/8737
http://cs.myid.net/issues/8747
http://cs.myid.net/issues/8748

결론부터 얘기하자면 RP측에서만 로그아웃을 처리해주면 되고 이에 큰 문제는 없어보인다. 위의 정책적인 부분들은(사실은 구현하면서 어렵던 부분들도 ㅎㅎ) myid측에서 많은 도움을 받았다. 달리 OpenID에 대해서 도움을 얻을 곳이 없고 그래도 myid에 계신분이 이해도가 제일 높다고 생각했기 때문에 여러차례 문의를 했는데 항상 빠르고 친절하게 답변을 주신데에 이자리를 빌어 감사드립니다. ^^


다음에 다시 OpenID를 구현할 때는 좀더 수준 높게 해볼 수 있게 되기를 바라면서.. ^^
Posted by 1010
01.JAVA/Java2009. 1. 28. 16:21
반응형
파일업로드를 하려면 기본적인 POST방식으로는 안되고 파일업로드를 처리할 수 있는 무언가(?) 있어야 한다.

일단 jsp에서 파일 업로드를 선택하는 form에서 entype이 multipart/form-data로 보내야 한다. 그렇지 않으면  받는쪽에서 파일을 받을 수가 없다. 파일이 없으면 보통의 form은 entype을 바꾸어 주지 않아도 된다.

  1. <form name="writeForm" method="post" action="writeProc.jsp" enctype="multipart/form-data" > 
  2.      <input type="file" name="attachFile" size="40" /> 
  3. </form> 

이제 받아야 하는데 이걸 받는 역할을 cos.jar 가 한다. 폼전송을 multipart/form-data로 보냈기 때문에 기존에 폼을 받던 request.getParameter()로는 받을 수가 없다. 그래서 cos.jar가 파일도 받고 폼의 다른 값들도 다 받아주는 역할을 한다.

cos는 com.oreilly.servlet의 약자이다. 보면 알겠지만 보통 java에서 package를 정의할 때 쓰는 방식이고 이 팩키지를 jar로 묶어서 cos.jar라고 배포를 하는 것이다. cos.jar의 페이지에서 cos-05Nov2002.zip 를 다운로드 가능하다.(파일명에서 보아 알겠지만 2002년 11월이 가장 최신판이다  ㅡ..ㅡ) 다운받은 파일안에 lib에 있는 cos.jar를 WAS쪽에 넣어도 되고 해당 프로젝트의 WEB-INF안에 lib안에 넣어도 된다. cos.jar를 사용한다고 했지만 정확히는 cos.jar의 MultipartRequest를 이용해서 파일을 받는다.

  1. <%@ page import="com.oreilly.servlet.MultipartRequest" %>   
  2. <%  
  3.      int maxPostSize = 10 * 1024 * 1024; // 10MB  
  4.      saveDirectory = config.getServletContext().getRealPath("/upload");  
  5.      MultipartRequest multi = new MultipartRequest(request, saveDirectory, maxPostSize, "utf-8");  
  6.  
  7.      Enumeration formNames=multi.getFileNames();  // 폼의 이름 반환  
  8.  
  9.      String fileInput = "";  
  10.      String fileName = "";  
  11.      String type = "";  
  12.      File fileObj = null;  
  13.      String originFileName = "";      
  14.      String fileExtend = "";  
  15.      String fileSize = "";  
  16.  
  17.      while(formNames.hasMoreElements()) {  
  18.           fileInput = (String)formNames.nextElement();                // 파일인풋 이름  
  19.           fileName = multi.getFilesystemName(fileInput);            // 파일명  
  20.           if (fileName != null) {  
  21.                type = multi.getContentType(fileInput);                   //콘텐트타입      
  22.                fileObj = multi.getFile(fileInput);                             //파일객체  
  23.                originFileName = multi.getOriginalFileName(fileInput);           //초기 파일명  
  24.                fileExtend = fileName.substring(fileName.lastIndexOf(".")+1); //파일 확장자  
  25.                fileSize = String.valueOf(fileObj.length());                    // 파일크기  
  26.           }  
  27.      }  
  28. %>  

위의 소스가 MultipartRequest를 이용한 파일 받기 예제이다. 소스가 그렇게 어렵지 않기 때문에 크게 어려운 부분은 없다고 생각한다. MultipartRequest에 파라미터를 넘기기 위해서 최대용량과 저장폴더의 실제경로를 미리 만들어 주고 MultipartRequest의 객체 생성을 할 때 request와 함께 넘겨준다. MultipartRequest는 아주 다양하게 오버라이딩되어 있기 때문에 전달해야하는 파라미터에 대해서는 원하는 대로 사용할 수 있다. MultipartRequest의 정의는 cos.jar쪽 페이지 에 자세하기 나와 있다.

파일이 몇개가 넘어오든 간에 MultipartRequest에서는5번라인의 객체생성 부분에서 모두 파일이 업로드 되어 저장되고 그 뒤에 저장한 파일들의 정보를 가져오는 형태로 되어 있다. 이 말은 정보를 얻기 전에 파일 저장이 먼저 되기 때문에 파일의 어떤 정보를 검사해서 저장할지 말지를 결정하는 것이 안된다는 얘기고 저장한 다음에 검사를 해서 삭제해 주는 형태가 되어야 할 것이다.

7번 라인에서는 Enumeration으로 multi객체로 부터 전달받은 폼의 이름을 구해온다. 9~14번에서 while루프 안에서 사용한 변수들을 초기화 하고 16번라인부터 7번에서 구해온 폼이름을 통해서 파일객체를 가져오면서 다 가져올때까지 루프를 돈다. while형태로 되어 있기 때문에 단일 파일이든 여러개의 파일이 올라오든 모두 처리가 가능하다. while문 안에서는 각 파일의 정보를 구해온다. 이렇게 구해온 정보는 보통은 Database에 넣을 목적으로 구해 올 것이며 필요한 것만 가져와서 사용하면 되겠다.

여기서 input=file외에 다른 input에 대한 값들을 가져오려면 request.getParameter()대신에 multi.getParameter()를 사용해주면 된다. (여기서 multi는 위에 예제소스에서 만든 MultipartRequest의 객체이다.) 사용법은 동일하다.





각자 다르겠지만 일반적으로 서버에 파일을 저장할 때는 사용자가 올린 파일명을 그대로 올리지 않는다. 이는 여러가지 이유가 있는데 jsp같은 경우는 올리지 못하게 하는게 일반적이고 올리더라도 실행되지 않도록 확장자를 날리는 등의 조치를 취함으로써 보안처리를 해준다. 또한 같은 파일명이 있을 경우에 이전파일을 덮어쓰면 안되기 때문에 같은 파일명이 있을 경우에 대한 처리를 해야하고 우리같은 경우는 한글로 된 파일명은 서버에 따라서 문제가 생길수도 읽고 띄어쓰기나 특수기호등 파일명에 대한 안정성을 보장할 수 없기 때문에 나같은 경우는 대게 영문이나 숫자의 조합으로 특정파일명을 만들어서 저장한 뒤에 디비에는 서버쪽 파일명과 초기파일명을 둘다 넣어좋고 서버쪽 파일명을 이용해서 찾고 다운할때나 보여줄때는 초기파일명을 가지고 처리한다.


이렇게하려면 업로드할때 파일명을 바꾸어 주어야 한다. MultipartRequest객체를 생성할 때 파일명을 수정하도록 파라미터를 하나 더 주면 된다.

MultipartRequest multi = new MultipartRequest(request, saveDirectory, maxPostSize, "utf-8", new DefaultFileRenamePolicy());

4번째 파라미터로 DefaultFileRenamePolicy를 넘겨준다. DefaultFileRenamePolicy는 cos.jar는 안에 존재하는 클래스이다. 파일명을 어떻게 바꾼다라는 규칙이 정해져 있는 클래스를 파라미터로 넘겨주고 파일을 업로드 할때 그 규칙에 따라 파일명이 바뀌어서 올라간다. 여기서 DefaultFileRenamePolicy는 같은 파일명이 있는지를 검사하고 있을 경우에는 파일명뒤에 숫자를 붙혀준다. ex: aaa.zip, aaa1.zip, aaa2.zip 이런 식으로...

이 규칙에 따르면 중복에 대한처리는 할 수 있지만 그 외의 경우에는 대응할 수가 없다. 그래서 DefaultFileRenamePolicy의 규칙을 필요하다면 수정해 주어야 한다. cos.jar안에 src파일도 있으니 직접 수정하고 다시 jar로 만들어도 되고 cos.jar는 그대로 두고 따로 클래스를 만들어서 MultipartRequest객체를 생성할 때 내가 만든 클랙스를 넘겨주어도 된다.

MultipartRequest multi = new MultipartRequest(request, saveDirectory, maxPostSize, "utf-8", new MyFileRenamePolicy());

MyFileRenamePolicy 클래스는 FileRenamePolicy로 부터 상속도 받아야 하고 혹시모를 다른 충돌도 막기 위해서 DefaultFileRenamePolicy와 동일하게 com.oreilly.servlet.multipart라는 팩키지를 만들어서 넣는다.

DefaultFileRenamePolicy.java
  1. package com.oreilly.servlet.multipart;  
  2. import java.io.*;  
  3.  
  4. public class DefaultFileRenamePolicy implements FileRenamePolicy {  
  5.      public File rename(File f) {  
  6.           if (createNewFile(f)) {  
  7.                return f;  
  8.           }  
  9.           String name = f.getName();  
  10.           String body = null;  
  11.           String ext = null;  
  12.  
  13.           int dot = name.lastIndexOf(".");  
  14.           if (dot != -1) {  
  15.                body = name.substring(0, dot);  
  16.                ext = name.substring(dot);  // includes "."  
  17.           }  
  18.           else {  
  19.                body = name;  
  20.                ext = "";  
  21.           }  
  22.  
  23.           int count = 0;  
  24.           while (!createNewFile(f) && count < 9999) {  
  25.                count++;  
  26.                String newName = body + count + ext;  
  27.                f = new File(f.getParent(), newName);  
  28.           }  
  29.           return f;  
  30.      }  
  31.  
  32.      private boolean createNewFile(File f) {  
  33.           try {  
  34.                 return f.createNewFile();  
  35.           }  
  36.           catch (IOException ignored) {  
  37.                return false;  
  38.           }  
  39.      }  
  40. }  

이게 기존에 있는 DefaultFileRenamePolicy.java의 소스이다. 소스는 처음에는 좀 어색하지만 그리 어렵지 않다. rename()이 처음 호출되는데 createNewFile()를 실행해서(5라인) 파일생성이 가능하면 그대로 끝을 내고 생성을 못했으면 파일명의 점(.)을 기준으로 파일명과 확장자를 나누어서 저장한다.(13라인) 그리고 파일생성이 가능할 때까지(중복파일이 없을때까지) 루프를 돌아서 파일명+숫자+확장자형태로 다시 만들어서 리턴한다.(23라인)



MyFileRenamePolicy.java
  1. package com.oreilly.servlet.multipart;  
  2. import java.io.*;  
  3. import java.util.Date;  
  4. import java.text.SimpleDateFormat;  
  5.  
  6. public class AlmapFileRenamePolicy  implements FileRenamePolicy {  
  7.      public File rename(File f) {  
  8.           long currentTime = System.currentTimeMillis();  
  9.           SimpleDateFormat simDf = new SimpleDateFormat("yyyyMMddHHmmss");  
  10.           int randomNumber = (int)(Math.random()*100000);  
  11.       
  12.           String uniqueFileName = "" + randomNumber + simDf.format(new Date(currentTime));  
  13.  
  14.           String name = f.getName();  
  15.           String body = null;  
  16.           String ext = null;  
  17.  
  18.           int dot = name.lastIndexOf(".");  
  19.           if (dot != -1) {  
  20.                body = name.substring(0, dot);  
  21.                ext = name.substring(dot);  // includes "."  
  22.           }  
  23.           else {  
  24.                body = name;  
  25.                ext = "";  
  26.           }  
  27.       
  28.           String tempName = uniqueFileName + ext;  
  29.           f = new File(f.getParent(), tempName);  
  30.           if (createNewFile(f)) {  
  31.                return f;  
  32.           }  
  33.  
  34.           int count = 0;  
  35.           while (!createNewFile(f) && count < 9999) {  
  36.                count++;  
  37.                String newName = uniqueFileName + "_" + count + ext;  
  38.                f = new File(f.getParent(), newName);  
  39.           }  
  40.  
  41.           return f;  
  42.      }  
  43.  
  44.      private boolean createNewFile(File f) {  
  45.           try {  
  46.                return f.createNewFile();  
  47.           }  
  48.           catch (IOException ignored) {  
  49.                return false;  
  50.           }  
  51.      }  
  52. }  

새로 만든 파일 명명규칙인 MyFileRenamePolicy이다. 기존에 있던 것에서 파일명명하는 방식만 약간 바꾼 것이다. 앞에것보다는 약간 길어졌지만 간단하다. 파일중복을 줄이기 위해서 5자리짜리 랜덤수와 현재날짜시간의 조합으로 유니크한파일명을 만든다(12라인) 앞에것과 동일하게 점(.)을 기준으로 파일명과 확장자를 분리하고(18라인) 분리한파일명대신 유니크한 파일명과 확장자를 조합해서 임시로 파일명을 만든 다음에 파일생성을 시도한다.(28라인) 성공하면 그대로 끝내고 실패하면 루프를 돌면서 파일생성가능할때까지 카운트를 올려서 유니크한 파일명_카운트.확장자형태가 되도록 만든다.


이렇게 모든 파일은 랜덤수+현재시간.확장자의 형태로 만든다.여기서는 파일명으로 수자만 사용했지만 원하는 규칙대로 만들어서 클래스를 구성해 주면 그에 맞게 리네임을 할 수 있다.
Posted by 1010
01.JAVA/Java2009. 1. 28. 16:15
반응형
MDB를 별로 좋아하지는 않지만 상황에 따라서 솔직히 편할때가 있는 것도 사실이고 뭐 MDB로 해야된다는데 어쩌랴... Java에서 MDB를 연결해야 하는데 알아보니 여러가지가 방법이 있었는데 여기서는 윈도우즈에서 제공하는 ODBC를 이용해서 연결하는 방법을 사용한다.(윈도우즈서버에서만 가능하다는 얘기다.)

[제어판] - [관리도구] - [데이터 원본 (ODBC)]에 들어간다.

사용자 삽입 이미지

"시스템 DSN"탬에서 새로운 ODBC를 연결하기 위해서 추가버튼을 누른다.

사용자 삽입 이미지

여기서는 MDB를 사용할 것이므로 mdb용 드라이벌를 선택한다.

사용자 삽입 이미지

데이터 원본 이름에 원하는 디비의 이름을 넣고 이제 MDB를 연결하기 위해서 데이터베이스의 "선택"을 눌러서 들어간다.

사용자 삽입 이미지

사용할 MDB를 찾아서 지정하고 이제 다시 ODBC Microsoft Access 설정화면에서 "고급"을 눌러서 들어간다.

사용자 삽입 이미지

사용할 로그인 아이디와 비밀번호를 지정한다. 이렇게 하면 ODBC의 등록과정이 완료된다.





이제 자바쪽에서 JDBC를 이용해서 연결할 차례다.
  1. import java.sql.Connection;  
  2. import java.sql.DriverManager;  
  3. import java.sql.PreparedStatement;  
  4. import java.sql.ResultSet;  
  5. import java.sql.SQLException;  
  6. import java.sql.Statement;  
  7. import java.util.Properties;  
  8.  
  9. public class DBConnection {      
  10.     private String DB_URL = "Jdbc:Odbc:Testdata";  
  11.     private String DB_USER = "test";  
  12.     private String DB_PASSWORD= "test";  
  13.       
  14.     Connection conn = null;  
  15.       
  16.     public Connection getConnection() {  
  17.         try {  
  18.             Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");  
  19.         } catch (ClassNotFoundException e1) {  
  20.             e1.printStackTrace();  
  21.         }  
  22.           
  23.         try {  
  24.             Properties props = new Properties();  
  25.             props.put("charSet", "8859_1" );   
  26.             props.put("user", DB_USER);  
  27.             props.put("password", DB_PASSWORD);  
  28.  
  29.             conn = DriverManager.getConnection(DB_URL, props);  
  30.         } catch (SQLException e) {  
  31.             e.printStackTrace();  
  32.         }  
  33.         return conn;  
  34.     }  
  35. }  

여기서는 연결을 위한 설명이라서 연결부분의 코드만 가져왔다. disconnect를 예제소스에서 뺐다는 말이다.

jdbc 연결 URL로는 "Jdbc:Odbc:데이터원본이름"을 사용하고 username과 password는 ODBC등록할 때 사용한 것을 적는다.(근데 없어도 잘 돌아가는것 같다.. ㅡ..ㅡ) 드라이버 클래스는 "sun.jdbc.odbc.JdbcOdbcDriver"를 사용한다.

그리고 여기서는 Properties를 사용하였는데 이건 mdb의 한글문제때문이다. mdb는 한글을 8859_1로 사용하기 때문에 한글을 안깨지고 받아오기 위해서는 Connection을 연결할 때 charSet을 8859_1로 설정해 주어야 한다.
  1. DBConnection dbConn = new DBConnection();  
  2. Connection conn = null;   
  3. PreparedStatement psmt = null;  
  4. ResultSet rs = null;  
  5.       
  6. try{          
  7.     String sql = " SELECT username FROM member ";  
  8.     conn = dbConn.getConnection();    
  9.     psmt = conn.prepareStatement(sql);          
  10.       
  11.     rs = psmt.executeQuery();  
  12.               
  13.     while (rs.next()) {  
  14.         String username = new String(rs.getString("username").getBytes("8859_1"), "euc-kr");  
  15.     }    
  16. }catch(Exception e){  
  17.     e.printStackTrace();  
  18. }finally{  
  19.     dbConn.disConnection(rs,psmt, conn);  
  20. }  

이제 Connection을 사용해서 Select 쿼리를 날리는 부분이다. import등의 필요없는 부분은 빼버렸다. 결과적으로는 14번째 라인만 보면 된다. 영문이나 숫자는 괜찮지만 한글의 경우 디비커넥션에서 사용할 때 Access에서 한글을 8859_1(ISO-8859-1)을 사용했기 때문에 여기서도 받아온 스트링을 14번 라인처럼 엔코딩을 변경해 주어야 받아온 한글이 깨지지 않고 표시된다. (특이하게도 문서의 encoding이 euc-kr이든 utf-8이든 동일하게 위처럼 euc-kr로 인코딩을 해주어야 한글이 깨지지 않는다.)

INSERT나 UPDATE등 자바쪽에서 mdb로 한글을 넣을때도 동일하게 인코딩 타입을 변경해서 넣어주어야 한다. 당연히 인코딩은 반대로 해서...

String str = new String("한글".getBytes("euc-kr"), "ISO-8859-1");

위처럼 하면 된다. ( ISO-8859-1와 8859_1는 동인한 인코딩이다.)

디비를 mdb를 쓰면 한글을 넣고 빼는 일은 빈번할테니까 위 2가지를 메서드로 따로 만들어두면 편할것이다. (뭐 일일이 인코딩을 바꿔주면서 넣는걸 편하다고 할 수는 없지만... ㅡ..ㅡ)
Posted by 1010
01.JAVA/Java2009. 1. 28. 16:13
반응형
문자열을 만들때는 보통 StringBuffer를 사용한다. StringBuilder인가도 있지만 사용해 본적도 없고 StringBuffer대신 쓸 이유는 아직 잘 모르겠다. 하지만 문자열을 만드는데는 단연코 String으로 +로 이어붙히는것보다는 훨씬 빠르다.

  1. StringBuffer sql = new StringBuffer(" SELECT col1, col2, col3, col4 ");  
  2. sql.append(" FROM table1  ");  
  3. sql.append(" WHERE col1 = 1  ");  


머 위의같은 코드들... append()를 이용해서 계속 이어붙힐 수 있고 속도도 빠르지만 이 StringBuffer객체를 재사용해야할 때가 있다. sql2를 만들기는 좀 그러니까...

가장 쉽게 생각할 수 있는 건 null이다.

  1. sql = null;  
  2. sql = new StringBuffer(" SELECT col1, col2, col3, col4 ");  

null로 객체 없애버리고 다시 새로 생성하는 것이다.  일반적으로 초기에 가장 많이 쓰는 방식이지 싶다.

또 하나는 setLength(0)이다.

  1. sql.setLength(0);  
  2. sql.append(" SELECT col1, col2, col3, col4  ");  

StringBuffer객체는 그대로 놔두고 setLength함수를 이용해서 길이를 0으로 설정해서 기존에 가지고 있던 문자열을 날려보리고 append로 새로 이어붙히는 것이다.


성능테스트는 안해봤지만 크게 차이는 나지 않을것 같기는 하지만 그래도 객체를 새로 생성하는 것보다는 길이를 0으로 만드는 것이 좀더 빠르지 않을까 생각한다.

간단한 것들은 잘 안적었더니 이런게 더 잘 찾기 힘들단 말야 ㅎ
Posted by 1010
01.JAVA/Java2009. 1. 28. 16:12
반응형
웹개발하면서 DB의 사용은 필연적이기 때문에 java.sql.PreparedStatement의 사용은 필연적이다. Statement도 있기는 하지만 PreparedStatement는 한번 사용한 SQL문이저장되기 때문에 반복해서 사용할 경우 성능이 좋기 때문에 일반적으로는 PreparedStatement를 사용한다.

  1. StringBuffer sql = new StringBuffer(" INSERT INTO poll (col1, col2 ) VALUES (?, ?) ");  
  2.  
  3. psmt = conn.prepareStatement(sql.toString());  
  4. psmt.setString(1, "test");  
  5. psmt.setString(2, "test");  
  6. psmt.executeUpdate();  

일반적으로 위와 같이 사용하는데 저렇게 사용해서 결과값 받아서 결과값 리턴해주고 끝인데 상황에 따라서는 쿼리를 반복적으로 사용해야할 때가 있다. 보통은 저게 DAO에 들어있기 때문에 메서드를 여러번 실행해도 되겠지만 그렇게 하면 Connection도 끝었다 다시하기 때문에 별로 효율적이지 못하다.

  1.  
  2. StringBuffer sql = new StringBuffer(" INSERT INTO poll (col1, col2 ) VALUES (?, ?) ");  
  3.  
  4. for(int i = 0; i<5; i++) {  
  5.     psmt = conn.prepareStatement(sql.toString());  
  6.     psmt.setString(1, "test");  
  7.     psmt.setString(2, "test");  
  8.     psmt.executeUpdate();  
  9. }  
  10.  

하지만 여러번 반복해서 쿼리를 실행하기 위해서 위처럼 사용하면 안된다. 보기에는 크게 문제가 없고 일반적인 객체의 경우는 저렇게 재할당해서 사용해도 되기는 하지만 PreparedStatement는 다르다. 저렇게한 다음에 psmt.close();를 실행하면 아래쪽 맨 마지막psmt만 닫히고 앞의 psmt들은 닫히지 않는다. 정확히 말하면  앞에서 할당한 psmt는 결코 닫을수 없는 형태가 되어버린다.

  1.  
  2. StringBuffer sql = new StringBuffer(" INSERT INTO poll (col1, col2 ) VALUES (?, ?) ");  
  3.  
  4. for(int i = 0; i<5; i++) {  
  5.     psmt.setString(1, "test");  
  6.     psmt.setString(2, "test");  
  7.     psmt.executeUpdate();  
  8.     psmt.clearParameters();  
  9. }  
  10.  
  11.  

이걸 위처럼 사용하고 있다. PreparedStatement의 객체를 새로할당하는게 아니라 파라미터를 할당하고 실행한 다음에 psmt.clearParameters();로 파라미터를 클리어해버린다. 루프돌면서 다시 할당하고 이렇게 돌리면 PreparedStatement객체를 여러번 사용할 수 있다.

  1.  
  2.  
  3. StringBuffer sql = new StringBuffer(" INSERT INTO poll (col1, col2 ) VALUES (?, ?) ");  
  4.  
  5. for(int i = 0; i<5; i++) {  
  6.     psmt.setString(1, "test");  
  7.     psmt.setString(2, "test");  
  8.     psmt.addBatch();  
  9. }  
  10. psmt.executeBatch();  
  11.  
  12.  
  13.  

이걸 좀 더 세련(?)되게 하면 위와같이 사용할 수 있다. JDBC의 배치기능을 사용한 것이다. PreparedStatement의 배치기능은 다수의 쿼리를 한꺼번에 실행하기 위한 기능이다. 여러번에 나누어서 실행하지않고 배치기능을 이용해서 한번에 송신해서 실행함으로써 퍼포먼스를 높일 수 있다.

addBatch()는 쿼리와 파라미터 셋을 배치에 추가하고 이렇게 추가된 배치를 executeBatch()를 통해서 한꺼번에 실행한다. 정확한 테스트까지는 못해봤지만 이렇게 사용하는 배치 메모리의 크기 제한이 있기 때문에 너무 많은 배치를 추가할 경우에는 out of memory가 발생할 수 있기 때문에 많은 addBatch()를 실행해야 할 경우에는 중간중간 executeBatch()를 실행해 주어야 한다.
Posted by 1010
01.JAVA/Java2009. 1. 28. 16:11
반응형
새로운 디비 연결할때마다 혹은 사용하던것도 굳이 외우고 있지는 않아서 이전소스를 찾아봐야하는 JDBC Drivers. 날잡아서 정리... ㅎ 실제 적용할때 한번더 찾아봐야겠지만 그래도 일일이 찾아다니는거 힘들어서.. ㅎ



Informix
Driver Class : com.informix.jdbc.IfxDriver
JDBC URL : jdbc:informix-sqli://<host>:<port>/<database>:informixserver=<dbservername>
Required File : ifxjdbc.jar  Download


JavaDB/Derby
Driver Class : org.apache.derby.jdbc.ClientDriver
JDBC URL : jdbc:derby:net://<host>:<port1527>/<databaseName>
Required File : derbyclient.jar  Download


Microsoft SQL Server 2000
Driver Class : com.microsoft.jdbc.sqlserver.SQLServerDriver
JDBC URL : jdbc:microsoft:sqlserver://<host>:<port1433>;DatabaseName=<database>
Required File : msjdbc.jar  Download


Microsoft SQL Server 2005
Driver Class : com.microsoft.sqlserver.jdbc.SQLServerDriver
JDBC URL : jdbc:sqlserver://<host>[:<port1433>];databaseName=<database>
Required File : sqljdbc.jar  Download


MySQL (Connector/J)
Driver Class : com.mysql.jdbc.Driver
JDBC URL : jdbc:mysql://<host>:<port3306>/<database>
Required File : mysql-connector-java-nn-bin.jar  Download


Oracle (Thin JDBC Driver)
Driver Class : oracle.jdbc.driver.OracleDriver
JDBC URL : jdbc:oracle:thin:@<host>:<port>:<SID>
                       jdbc:oracle:thin:@<host>:<port>/<service>
                       jdbc:oracle:thin:@<TNSName>
Required File : ojdbcxx.jar  Download
Oracle JDBC FAQ


Oracle (OCI JDBC Driver)
Driver Class : oracle.jdbc.driver.OracleDriver
JDBC URL : jdbc:oracle:thin:@<host>:<port>:<SID>
                      jdbc:oracle:thin:@<host>:<port>/<service>
                      jdbc:oracle:thin:<TNSName>
Required File : ojdbcxx.jar  Download    Oracle Database Instant Client
Oracle JDBC FAQ


PostgreSQL
Driver Class : org.postgresql.Driver
JDBC URL : jdbc:postgresql://<host>:<port5432>/<database>
Required File : postgresql-nn.jdbc3.jar   Download



Sun에서 제공하는 JDBC Driver 리스트
Posted by 1010
01.JAVA/Java2009. 1. 19. 13:39
반응형
동적 서블릿 이미지 메이킹을 마음대로!

난이도 : 초급

Dan Becker
소프트웨어 개발자, IBM Software Group
2002년 11 월

웹 사이트를 코딩하고 지원하거나 인터넷에 페이지를 갖고 있다면 독자들의 필요를 충족시키는 이미지를 제공하는 것이 얼마나 어려운 일인지 알 것이다. JavaServer Pages (JSP) 태그를 이용하여 이미지 관리를 시도해보자.

세상을 지배하라! 그렇지 않다면, 적어도 이미지라도 지배하라!
사이트 개발자 또는 페이지 작성자로서 다양한 선호를 만족시키는 것은 어려운 일이라는 것을 안다. 이를 수작업으로 수행하기 위해서는 사이트의 이미지 하나하나를 웹 사이트가 지원하는 이미지 사이즈로 변환해야한다. 그런다음 사이트의 각 페이지에 이미지 태그를 조정하여 각각의 태그가 이미지 사이즈를 적절하게 반영할 수 있도록 해야한다. 이미지를 변경하지 않고서는 HTML img 태그의 넓이와 높이를 간단히 변경시킬 수 없다. 이는 저대역 사용자들은 큰 이미지를 다운로드하여 클라이언트 측에서 리사이징을 해야한다는 것을 뜻한다. 이러한 종류의 이미지 관리는 성가실 뿐 아니라 에러를 양산해낸다. 그리고 대부분의 웹 사이트가 다양한 이미지 사이즈를 제공하지 않는 이유를 쉽게 알 수 있다.

문제는 기술의 문제가 아니다. 자바 프로그래밍으로 이미지를 다양한 사이즈나 포맷으로 변환하기는 쉽다. 서비스만의 문제 또한 아니다. 웹 서비스를 사용하여 각자의 필요에 맞춰 페이지를 커스터마이징하는 것이 일반적이기 때문이다. 문제는 전개와 관리가 쉽도록 기술과 서비스를 조합하는 일이다.

이 글은 JavaServer Pages (JSP) 태그를 사용하는 솔루션으로 이미지를 관리하는 방법을 다룬다. 예를들어 HTML에서 이미지 태그를 코딩하고 다양한 버전의 이미지 사이즈를 갖추는 대신 다음과 같이 하는것이다:



<img src="images/LazyDog.jpg" width="800" height="600" >

사용자의 선호에 맞춰 이미지를 자동으로 사이징하는 태그를 갖추는 것이 합리적이다:



<util:imagesizer src="images/LazyDog.jpg"/>

사용자가 다양한 사이즈 중에서 선택하고 선호도에 따라 사이트상의 모든 이미지가 바뀌도록 하는 것이 가능하다. 그림 1의 샘플 이미지를 보자. 또한 넓이와 높이 속성을 삽입하고 태그를 수동으로 편집하는 일 따위는 하지 않아도 된다.

그림 1. 이미지 선택이 있는 JSP 페이지

한번도 JSP 커스텀 태그를 본 적이 없다면 예제에서 신택스를 연구해보자. JSP 커스텀 태그를 HTML 태그와 비교해보자:

JSP 페이지가 커스텀 image-sizer 태그를 사용할 때, 태그의 자바 구현은 이미지 파일을 찾아 적절한 사이즈로 변환하고 이 이미지를 독자에게 제공한다. 태그는 퍼블리싱하기 전에 이미지 변환 부터 사이트 매니저를 저장한다. 또한 웹 페이지 작성 작업을 단순화한다. 단지 하나의 페이지가 많은 이미지 사이즈 선택을 핸들하는데 필요하기 때문이다. 무엇보다도, 중요한 것은 각자의 사이트 이미지에 이러한 유연성을 제공한다면 인기있는 사이트가 될 것이다.

웹 서버
이제는, 클라이언트(웹 브라우저를 이용하는 독자)가 JSP 페이지를 제공하는 사이트를 방문할 때 그 배후에 어떤일이 벌어지는지 알아보자. 세 개의 인터랙션이 이루어진다. (그림 2):

그림 2. 웹 클라이언트와 서버 간 인터랙션

첫 번째 경우, 브라우저가 HTML 파일이나 이미지 파일 같은 정적 문서를 요청한다고 가정해보자. 서버는 파일 공간에 리소스를 위치시키고 브라우저에 파일을 제공한다. 문서를 요청하고 요청에 응답하는 것은 HTTP에 정의되어 있는데 이것은 인터넷 상에서 클라이언트-서버 인터랙션의 기초가 된다. 웹 서버는 요청을 완벽하게 핸들하고 웹 애플리케이션 서버나 서블릿 콘테이너와 인터랙션 할 필요가 없다.

두 번째 경우 브라우저가 자바 서블릿을 포함하는 웹 리소스를 요청한다고 가정해보자. 자바 서블릿은 웹 서버가 자바 프로그래밍 언어를 사용하여 서버상에서 태스크를 수행하도록 한다. 서블릿들은 효율적이어서 CGI와 서버측 JavaScript 같은 오래된 기술보다 메모리와 프로세싱 파워를 덜 사용한다. 서블릿은 IBM WebSphere Application Server과 Apache Tomcat 같은 웹 서버에서 다른 기술들에 비해 이동성이 강하고, 다양한 플랫폼 상에서 같은 서블릿을 구동할 수 있는 서블릿 콘데이너를 지원한다. 마지막으로, 자바언어 고유의 안정성으로 인해 잘못된 서블릿이 웹 서버에 영향을 주는 경우도 드물다. 웹 서버는 적절한 서블릿을 찾아 필요할 경우 서블릿 소스 코드를 컴파일하고 서블릿 프로세싱 결과를 요청자에게 리턴한다. 자주 요청되는 서블릿은 서버 메모리에 캐싱된다. (그림 2).

세 번째 경우, 브라우저가 JSP 페이지를 포함하는 웹 페이지를 요청한다는 것을 가정해보자. JSP 페이지는 정보를 디스플레이 하는 작업을 쉽게 하고 동적 콘텐트를 정적 페이지와 분리하는 것을 돕는다. 웹 페이지 디자이너는 HTML 라이브러리에 있는 다른 태그인 것 처럼 하여 JSP 태그를 사용한다. JSP 프로그래머는 JSP 프로그래밍 스팩에 따라 태그를 구현한다.

이번에는 이미지-사이징 JSP 태그를 구현하고 JSP 코드를 작성하는 방법을 알아보자. 웹 콘테이너의 관점에서 볼 때 JSP 페이지는 자바 서블릿과 밀접하게 관련되어 있다. 텍스트 기반 JSP 페이지는 웹 콘테이너에 의해 자바 구현으로 변환된다. 웹 콘테이너는 자바 구현을 찾아 자바 서블릿 같은 구현을 처리하고 코드를 구동하여 프로세싱 결과를 클라이언트에 리턴한다. 많은 레이어와 리다이렉트가 있는 듯이 보이지만 디스패칭은 빠르고 사용자에게도 투명하다. 서블릿과 마찬가지로 자주 요청되는 JSP 페이지는 서버 메모리에 캐싱된다.

JSP 커스텀 태그 작성하기
웹 서비스가 JSP 페이지 요청을 어떻게 처리하는지 보았다. 이제는 JSP 커스텀 태그를 어떻게 구현하는지 보자. JSP 태그는 Java Standard Template Library(JSTL) 같은 표준 라이브러리와 일명 커스텀 태그라고 하는 스스로 작성한 라이브러리에도 해당된다는 것을 명심하라. 일반적으로 커스텀 태그는 특정 문제 도메인을 다룬다. 이 글에서는 이미지를 관리하는 방법을 다루겠다. 현재 Java 2 Extended Edition (J2EE) Versions 1.2와 1.3은 JSP Version 1.2를 사용한다. 현재 Sun은 JSP 스팩 2.0 버전을 발표했다. 이 새로운 스팩은 커스텀 태그를 구현하는 방식에 있어서 큰 변화가 없다.

표준과 커스텀 태그 라이브러리를 taglib 디렉티브를 통해 JSP 페이지로 반입할 수 있다:



<%@ taglib uri='imagesizer.tld' prefix='util' %>

이 디렉티브는 태그 라이브러리 디스크립터 파일의 위치를 명명한다. 여기서는 imagesizer.tld로 명명되었다. 그리고 사용된 프리픽스는 util로 명명되었다. 태그 예제에서 보았듯이 프리픽스와 이름으로 태그를 사용할 수 있다:



<util:imagesizer src="images/LazyDog.jpg"/>

태그 라이브러리 디스크립터는 웹 콘데이너에게 사용가능한 태그가 무엇인지 그들이 어떻게 작용하는지를 설명한다. Listing 1은 그 예제이다. 이 파일은 XML 포맷으로 되어있고 읽기 쉽다. 하지만 IBM WebSphere Studio Application Developer 같은 애플리케이션 개발 플랫폼은 필드를 채우고 파일을 검사하는 것을 돕는다. 가장 중요한 정보는 태그 엘리먼트이다. 이것은 JSP 커스텀 태그의 이름과 태그를 구현하는 자바 클래스를 정의한다. 또한 태그가 받아들이는 모든 속성과 바디 콘텐트를 보여준다.

Listing 1. Tag Library Descriptor (TLD) 발췌

<taglib >
  <tlibversion> 1.0 </tlibversion>
  <jspversion> 1.1 </jspversion>
  <tag>
    <name>imagesizer</name>
    <tagclass>tags.ImageSizerTag</tagclass>
    <bodycontent>empty</bodycontent>
    <attribute>
      <name>src</name>
      <required>required</required>
    </attribute>
    <attribute>
      <name>alt</name>
    </attribute>
    <attribute>
      <name>quality</name>
    </attribute>
  </tag>
</taglib>

이 예제에서 태그는 src 속성이 요구하는 세 개의 속성을 갖고 있다. alt 속성은 HTML img alt 속성을 모방한 것이다. 이 JSP 태그를 확장하여 다른 이미지 속성을 포함시켜도 된다.

JSP 커스텀 태그를 작성하는 다음 단계는 태그 용 자바 코드를 구현하는 것이다. 이 글에서 imagesizer 태그는 tags.ImageSizerTag 자바 클래스에서 구현된다. J2EE 커스텀 태그 지원 대부분은 javax.servlet.jsp.tagext 패키지에 위치해있다. imagesizer 클래스는 표준 TagSupport를 확장한다. 이것은 바디 없이 태그를 구현한다. 이것의 자손 클래스는 BodyTagSupport인데 이것은 바디가 있는 태그를 구현한다. 두 클래스 모두 Tag 인터페이스를 구현한다. doStartTagdoEndTag 메소드는 태그가 처음 읽혀지고 태그가 웹 컨테이너에 의해 완전히 읽혀진 후에 호출된다. ImageSizer 태그는 doEndTag 만 구현한다.

TagSupport 클래스에서 PageContext 클래스는 JSP 페이지와 관련된 중요한 정보에 액세스 하도록 한다. 예를들어, PageContextHttpRequestHttpResponse 객체에 액세스를 가능하게 한다. 이러한 객체들은 값을 읽고 응답을 작성하는데 필수적이다. 요청은, 사용자 선택을 트래킹하고 페이지에서 페이지로 값을 수행할 경우 HttpSession로 액세스를 허용한다. PageContextServletContext로 액세스를 허용하는데 이것은 서블릿 경로, 이름, 기타정보를 위치시키는데 도움이 된다. ImageSizer 코드(Listing 2)에서, PageContext 객체에 대한 레퍼런스와 정보가 있다. 그림 3은 이들 클래스들의 관계이다. 다른 표준 클래스 다이어그램 처럼 실선으로 된 박스는 클래스를 나타내고 점선 박스는 인터페이스를 나타낸다. 상속은 파생된 클래스나 인터페이스부터 부모까지 선으로 이어져있다.

Listing 2. ImageSizerTag doEndTag 구현

// Implement the tag once the complete tag has been read.
public int doEndTag() throws JspException {

  // Move request data to session.
  int outputSize = 0;
  String sizeVal = request.getParameter( REQUESTSIZEKEY );
  if ( sizeVal != null ) {
    session.setAttribute( REQUESTSIZEKEY, sizeVal );
    sizeVal = (String) session.getAttribute( REQUESTSIZEKEY );
    if ( sizeVal != null ) {
      outputSize = Integer.parseInt( sizeVal );
    }
  }
  
  // Get specified image locally.
  String contextPath = getContextPath( request );
  Image image = Toolkit.getDefaultToolkit().getImage(contextPath + src );
  ImageSizer.waitForImage( image );
  int imageWidth = image.getWidth( null );
  int imageHeight = image.getHeight( null );

  if (( imageWidth > 0 ) && ( imageHeight > 0 )) {
    if (( outputSize > 0 ) && ( outputSize != imageWidth )) {
      // Convert image to new size.
      Image outputImage = ImageSizer.setSize( image, outputSize, -1 );
      ImageSizer.waitForImage( outputImage );
      int outputWidth = outputImage.getWidth( null );
      int outputHeight = outputImage.getHeight( null );
 
      if ( outputWidth > 0 && outputHeight > 0 ) {
        // Change image file name to xxxx.size.jpg
        String originalSrc = src;
        int lastDot = src.lastIndexOf( '.' );
        if ( lastDot > -1 ) {
          src = src.substring( 0, lastDot + 1 );
        }
        setSrc( src + outputSize + ".jpg" );

        // Write new size image to JPEG file.
        File file = new File( contextPath + src );
        if ( !file.exists() ) {
          out.println( "" );
          FileOutputStream fos = new FileOutputStream( contextPath + src );
          ImageSizer.encodeJPEG( fos, outputImage, quality );
          fos.close( ) ;
        }
        
        imageWidth = outputWidth;
        imageHeight = outputHeight;
      }
    } // if outputSize
  } // if image found

  // Produce output tag.
  out.print( "<img src=\"" + src + "\"" );
  // Add alt text, if any
  if ((alt != null ) && ( alt.length() > 0 )) {
    out.print( " alt=\"" + alt + "\"" );
  }
  
  // Add proper width, height.
  out.print( " width=\"" + imageWidth + "\" height=\"" + 
    imageHeight + "\"" );
  out.println( ">" );

  return EVAL_PAGE;
} // doEndTag

그림 3. javax.servlet.jsp.tagext 클래스

ImageSizerTag 클래스의 doEndTag 메소드는 Listing 2에 나와있다. 이것은 JSP 커스텀 태그를 구현하는데 필요한 거의 모든 자바 코드라고 볼 수 있다. 큰 블록이지만 메소드를 완벽하게 보는데 도움이 된다. 우선 모든 HTTP 요청 매개변수는 HTTP 세션에 저장된다. 이것은 이미지 사이즈에 대한 사용자 선택 같은 속성을 가져다가 세션에 저장하여 페이지에 따라 사용자가 이를 따르도록 한다. 이 태그의 기능을 확장하기 위해서 이것을 확장하여 쿠키에 선택을 저장하여 사용자가 다음에 이 사이트를 방문할 때 선택을 사용하도록 할 수 있다.

이미지 사이징
지금까지 JSP 태그를 작성하는 단계를 검토했다. ImageSizerTag 클래스는 이미지를 자동으로 리사이징하여 사용자 선택에 맞춘다. 이번에는 ImageSizer 클래스를 사용하여 JPEG 파일로서 이미지를 리사이징하고 저장하는 구체적인 방법을 설명하겠다. 자바 코드로 이미지를 리사이징 할 때 java.awt.Image 클래스의 getScaledInstance 메소드를 사용할 때 쉬워진다. 이 메소드를 새로운 넓이와 높이로 하거나 형태 비율을 보존하기위해 -1에서 1까지의 매개변수 값을 제공한다. 그리고 나서 새롭게 리사이징된 이미지를 얻는다. 하지만 모든 자바 이미지는 즉시 사용할 수 있는 것은 아니다. 따라서 java.awt.MediaTracker 를 사용하여 이미지가 완전히 로딩될 때까지 기다려야한다. ImageSizerwaitForImage 메소드는 이 코드를 캡슐화한다.

이 예제에서 가장 어려운 디자인 포인트는 이미지 저장 방법을 결정하는 일이다. 자바 프로그래밍에는 이미지 인코딩과 저장에 많은 선택권이 있어 비교를 해야한다.

Listing 3의 경우 com.sun.image.codec 패키지를 사용했는데 IBM WebSphere와 Apache Tomcat 같은 J2EE 1.2 와 1.3 웹 서버 콘테이너에서 사용할 수 있기 때문이다. 인코더는 단순하며 100% 순수한 자바 코드이지만 com.sun 패키지에 있다. 장기적인 시각으로 볼 때 Java Image I/O 패키지는 보완해야 할 사항이 많이있다. 이미지 변형 기능과 많은 파일 포맷을 저장하는 기능이 훌륭하다. Java Image I/O 패키지는 Java 2 version 1.4 이전까지는 표준이 아니다.

어떤 이미징 패키지를 사용할 것인가에 대해 결정이 끝났다면 JPEG 파일을 저장하는 코드는 간단하다. ImageSizerencodeJPEG 메소드는 다음 절차를 캡슐화한다:

  1. java.awt.image.BufferedImage 객체-자바 Image 자손-는 리사이징된 아웃풋 이미지에서 만들어진다. 주석은 로고, watermark, 타임스탬프, 이미지 저작권 정보를 추가할 때 코드에 주석이 달린다.

  2. Image에서 BufferedImage로 변환한 후에, 아웃풋 스트림에 JPEGImageEncoder 객체를 만들어라. 아웃풋 인코딩 평가는 0.0 (최악)부터 1.0 (최고) 까지 이다. 0.75는 디폴트이지만 0.95는 좀더 자세한 이미지와 함께 큰 파일 사이즈로 만들어진다.

  3. 이미지는 아웃풋 스트림으로 인코딩되고 스트림은 모든 정보가 이미지 파일에 나타나도록 플러쉬된다.
Listing 3. ImageSizer encodeJPEG 구현

// Encodes the given image at the given
// quality to the output stream.
public static void encodeJPEG
  ( OutputStream outputStream,
  Image outputImage, float outputQuality )
  throws java.io.IOException {

  // Get a buffered image from the image.
  BufferedImage bi = new BufferedImage
    ( outputWidth, outputHeight,
      BufferedImage.TYPE_INT_RGB );
  Graphics2D biContext =
    bi.createGraphics( );
  biContext.drawImage
    ( outputImage, 0, 0, null );
  // Additional drawing code, such as
  // watermarks or logos can be placed here.

  // com.sun.image.codec.jpeg package
  // is included in Sun and IBM sdk 1.3.
  JPEGImageEncoder encoder =
    JPEGCodec.createJPEGEncoder
    ( outputStream );
  // The default quality is 0.75.
  JPEGEncodeParam jep =
    JPEGCodec.getDefaultJPEGEncodeParam
    ( bi );
  jep.setQuality( outputQuality, true );
  encoder.encode( bi, jep );
  outputStream.flush();
} // encodeImage

여기까지는 이미지를 리사이징하고 저장하는데 필요한 모든 단계이다.

WebSphere또는 Tomcat에서 패키징 및 전개
이번에는 Application Server version 4.0이나 Apache Tomcat version 4.0에서 ImageSizer JSP 태그의 패키지 및 전개 방법을 설명하겠다. 그림 4는 Application Developer 이다. 왼쪽 칼럼의 상단에 있는 Navigator는 웹 애플리케이션의 디렉토리 구조를 나타내고 JSP 태그가 J2EE 스팩에 따라 어떻게 패키지되어야 하는지를 보여준다. J2EE 스팩에 필요하기 때문이 이 디렉토리 구조는 모든 웹 애플리케이션에 일반적이다. 일단 구조가 저장되면 Web Archive (WAR) 파일이 되고 WebSphere, Tomcat, 기타 웹 콘테이너에 쉽게 전송된다. Application Developer 같은 좋은 개발 환경은 다음의 스팩을 따르고 훌륭한 애플리케이션을 만들어낸다.

그림 4. WebSphere Studio Application Developer에서 ImageSizer 패키징

ImageSizer 프로젝트에는 소스 코드용 디렉토리가 있다. 최종 WAR 파일에 이 디렉토리를 추가할 것인지의 여부는 개발자가 선택하기 나름이다. webApplication 디렉토리에는 실제 프로그램 코드가 포함되어 있다. 예제 프로젝트에는 PickASize.jsp이라고 하는 테스트 JSP 페이지와 LazyDog.jpg 이라고 하는 테스트 이미지가 포함되어 있다. 일반적으로 이들은 ImageSizer 커스텀 태그의 라이브러리 버전에 포함되지 않는다. 이 태그의 구현은 WEB-INF 디렉토리에 있다. 자바 클래스는 WEB-INF/classes에 있고 Tag Library Descriptor 파일은 WEB-INF/tlds에 있다. 이들은 모든 웹 애플리케이션에 적용되는 표준 디렉토리 위치이다. 이 트리에 있는 다른 파일들은 서버 옵션 설정에 도움을 주지만 그렇지 않더라도 WAR 파일에 필수적인 것은 아니다. Application Developer나 Java SDK를 사용하여 이 애플리케이션 용 WAR 파일을 만들어보라.

To m c a t 에 웹 애플리케이션을 설치하려면 ROOT/webapps 디렉토리에 파일을 놓고 서버가 WAR 파일을 디렉토리 구조로 확장하도록 해야한다. Application Server의 경우, Administrators Console의 웹 애플리케이션 마법사(Web Application wizard)를 사용하여 애플리케이션을 설치할 수 있다. 전개한 후에, http://yourhostname:port/ImageSizer/PickASize.jsp를 방문하여 JSP 페이지를 구동해보라.

결론
지금까지 이미지 사이징을 자동으로 관리하는 JSP 커스텀 태그를 만들었다. 커스텀 태그는 이미지 리사이징 작업에 도움이 되고 웹 사이트를 방문하는 사용자들이 선택을 할 수 있도록 한다. 예제 태그를 확장하여 모든 종류의 이미지 조작을 할 수 있다. 저작권 텍스트, 타임스탬프, 로고, 워터 마크 등이 그것이다. Application Server나 Apache Tomcat에 이를 전개하고 이미지 기반 JSP 페이지를 작성하거나 예제를 사용하여 코드를 실험할 수 있다. 이 글이 JSP 태그에 대해 실질적인 팁을 주었기를 바라며 각자의 필요에 맞춰 기능을 확장해보기를 바란다.


출처 : http://www-128.ibm.com/developerworks/kr/library/j-jspdwj/index.html


참고자료

Posted by 1010
01.JAVA/Java2009. 1. 19. 13:38
반응형

썸네일 이미지 만들기

Java SDK1.4부터 Image I/O API가 추가되었다.


############################################
# 이미지 파일 읽어오기
############################################


javax.imageio.ImageIO 클래스가 제공하는 read()메소드를 이용한다.

BufferedImage read(File input)
BufferedImage read(InputStream input)
BufferedImage read(URL input)

File file = new File("c:\\image\\good.gif")
BufferedImage image = imageIO.read(file)

FileInputStream input = new FileInputStream("c:\\image\\good.gif")
BufferedImage image = imageIO.read(input)

URL logUrl = new URL("http://www.xxx.com/main/xxx.gif")
BufferedImage logoImage = imageIO.read(logUrl)



############################################
# 썸네일 이미지 만들기
############################################


1. ImageIO.read()메소드를 이용하여 BufferedImage에 저장한다.(원본)
2. 변경할 크기를 갖는 새로운 BufferedImage 객체를 생성한다.
3. 대상 BufferedImage객체의 createGraphics()메소드를 호출하여 대상 BufferedImage에 그림을 그릴 수 있는 Graphics2D 객체를 구한다.
4. Graphics2D 객체를 사용하여 원본 BufferedImage를 대상 BufferedImage에 그린다. 이때 크기를 조절한다.


BufferedImage srcimg = imageIO.read(imgFile);
BufferedImage destimg = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g = srcImg.createGraphics();
g.drawimage(srcImg, 0, 0, width, height, null);



###########################################
# BufferedImage를 파일로 저장하기
###########################################


imageIO.write()메소드를 통해서 BufferedImage를 출력할 수 있다.

write(RenderedImage im, String formatName, File output)
write(RenderedImage im, String formatName, OutputStream output)

formatName : PNG png, jpeg JPEG jpg JPG



###########################################
# 이미지크기를 변경
###########################################


boolean isResult = false;

FileInputStream srcls = null;
srcls = new FileInputStream("c:\\sample.gif");

BufferedImage srcimg = ImageIO.read(srcls);
BufferedImage destimg = new BufferedImage(30, 30, BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g = destimg.createGraphics();
g.drawImage(srcimg, 0, 0, 30, 30, null);

File dest = new File("c:\\thum2_sample.jpg");
isResult = ImageIO.write(destimg, "jpg", dest);

if (isResult)
{
 out.println("Image변환 성공");
}

Posted by 1010
01.JAVA/Java2009. 1. 14. 11:07
반응형
달빼기, 달더하기(?)
특정일에 이상한 날짜가 들어가서 페이지 에러가 난다는 제보...
페이지를 확인해 보니
검색기간을 기본세팅해줘야 하는데 메소드가 아주 지저분했었다.

3월달보다 작거나 같으면 연도를 빼주고,
31일이 안되는 날은 30일이나 28일로 만들고...
Date 클래스를 많이 안써본 관계로
당연히 오라클의 add_months() 같은 메소드가 있을것이라 생각했는데
getXXX, setXXX 메소드의 집합만이...OTL

다행히 Calendar 클래스가 있었고
이용해서 대강 만들어서 수정했다.



java.util.Calendar cal = new java.util.GregorianCalendar(); 
// 생성자가Calendar()가 아니고 GregorianCalendar()...이유는 검색^^
cal.setTime(today);
// void setTime(Date)
cal.add(Calendar.MONTH, -3);
// void add(int Field, int amount)...Field는 Calendar클래스 내의 Field
tempDay = cal.getTime();
// Date getTime()
Posted by 1010
01.JAVA/Java2008. 12. 22. 15:18
반응형

Chart Types

This document gives an overview of the types of charts you can create using the Chart API.

Table of Contents

Line charts

Specify a line chart with

cht=<line chart style>

Where <line chart style> is lc, ls, or lxy as described in the following table.

Parameter Description Example

cht=lc

For charts of type lc, data points are spaced evenly along the x-axis. Multiple data sets are drawn as multiple lines.

For information on how to specify multiple data sets see Using multiple data series.

For information on available parameters, see Optional parameters by chart type.

Chart with line in yellow
cht=lc
chd=t:40,60,60,45,47,75,70,72

cht=ls

Line charts of type ls are also known as sparklines. They take the same parameters, and behave in the same way, as charts of type lc. The difference between charts of type lc and charts of type ls, is that by default, no axis lines are drawn for charts of type ls.

For information on how to add axis lines and labels to a line chart, see Axis styles and labels.

Sparkline chart in blue
cht=ls
chd=t:27,25,60,31,25,39,25,
31,26,28,80,28,27,31,27,
29,26,35,70,25

cht=lxy

For charts of type lxy, a pair of data sets is required for each line. The first data set of each pair specifies the x-axis coordinates, and the second data set specifies y-axis coordinates.

If you pass in an odd number of data sets, the last set is ignored. To space the data points evenly along the x-axis, provide a single undefined value as the first data set of the pair. For more information about undefined values, see Data formats.

Line chart with unevenly spaced data points and lines in red, green and dashed blue
cht=lxy
chd=t:10,20,40,80,90,95,99|
20,30,40,50,60,70,80|
-1|
5,25,45,65,85

Back to top

Bar charts

Specify a bar chart with

cht=<bar chart style>

Where <bar chart style> is bhs, bhg, bvs or bvg as described in the following table.

Depending on the bar chart style, multiple data sets are drawn as stacked or grouped bars. For information on how to specify multiple data sets, see Using multiple data series.

For information on available parameters, see Optional parameters by chart type.

Parameter Description Example

cht=bhs

Horizontal bar chart, with stacked bars.

The first example (all bars in dark blue) has a single data set.

The second example (bars in dark and pale blue) has two data sets.

As you can see, multiple data sets are stacked. You must specify a color for each data set. You can also specify a color for each data point, or bar, within a single data set. See Colors for information about specifying colors.

The second example does not use data scaling. Data points that have a combined value greater than 100 are only partially visible. See Text encoding with data scaling for more information about data scaling.

Horizontal bar chart with two data sets: both are colored in blue
cht=bhs
chco=4D89F9

Horizontal bar chart with two data sets: one data set is colored in dark blue the second is stacked in pale blue
cht=bhs
chco=4D89F9,C6D9FD
chd=t:10,50,60,80,40|
50,60,100,40,20

cht=bvs

Vertical bar chart, with stacked bars.

This example has two data sets, and uses data scaling to ensure that the bars are fully visible.

Vertical bar chart with two data sets: one data set is colored in dark blue the second is stacked in pale bluecht=bvs
chco=4D89F9,C6D9FD
chd=t:10,50,60,80,40|
50,60,100,40,20
chds=0,160

cht=bhg

Horizontal bar chart, with grouped bars.

Horizontal bar chart with two data sets: one data set is colored in dark blue the second is adjacent in pale blue
cht=bhg
chco=4D89D9,C6D9FD

cht=bhg
cht=bvg

Vertical bar chart, with grouped bars.

Vertical bar chart with two data sets: one data set is colored in dark blue the second is adjacent in pale blue
cht=bvg
chco=4D89F9,C6D9FD

chbh

The default width for bars is 23 pixels. If you specify too many bars for the available chart size, not all bars appear.

Use chbh=a to fix this problem, by automatically resizing bars so that they fit within the chart size.

For more options, see Bar width and spacing.

Vertical grouped bar chart in blue and black, bars have the default width
cht=bvg

chbh omitted

Vertical grouped bar chart in blue and black, bars and spaces are automatically sized
cht=bvg
chbh=a

Back to top

Pie charts

Specify a pie chart with

cht=<pie chart style>

Where <pie chart style> is p, p3, or pc as described in the following table. For information on available parameters, see Optional parameters by chart type.

Parameter Description Example

cht=p

Two dimensional pie chart.

Supply one data set only; subsequent data sets are ignored.

By default, pie segment colors are interpolated from dark orange to pale yellow. Specify other colors as described in Colors.

Specify labels with chl as described in Pie chart labels.

The Google Chart API calculates the circle's radius from the minimum of width and height specified in the chart size (chs) parameter. If you are including labels, you probably need to specify the size of the width to be twice the size of the height, to ensure that your labels are fully visible.

Two-dimensional pie chart with four segments where segment colors are interpolated from dark to pale orange

cht=p
chs=200x100

cht=p3

Three dimensional pie chart.

Specify data and formatting in the same way as for two dimensional pie charts, above.

If you are including labels in a three dimensional pie chart, you probably need to specify the size of the width to be 2.5 times the size of the height, to ensure that your labels are fully visible.

Three-dimensional pie chart with four segments where segment colors are interpolated from dark to pale orange

cht=p3
chs=250x100

cht=pc

Concentric pie charts.

Supply two or more data sets.

Two concentric pie charts with four segments each, where segment colors are interpolated from dark to pale orange

cht=pc
chd=s:Helo,Wrld

chp

Pie chart orientation.

To change the orientation of a pie chart, use
chp=<angle in radians>

Pie chart with two segments

chd=t:80,20

Pie chart with two segments, rotated by 0.628

chd=t:80,20
chp=0.628

Back to top

Venn diagrams

Specify a venn diagram with

cht=v

Supply one data set where:

  • The first three values specify the relative sizes of three circles: A, B, and C.
  • The fourth value specifies the area of A intersecting B.
  • The fifth value specifies the area of A intersecting C.
  • The sixth value specifies the area of B intersecting C.
  • The seventh value specifies the area of A intersecting B intersecting C.
Parameter Description Example

cht=v

In this example, the value 100 specifies the first circle, the value 80 specifies the second circle, and the value 60 specifies the third circle. The values 30, 25 and 20 specify the areas of overlap between each pair of circles. The value 10 specifies the area of overlap between all three circles.

For information on available parameters for Venn diagrams, see Optional parameters by chart type.

Venn diagram with three overlapping circles
cht=v
chd=t:100,80,60,30,25,20,10

Back to top

Scatter plots

Specify a scatter plot with

cht=s

For information on available parameters, see Optional parameters by chart type.

Parameter Description Example

cht=s

The default shape for data points in a scatter plot is a circle. The default color for data points is blue. To use a different shape or color, specify the shape markers by using the chm parameter. More information can be found in Shape markers.

At least two data sets should be supplied. The first data set specifies x-coordinates, and the second data set specifies y-coordinates.

A third data set can be used to provide points of varying sizes. If you use chm to alter the shape or color of data points, note that points are scaled relative to the size provided in chm.

Scatter plot with default blue circle data points in different sizes as defined by a third data set
cht=s
chd=t:12,87,75,41,23,96,68,71,34,9|
98,60,27,34,56,79,58,74,18,76|
84,23,69,81,47,94,60,93,64,54

Back to top

Radar charts

Specify a radar chart with

cht=r or cht=rs

For information on available parameters, see Optional parameters by chart type.

Parameter Description Example

cht=r

In a radar chart, data points are drawn between the center of the chart and the perimeter. Points of value zero (0, A or AA depending on the type of encoding) are drawn at the center. Points with the maximum value for the encoding used (100, 9 or ..) are drawn at the perimeter. Intermediate values are scaled between the two.

The points representing the first and last values in the data set are drawn between the center of the chart and the top of the chart. The remaining points are evenly spaced clockwise around the chart, and a line is drawn between each pair of points.

For a chart of type r, points are connected with straight lines.

This example shows a simple radar chart, with a single data set.

Radar chart
cht=r
chd=t:10,20,30,40,50,60,70,80,90

You can add further information and clarity to a radar chart by adding colors, line styles, and axis labels.

This example is a more complex radar chart with two data sets. The color of each data set is specified with chco, as described in Chart colors.

Line styles are specified with chls, as described in Line styles.

Axis labels are specified with chxt, chxl, and chxr, as described in Axis styles and labels. For radar charts, the x-axis is drawn in a circle, and the y- and r-axes go from the center of the chart to the top. The t-axis is ignored.

When labels are included in a radar chart, the spacing of the data points around the chart is determined by the number of labels, or the number of data points, whichever is larger.

Radar chart
chco=FF0000,FF9900
chls=2.0,4.0,0.0|2.0,4.0,0.0
chxt=x
chxl=0:|0|45|90|135|180|225|270|315
chxr=0,0.0,360.0

cht=rs

This example uses the same parameters as the previous example, but has a Fill area specified for both data sets.

This example includes an added grid. See Grid lines for more information.

For charts of type rs, points are connected with splines to form a curved line.

Radar chart
chg=25.0,25.0,4.0,4.0
chm=
B,FF000080,0,1.0,5.0|
B,FF990080,1,1.0,5.0

The final example shows the previous example with added Shape markers. The horizontal line shape marker (specified with chm=h) produces a circle on a radar chart. The vertical line shape marker specified with chm=v is drawn as a line from the center of the radar chart to the specified data point. The vertical line shape marker specified with chm=V is drawn as a line from the center of the chart to the perimeter.

The shape markers are specified as follows:

  • the outer blue circle is specified with chm=h,0000FF,...
  • the inner, partially transparent, blue circle is specified with chm=h,3366CC80,...
  • the partially transparent green line (between 1 and 2 o'clock) is specified with chm=V,00FF0080,...
  • the dark green line (at 8 o'clock) is specified with chm=V,008000,...
  • the green line (at 10 o'clock) is specified with chm=v,00A000,...

See Shape markers for more information about available shapes.

You can also specify range markers. See Range markers for more information.

Radar chart
chm=
h,0000FF,0,1.0,4.0|
h,3366CC80,0,0.5,5.0|
V,00FF0080,0,1.0,5.0|
V,008000,0,5.5,5.0|
v,00A000,0,6.5,4

Back to top

Maps

Specify a map with

cht=t, and chtm=<geographical area>

Where <geographical area> is one of the following:

  • africa
  • asia
  • europe
  • middle_east
  • south_america
  • usa
  • world

For example:

Map of the World
chs=440x220
chd=s:_
cht=t
chtm=world

This is the default map for the world. The size used in the example (440 by 220 pixels) is the maximum for all maps. Note that the data set contains just one character, an underscore (_). The underscore specifies a missing value in simple encoding and gives us the simplest map possible. You can make your map more informative and interesting by using color for one or more countries.

Specify the colors on a map, and how they are applied to each country or state, with three parameters in combination:

chco=<default color>,<start of gradient>,<end of gradient>
chld=<country or state codes>
chd=<color levels>

Where:

  • <default color>, <start of gradient>, and <end of gradient> are RRGGBB format hexadecimal numbers. The default color is applied to countries or states that are not listed in the chld parameter. The other colors specify the extremes of a color gradient that is used to color all countries listed in the chld parameter. The color that is applied depends on the country's value in the chd parameter. To control the gradient further, specify more colors.
  • <country or state codes> is a list of either of the following:
  • <color levels> are text encoding, simple encoding, or extended encoding values. Text encoding with data scaling is not available for maps. The first value is used for the first country listed in the chld parameter, the second value is used for the second country listed, and so on. The lowest data value (A, 0, or AA depending on the type of encoding used) is drawn in the color specified by <start of gradient> in chco. The highest value (9, 100, or ..) is drawn in the color specified by <end of gradient>. Intermediate values give intermediate colors.

For example:

Map of Africa
chtm=africa
chco=FFFFFF,FF0000,FFFF00,00FF00
chld=DZEGMGAOBWNGCFKECGCVSNDJTZGHMZZM
chd=t:0,100,50,32,60,40,43,12,14,54,98,17,70,76,18,29
chf=bg,s,EAF7FE

In this example:

  • The first color is FFFFFF, which specifies white. This color is used for any country not listed in the chld parameter.
  • The other colors specify a color gradient from red, through yellow, to green.
  • The first country listed is Algeria (DZ). The first value listed is 0. Therefore, Algeria is drawn with the color at the beginning of the gradient, and appears in red.
  • The second country listed is Madagascar (MG). The second value listed is 100. Therefore, Madagascar is drawn with the color at the end of the gradient, and appears in green.
  • The third country listed is Egypt (EG). The third value listed is 50. Therefore, Egypt is drawn with the color that lies midway across the gradient. The gradient is specified as going from red, through yellow, to green. Egypt appears in yellow.
  • Water masses are considered to be the map's background. They are drawn in pale blue (EAF7FE), as specified in the chf parameter. See Chart area and background fill for more details on background colors.

Back to top

Google-o-meters

Specify a Google-o-meter with

cht=gom

For information on available parameters, see Optional parameters by chart type.

Parameter Description Example

cht=gom

Here's an example of a Google-o-meter using the default colors. See Chart colors for information on specifying other colors.

See Google-o-meter labels for information on specifying the text that appears at the end of the arrow.

Google-o-meter with default red to green coloring
cht=gom
chd=t:70

Back to top

QR codes

QR Codes are a popular type of two-dimensional barcode, which are also known as hardlinks or physical world hyperlinks. QR Codes store text, which can be a URL, contact information, telephone number, even whole verses of poems!

QR codes can be read by any device that has the appropriate software installed. Such devices range from dedicated QR code readers to mobile phones.

Specify a QR code with

cht=qr
chl=<text to encode>
choe=<output encoding>

Where:

  • <text to encode> is the text for the QR code. The text must be url-encoded in UTF8. Note that the space between hello and world is written as %20 in the following example.
  • <output encoding> specifies how the output is encoded. If this parameter is omitted, the default encoding of UTF-8 is used. Available encodings are Shift_JIS, UTF-8, or ISO-8859-1.

For example:
QR code
cht=qr
chl=hello%20world
choe=UTF-8

The example QR code above is the simplest possible type of QR code, and is known as Version 1. It has 21 rows and 21 columns.

  • Version 1 has 21 rows and 21 columns, and can encode up to 25 alphanumeric characters
  • Version 2 has 25 rows and 25 columns, and can encode up to 47 alphanumeric characters
  • Version 3 has 29 rows and 29 columns, and can encode up to 77 alphanumeric characters
  • ...
  • Version 40 has 177 rows and 177 columns, and can encode up to 4,296 alphanumeric characters

The Chart API generates the appropriate QR code version depending on the number of characters you provide. For example, if you provide 55 alphanumeric characters, the Chart API will generate a Version 3 QR code. This may be modified by the error correction (EC) level you choose to use.

Don't confuse the number of rows and columns with size of the QR code image. The number of characters you provide determines the number of rows and columns. The pixel size of the chart is determined with chs as usual (see Chart size).

Error correction and margins

Four levels of error correction (EC) are available. The default level (L) allows the QR code to be read even if up to 7% of the code is misread, missing, or obscured. Other levels provide error correction for codes where up to 30% of the code cannot be read. The number of characters that can be encoded decreases as the EC level increases. See Versions, error correction and maximum characters for details.

The default margin is 4 modules. This means that a blank space equivalent to four rows at the top and bottom and four columns on the left and right is placed around the QR code. This is the minimum required by QR readers.

Optionally, specify an EC level and margin with

chld=<EC level>|<margin>

Where:

  • <EC level> is one of the following:
    • L allows 7% of a QR code to be restored
    • M allows 15% of a QR code to be restored
    • Q allows 25% of a QR code to be restored
    • H allows 30% of a QR code to be restored
  • <margin> defines the margin (or blank space) around the QR code. The default image has a margin equivalent to 4 rows / columns of the chart.

Versions, error correction and maximum characters

Before generating your QR code, consider what kind of device is used to read your code. The best QR code readers are able to read Version 40 codes, mobile devices may read only up to Version 4.

See the following table for information on maximum number of characters for each version and EC level.

Version Rows by columns EC level Maximum characters by EC level and character type
Digits: 0 to 9 Alphanumeric:
0 to 9, A to Z,
space, $ % * + - . / :
Binary Kanji
1 21x21 L 41 25 17 10
M 34 20 14 8
Q 27 16 11 7
H 17 10 7 4
2 25x25 L 77 47 32 20
M 63 38 26 16
Q 48 29 20 12
H 34 20 14 8
3 29x29 L 127 77 53 32
M 101 61 42 26
Q 77 47 32 20
H 58 35 24 15
4 33x33 L 187 114 78 48
M 149 90 62 38
Q 111 67 46 28
H 82 50 34 21
10 57x57 L 652 395 271 167
M 513 311 213 131
Q 364 221 151 93
H 288 174 119 74
40 177x177 L 7,089 4,296 2,953 1,817
M 5,596 3,391 2,331 1,435
Q 3,993 2,420 1,663 1,024
H 3,057 1,852 1,273 784

Further information and standards

QR code is trademarked by Denso Wave, Inc. As you'd expect, the Denso Wave website includes a lot of useful information about QR codes.

QR code was approved as:

  • AIM International (Automatic Identification Manufacturers International) standard (ISS - QR Code) in October 1997.
  • JEIDA (Japanese Electronic Industry Development Association) standard (JEIDA-55) in March 1998.
  • JIS (Japanese Industrial Standards) standard (JIS X 0510) in January 1999.
  • ISO international standard (ISO/IEC18004) in June 2000.

QR code reader software is available from many sources. Google offers a QR Code reader library, Zebra Crossing (ZXing), for free. See http://code.google.com/p/zxing/ for details.

See Barcode Contents for a rough guide to standard encoding of information in barcodes.

Posted by 1010
01.JAVA/Java2008. 12. 22. 15:15
반응형
-Chaos Graph API Ver 1.2

이 컨포넌트는 그래프 차트를 그려주는 API입니다.
구성은 라인그래프, 막대그래프, 영역그래프, 원그래프로 버전 1.0과 같이 4가지 입니다.
버전 1.0과 크게 달라진 부분은 이미지 객체에 관련된 부분인데 이전 버전에서는 
기 생성된 이미지 객체를 인자로 받아서 그 이미지 객체에 그래프를 도시 하였다면 이번 버전에서는
컴포넌트 내부에서 이미지를 생성하고 관리하도록 수정된 부분 입니다. 또한 그래프 이미지를 패널에 
도시해 주는 그래프 패널 클래스와 이미지를 파일로 저장하는 등의 작업을 수행하는 그래프 유틸리티 클래스, 
각종 오류를 처리하는 익셉션 클래스 등이 새로 추가 되었습니다. 
그리고 그래프를 resize(사이즈 재조정)하고 여백을 조정하는 등의 메서드가 추가되었습니다.

이번 버전에서는 예제 프로그램이 두개가 제공됩니다.
하나는 "그래프 뷰어 및 파일저장" 프로그램으로 4가지 종류의 그래프를 패널에 도시하고
도시된 이미지를 파일로 저장시켜주는 프로그램 입니다. (구체적인 내용은 Example1.java을 참조)
다른 하나는 "JVM 메모리 뷰어"로 실시간으로 자바 버추얼 머신의 메모리 정보를 동적인 그래프로
도시하여 주는 프로그램입니다.(구체적인 내용은 Example2.java를 참조)

컨포넌트를 다운로드 받은 후 약간의 설정으로 예제 프로그램(Example1.bat, Example2.bat)을
실행시켜 볼 수 있습니다. 현재는 윈도우용 배치파일만 만들어 놨습니다. 
컴포넌트와 예제는 순수 자바언어로 개발되었기 때문에 자바가 설치된 시스템 이라면 윈도우, 리눅스, 솔라리스 등 어디에서든 실행 가능합니다. 
제가 윈도우에서만 개발하고 테스트 했기 때문에 다른 시스템에서의 테스트는 해보지 못했습니다.
윈도우 외의 시스템에서 실행할 수 있는 환경을 갖추신 분이 있다면 테스트 후에 좀 알려 주셨으면 합니다.

마지막으로 이 컨포넌트를 이용하여 개발하는 것을 누구에게나 자유입니다. 
소스 패키지에 포함되어 있습니다. 공유와 복제는 창조의 어머니 입니다.
하지만 이 컨포넌트 자체를 상업적으로 이용할 수는 없습니다.
원본 수정이나 오류사항 chaos93@hanmail.net으로 연락주십시오.



-설치방법

1. ChaosGraph.zip을 다운로드 받는다.

2. 임의의 디렉토리(C:\ChaosGraph)에 압축을 풉니다.

3. 그러면 C:\ChaosGraph하부에 doc, jar, lib, src 디렉토리가 생깁니다.

4. doc은 API 도큐먼트가 있고, jar에는 이 컨포넌트 패키지인 ChaosGraph.jar가 있습니다. 
   lib에는 jai_codec.jar와 jai_core.jar가(JAI : Java Advanced Imaging Component) 위치하며 
   src에는 소스파일이 위치하게 됩니다.

5. ChaosGraph를 사용하려면 일단 ChasoGraph.jar, jai_codec.jar, jai_core.jar를 classpath에 
   설정하시고 코딩하시면 됩니다.

6. 예제를 실행하려면 Example1.bat, Example2.bat 파일을 열어서 "JAVA_HOME"속성에서
   JDK가 설치된 디렉토리를 설정한 후 배치파일을 실행시키면 됩니다.



-구조

C:\ChaosGraph  ----------  doc  -----  API Documentation (도큐먼트)
                    |
                    -----  jar  -----  ChaosGraph.jar (그래프 컨포넌트)
                    |
                    -----  lib  -----  jai_codec.jar, jai_core.jar (JAI 라이브러리)
                    |
                    -----  src  -----  Source Files (소스)
                    |
                    -----  Example1.bat, Example2.bat(예제 배치파일)
                    |
                    -----  Readme.txt(이 문서)
 




                                                                                                 Developed by 9ins.
첨부파일 :  
ChaosGraph.zip   [2044 KB]   다운로드 횟수 422 회
Posted by 1010
01.JAVA/Java2008. 12. 15. 15:09
반응형
  RMI 원격 서비스(서버측 서비스)만들기

 1. 원격 인터페이스를 만든다

 : 원격 인터페이스(interface)는 쉽게말하면 서버와 클라이언트간의 규약을 만드는것이라 할 수 있다. 인터페이스를 만들어놓고, 서버측, 클라이언트측모두 이 인테페이스를 사용 하게 만든다면 효과적으로 메소드들을 연계시킬 수 있을 것이다.

<예제파일 : MyRemote.java>

1) java.rmi.Remote를 확장한 인터페이스를 만든다. 이때, Remote는 '표지' 인터페이스로서,  아무런 메소드도 가지고 있지 않다. 이 인터페이스를 확장해서 자신만의 인터페이스를 만든다

      


2) 서버측이 구현하고, 클라이언트측이 실행시킬 메소드를 만든다. 이때, 모든 메소드들이 RemoteException을 던지도록 선언해 주어야만 한다

      

* 여기서는 예제로 sayHello라는 메소드를 사용할것임을 알 수 있다.

2. 원격 인터페이스를 구현한 클래스를 만든다

: 서버측에서는 방금전에 만든 인터페이스를 확장한 클래스를 만들어야 하며, 이 클래스 내부에 우리가 서비스해줄 메소드가 들어있다.

<예제파일 : MyRemoteImpl.java>

1) 인터페이스와 메소드를 구현한다. 이때, 이 파일이 원격서비스 객체역할을 하려면 객체 자체에 원격으로 돌아가는 역할에 관한 내용을 포함하고 있어야 하며, 이를위해 Unicast-RemoteObject를 확장하게 된다.

* UnicastRemoteObject는 Java.rmi.server패키지 내부에 있다.
   
2) RemoteException을 선언하는 생성자를 만들어준다. 이는 이 클래스의 상위클래스인 Unicast-Remote의 생성자에서 Exception을 발생시키기 때문이며, MyRemoteImpl은 이를 구현한 클래스이므로 역시 생성자에서 동일한 작업을 실시해 주어야 한다.
   

3) 서비스를 RMI레지스트리에 등록 합니다. 반드시 서비스가 RMI레지스트리에 등록되어야 하는데 이를 위해서 java.rmi.Naming 클래스의 rebind() 메소드를 이용한다. 아래 그림은 서비스에 Remote_Hello라는 이름을 붙여주고, 이를 RMI레지스트리에 등록하는 작업을 나타낸다.

   

 
 
3. 두개의 파일을 컴파일하여 .class파일을 생성한다. 컴파일 과정은 생략하며 이번 예제에서는 패키지를 사용하지 않았다.
   

4. rmic를 이용해 stub, skelecton을 만든다

: 이제 서비스 보조객체인 스터브와 스켈렉톤을 생성한다. 이때 생성되는 스터브와 스켈렉톤은  MyRemoteImpl에 관한 것이므로 모든 작업은 클래스 파일을 접근할 수 있는 위치에서 이루어 져야 한다.


1) 명령행에서 MyRemoteImpl이 있는 폴더로 이동한다

   


2) rmic명령어를 실행시켜 스터브와 스켈렉톤을 만든다   

  

 
3) 만들어진 스터브와 스켈렉톤을 확인한다

   

5. RMI레지스트리를 시작한다
1) rmiregistry명령어를 이용해 RMI레지스트리를 실행시킨다.
   
* RMI레지스트리를 실행시키면 대기상태처럼 커서가 계속 깜박거리고있다.
   혹시, 아무런 반응이 없다고 끄지 마시길 바란다..
 
 
6. 원격 서비스를 시작한다
1) 다른창을 열고 평소처럼 java명령어를 이용해 원격 서비스를 실행한다

   

* 이때도 역시, 실행시키면 커서가 계속 깜박거리고있다. 절대로 끄면 안된다..

7. 주의사항

: 여기서 우리는 엄청난 사실을 발견하게 된다. 4-2)rmic실행시 stub는 생성되었으나 skelecton은 생성되지 않은것이다. 왜일까... rmic명령어에 관한걸 좀더 알아보기 위해서 명령어창에 rmic 만을 쳐보자. 그러면 아래와 같은 도움말 창이 뜨게된다.

1) 1.2버전으로 오면서 default값이 stub만 생성하는것으로 바뀐것을 알 수 있다. 즉, 1.2버전에서 skelecton은 필요가 없어졌다.

2) -v1.1옵션을 사용하면 1.1버전의 stub/skelecton을 만들 수 있는것을 알 수 있다.

3) -vcompat옵션을 사용하여 1.2에서 1.1호환버전의 skelecton을 생성할 수 있음을 알 수 있다.

 

Posted by 1010
01.JAVA/Java2008. 12. 15. 15:08
반응형

RMI(Remote Method Invocation)

 다형성 기본 개념 이해하고 있어야한다..

 다형성은 항상 상위(Super) 클래스의 멤버필드 사용할수 있다..

  (단 메서드는 상위 클래스의 메서드를 사용하나 재정의시에는 상속받은 클래스의 메서드를 사용한다..)

UnicastRemoteObject 같은 클래스에 이미 상속해 두었다..

 

가.원격 인터페이스 작성

1. 서버와 클라이언트에서 동시에 사용할 원격(Remote) 인터페이스를 생성한다...

 인터페이스 작성규칙..

  조건1. java.rmi.Remote상속받아야한다..

          -  이 인터페이스에 있는 메서드는 로컬이 아닌 원격에서 사용할수 있어야 한다는 것을 명시하기 위해서이다..

  조건2. 무조건 public 이어야한다..

          - 원격은 클래스 내부에서의 접근만 허용하는 private 지정도 안될 것이고 상속의 관계에서 사용하는 protected지정도

            안될것 이고 동일 package에서의 호출만 가능한 package지정도 안될 것이기 때문이다..

  조건3. 메서드는 무조건 RemoteException 처리를 하여야한다..(예외발생처리)

 

    ex)  import  java.rmi.*;

       public interface Ex01 extends Remote {

          public String Message() throws RemoteException//인터넷상의 예외를 위해 반드시 처리..

             //특정 메세지를 전송하기 위한 메서드..

          public void printData() thorws RemoteException;

            // 호출된 시간을 서버에 출력하는 메서드..


2. 서버에 바인딩될 클래스의 멤버 메서드를 재정의 한다..(클래스가 실제 요청이 있을경우 실행하는 부분..)

- 서버에서는 이 객체가 바인딩되어 있어야하고 클라이언트는 이 객체를 통해 서버의 내용을 실행할수 있다..

   이 클래스는 외부 요청을 받기 위해 자신의 객체를 바인딩하기 때문에 직렬화가 되어있어야 하고 이전 단계에서 작성한

   원격인터페이스를 구현해야한다..


나. 인터페이스를 구현한 실제 서버 클래스 작성

이 클래스는 인터페이스를 구현할 목적으로 만든 클래스로 원격으로 객체를 복사해서 통신할 수 있는 형태를 표현하기 위해

미리 만들어진 UnicastRemoteObject와 같은 계열의 클래스를 상속받아야 한다. 실제 서버 행동의 주체(동작되는)가 되는 클

래스이기도 하다..

 구현 클래스의 규칙

  조건1 : 무조건 public이어야 한다.

  조건2 : 앞에서 만든 인터페이스를 상속받아 구현해 주어야한다.

             (인터페이스를 상속받았으니 메서드를 재정의하는것은 당연..)

  조건3 : UnicastRemoteObject 계열의 통신 클래스를 상속받아야한다.

              (해당 통신의 하부 구조를 일부 구현해주고 객체의 직렬화를 만들어준다..)

  조건4 : 이 클래스의 디폴트 생성자는 반드시 정의해야 한다. 또한 그 생성자는 RemoteException 처리.

              (UnicastRemoteObject생성자에서 해당 예외를 발생시키기 때문)

ex)

import java.rmi.*;
import java.rmi.server.*;
import java.util.*;

public class Ex02 extends UnicastRemoteObject implements Ex01 {
  public Ex02() throws RemoteException {
   super();
  }

  public String Message() throws RemoteException {
   printDate();
   return "안녕하세요! 반갑습니다.";
  }

  public void printDate() throws RemoteException {
   Date d = new Date();
   System.out.println("Connect Date = " + d.toString());
  }
}

 

다. 객체를 바인딩하는 Run 클래스 작성

 이 클래스는 객체인 Ex02를 RMIRestry라는 데몬 서버에 바인딩시키는 역할을 한다. 여기에서 객체를 바인딩시키는

메서드는 Naming 클래스의 rebind()라는 매서드이다.. 객체의 이름은 임의로 apple로 정한다..

 

import java.rmi.*;
import java.net.*;

public class Ex03 {
 public static void main(String[] args) {
  try {
   Ex02 sr = new Ex02();
   System.out.println("Server Ready...");
   Naming.rebind("apple", sr);
  } catch (RemoteException ee) {
   ee.printStackTrace();
  } catch (MalformedURLException ee) {
   ee.printStackTrace();
}
 }
}

 

라. 클라이언트가 객체를 찾아 호출하는 클래스작성

클라이언트가 유일하게 작성하는 클래스를 서버의 객체를 복사해서 가지고 와서 사용할수 있도록한다.

이것의 실행조건은  당연히 1번에서 작성한 원격인터페이스가 동일한 폴더에 있어야 한다는 것이다.

그것을 통해 다형성의 개념으로 실행할 수 있기 때문이다. 객체를 찾는 방법으로 사용된 클래스는

Naming이라는 클래스의 static메서드인 lookup()이다.

 

import java.net.*;
import java.rmi.*;

public class Ex04 {
 public static void main(String[] args) {
    try {
     Ex01 fr = (Ex01) Naming.lookup("rmi://원격시키고자 하는 서버 ip주소값/apple"); // 다형성...
     String msg = fr.Message();
     System.out.println("Message = " + msg);
    } catch (RemoteException ee) {
     ee.printStackTrace();
    } catch (MalformedURLException ee) {
     ee.printStackTrace();
    } catch (NotBoundException ee) {
     ee.printStackTrace();
    }
    }
}

< RMI 실행방법 (Dos화면에서)>

  1. 네 개의 파일을 모두 컴파일한다..(시작->실행->cmd)

   -> 관련파일경로 > java -d . *.java

 

  2. 두번째 작성한 실제 서버 클래스를 RMI컴파일을 통해 스텁클래스(파일명_Stub.class)를 생성..

   ( 스텁클래스는 내부 통신의 통로 역할을 하게 된다..)

   ->관련파일경로 > rmic -d . Ex02


 3.자바폴더의 bin폴더안에 rmiregistry.exe 파일을 실행하여 RMI서버를 가동한다.(새창이 생성됨)

   ( 보안경고창이 뜨면 차단해제 클릭~!) - 서버를 실행할수 있는 상태를 만든다..

 

 4. 생성된 새창은 실행상태로 두고 기존에 컴파일시켰던 도스창으로 돌아와 세번쨰 예제인 서버 실행 클래스를

   실행시킨다. 이것으로 RMI 서버에 객체를 바인딩할수 있다. - 서버실행상태

  ->관련파일경로 > java Ex03

    (실행하면 "Server Ready..."라는 메세지가 뜬다.)


 5. 마지막으로 또 하나의 Dos창을 열어 네번째 작성된 클래스를 통해 실행을 확인한다. 네번째 클래스는 다른 컴퓨터에

  있어도 되지만 첫번째 예제로 작성된 Remote를 상속받은 interface와 함께 있어야 한다.
  ->
관련파일경로 > java Ex04

  (실행시 첫번째 도스창에 Date(날짜)값이 출력되고  입력창에는 "안녕하세요!반갑습니다"라는 메세지가 뜬다.)


출처 : http://shizuku.tistory.com/category/☆%20IT/JAVA

Posted by 1010
01.JAVA/Java2008. 12. 15. 15:08
반응형

[Calculator.java]

import java.rmi.*;

public interface Calculator extends Remote
{
 public int sum(int num1, int num2) throws RemoteException;
 public int sub(int num1, int num2) throws RemoteException;
 public int multy(int num1, int num2) throws RemoteException;
 public int division(int num1, int num2) throws RemoteException;
}



[CalculatorImpl.java]

import java.rmi.server.*;
import java.rmi.*;

public class CalculatorImpl extends UnicastRemoteObject implements Calculator
{
 public CalculatorImpl() throws RemoteException{

 }

  public int sum(int num1, int num2) {
   return num1+num2;
  }

  public int sub(int num1, int num2) {
   return num1-num2;
  }
 
  public int multy(int num1, int num2) {
   return num1*num2;
  }
 
  public int division(int num1, int num2) {
   return num1/num2;
  }

}


[Server.java]

import java.rmi.*;

public class Server
 {
  public static void main(String[] args) throws Exception
   {
    CalculatorImpl c = new CalculatorImpl();
    Naming.rebind("Calculator",c);
    System.out.println("server ready...");
   }
 }


[Client]

 public static void main(String[] args) throws Exception
 {
  Calculator c = (Calculator)Naming.lookup("Calculator");
  int num1 = Integer.parseInt(args[0]);
  int num2 = Integer.parseInt(args[1]);

  int result1 = 0, result2 = 0, result3 = 0, result4 = 0;
  result1 = c.sum(num1, num2);
  result2 = c.sub(num1, num2);
  result3 = c.multy(num1, num2);
  result4 = c.division(num1, num2);

  System.out.println("sum : " + result1);
  System.out.println("sub : " + result2);
  System.out.println("multy : " + result3);
  System.out.println("division : " + result4);
 }
}



출처 : http://shizuku.tistory.com/category/☆%20IT/JAVA




Posted by 1010
01.JAVA/Java2008. 12. 4. 12:31
반응형
1. 필요 Lib
 3가지가 필요합니다.

 : commons-email.jar
 : mail.jar
 : activation.jar


2. 아래의 링크에서 다운 받으실수 있어요.


Commons-Email을 사용하기 위해서는 JavaMail의 mail.jar와 JAF의 activation.jar 가 필요합니다

Commons-Email

http://jakarta.apache.org/site/downloads/downloads_commons-email.cgi

JavaMail 1.3

http://java.sun.com/products/javamail/downloads/index.html

JAF 1.0.2

http://java.sun.com/products/javabeans/glasgow/jaf.html




3. 예제

Ex1> SimpleEmail

SimpleEmail email = new SimpleEmail();
email.setHostName("mail.nate.com");       //ex) nate.com 일 경우!! ->> mail.nate.com
email.addTo(onamt@nate.com, "받는사람");    //ex) onamt@nate.com
email.setFrom("onamt@nate.com", "보내는사람");
email.setSubject("제목을 입력합니다");
email.setMsg("메일의 내용을 입력합니다.");
email.send();


위의 예제는 아주 간단한 예제로 SimpleEmail입니다.

받는사람, 보내는사람, 호스트, 제목, 내용만을 보낼수 있습니다.



Ex2> 파일 첨부 메일



//첨부하실 파일의 정보를 넣습니다.
EmailAttachment attachment = new EmailAttachment();
attachment.setPath("mypictures/john.jpg");      //파일의 경로
attachment.setDisposition(EmailAttachment.ATTACHMENT);      //파일의 형태
attachment.setDescription("Picture of John");      //파일의 성명
attachment.setName("John");       // 첨부파일명


//그 후 MultiPartEmail 을 통해 SimpleEmail 처럼 기본 메일정보를 설정합니다
MultiPartEmailemail = new MultiPartEmail();
email.setHostName("mail.nate.com");
email.addTo("onamt@nate.com", "받는사람");
email.setFrom("onamt@nate.com", "보내는사람");
email.setSubject("제목");
email.setMsg("내용");


// 마지막으로 MultiPartEmail의 attach() 함수를 통해 첨부 파일을 추가하여 전송합니다
email.attach(attachment);

// 메일을 전송합니다
email.send();


만약 첨부파일이 여러개라면 EmailAttachment 를 여러개 생성하여 파일 정보를 설정 한 후 attach()를 통해 추가해 주기만 하면 됩니다

 

 

Ex3>HTML 이메일 보내기

// 기본 메일 정보를 생성합니다
HtmlEmailemail = new HtmlEmail();
email.setHostName("mail.nate.com");
email.addTo("onamt@nate.com", "받는사람");
email.setFrom("onamt@nate.com", "보내는사람");
email.setSubject("제목");


// 삽입할 이미지와 그 Content Id를 설정합니다
URL url = new URL("http://www.이미지 경로");

String contentId = email.embed(url, "Apache logo");


// HTML 메세지를 설정합니다

email.setHtmlMsg("<html>뭐뭐뭐뭐뭐뭐뭐</html>");


// HTML 이메일을 지원하지 않는 사람이면 다음 메세지를 뿌려웁니다
email.setTextMsg(" HTML 이메일을 지원하지 않습니다..ㅋㅋ");

// 메일을 전송합니다
email.send();

HtmlEmail 클래스는 setHtmlMsg()로 작성된 html을 보낼 수 있습니다

Posted by 1010
01.JAVA/Java2008. 12. 1. 14:17
반응형
JVM GC와 메모리 튜닝




자바스터디 네트워크 [www.javastudy.co.kr]

조대협 [bcho_N_O_SPAM@j2eestudy.co.kr]




모든 Java Application은 JVM(Java Virtual Machine)위에서 동작한다.
이 JVM이 동작하는데 있어서, 메모리의 구조와 특히 GC는 Application의 응답시간과 성능에 밀접한 관계를 미친다. 이번 강좌에서는 JVM 의 메모리 구조와 GC 알고리즘 (JDK 1.4.X에 포함된 새로운 알고리즘 포함) 그리고, JVM의 메모리 튜닝을 통한 Application의 성능향상방법에 대해서 알아보도록 하자.


1.GC란 무엇인가?


GC는 Garbage Collection의 약자로 Java 언어의 중요한 특징중의 하나이다.
GC는 Java Application에서 사용하지 않는 메모리를 자동으로 수거하는 기능을 말한다.
예전의 전통적인 언어 C등의 경우 malloc, free등을 이용해서 메모리를 할당하고, 일일이 그 메모리를 수거해줘야했다. 그러나 Java 언어에서는 GC 기술을 사용함에 따라서 개발자로 하여금 메모리 관리에서 부터 좀더 자유롭게 해주었다.


2.GC의 동작 방법은 어떻게 되는가?


1) JVM 메모리 영역

GC의 동작 방법을 이해하기 위해서는 Java의 메모리 구조를 먼저 이해할 필요가 있다.
일반적으로 Application에서 사용되는 객체는 오래 유지 되는 객체보다, 생성되고 얼마안있어서 사용되지 않는 경우가 많다. <그림 1 참조>


<그림 1. 메모리 foot print>


그래서 Java에서는 크게 두가지 영역으로 메모리를 나누는데 Young 영역과 Old 영역이 그것이다.
Young 영역은 생긴지 얼마 안된 객체들을 저장하는 장소이고, Old영역은 생성된지 오래된 객체를 저장하는 장소이다. 각 영역의 성격이 다른 만큼 GC의 방법도 다르다.
먼저 Java의 메모리 구조를 살펴보자.


<그림 2. Java 메모리 구조>


Java의 메모리 영역은 앞에서 이야기한 두 영역 (Young 영역,Old 영역)과 Perm 영역 이렇게 3가지로 영역으로 구성된다.


<표 1. Java 메모리 영역>



2) GC 알고리즘

그러면 이 메모리 영역을 JVM이 어떻게 관리하는지에 대해서 알아보자.
JVM은 New/Young 영역과, Old영역 이 두영역에 대해서만 GC를 수행한다. Perm영역은 앞에서 설명했듯이 Code가 올라가는 부분이기 때문에, GC가 일어날 필요가 없다. Perm영역은 Code가 모두 Load되고 나면 거의 일정한 수치를 유지한다.


○ Minor GC
먼저 New/Young영역의 GC방법을 살펴보자 New/Young 영역의 GC를 Minor GC라고 부르는데, New/Young영역은 Eden과 Survivor라는 두가지 영역으로 또 나뉘어 진다. Eden영역은 Java 객체가 생성되자 마자 저장이 되는곳이다. 이렇게 생성된 객체는 Minor GC가 발생할때 Survivor 영역으로 이동된다.

Survivor 영역은 Survivor 1과 Suvivor2 영역 두 영역으로 나뉘어 지는데, Minor GC가 발생하면 Eden과 Survivor1에 Alive되어 있는 객체를 Suvivor2로 복사한다. 그리고 Alive되어 있지 않는 객체는 자연히 Suvivor1에 남아있게 되고, Survivor1과 Eden영역을 Clear한다. (결과적으로 Alive된 객체만 Survivor2로 이동한것이다.)
다음번 Minor GC가 발생하면 같은 원리로 Eden과 Survivor2영역에서 Alive되어 있는 객체를 Survivor1에 복사한다. 계속 이런 방법을 반복적으로 수행하면서 Minor GC를 수행한다.

이렇게 Minor GC를 수행하다가, Survivor영역에서 오래된 객체는 Old영역으로 옮기게 된다.

이런 방식의 GC 알고리즘을 Copy & Scavenge라고 한다. 이 방법은 매우 속도가 빠르며 작은 크기의 메모리를 Collecting하는데 매우 효과적이다. Minor GC의 경우에는 자주 일어나기 때문에, GC에 소요되는 시간이 짧은 알고리즘이 적합하다.

이 내용을 그림을 보면서 살펴보도록 하자.


<그림 3-1. 1st Minor GC>


Eden에서 Alive된 객체를 Suvivor1으로 이동한다. Eden 영역을 Clear한다.


<그림 3-2. 2nd Minor GC>


Eden영역에 Alive된 객체와 Suvivor1영역에 Alive된 객체를 Survivor 2에 copy한다.
Eden영역과 Suvivor2영역을 clear한다.


<그림 3-3. 3rd Minor GC>


객체가 생성된 시간이 오래지나면 Eden과 Suvivor영역에 있는 오래된 객체들을 Old 영역으로 이동한다.


○ Full GC

Old 영역의 Garbage Collection을 Full GC라고 부르며, Full GC에 사용되는 알고리즘은 Mark & Compact라는 알고리즘을 이용한다. Mark & Compact 알고리즘은 전체 객체들의 reference를 쭉 따라가다면서 reference가 연결되지 않는 객체를 Mark한다. 이 작업이 끝나면 사용되지 않는 객체를 모두 Mark가 되고, 이 mark된 객체를 삭제한다.<그림 4 참고> (실제로는 compact라고 해서, mark된 객체로 생기는 부분을 unmark된 즉 사용하는 객체로 메꾸어 버리는 방법이다.)

Full GC는 매우 속도가 느리며, Full GC가 일어나는 도중에는 순간적으로 Java Application이 멈춰 버리기 때문에, Full GC가 일어나는 정도와 Full GC에 소요되는 시간은 Application의 성능과 안정성에 아주 큰 영향을 준다.


<그림 4. Full GC>




3. GC가 왜 중요한가?


Garbage Collection중에서 Minor GC의 경우 보통 0.5초 이내에 끝나기 때문에 큰문제가 되지 않는다. 그러나 Full GC의 경우 보통 수초가 소요가 되고, Full GC동안에는 Java Application이 멈춰버리기 때문에 문제가 될 수 있다.
예를 들어 게임 서버와 같은 Real Time Server를 구현을 했을때, Full GC가 일어나서 5초동안 시스템이 멈춘다고 생각해보자.
또 일반 WAS에서도 5~10초동안 멈추면, 멈추는동안의 사용자의 Request가 Queue에 저장되었다가 Full GC가 끝난후에 그 요청이 한꺼번에 들어오게 되면 과부하에 의한 여러 장애를 만들 수 있다..
그래서 원할한 서비스를 위해서는 GC를 어떻게 일어나게 하느냐가 시스템의 안정성과 성능에 큰 변수로 작용할 수 있다.


4. 다양한 GC 알고리즘


앞에서 설명한 기본적인 GC방법 (Scavenge 와 Mark and compact)이외에 JVM에서는 좀더 다양한 GC 방법을 제공하고 그 동작방법이나 사용방법도 틀리다. 이번에는 다양한 GC 알고리즘에 대해서 알아보자. 현재 (JDK 1.4)까지 나와 있는 JVM의 GC방법은 크게 아래 4가지를 지원하고 있다.

- Default Collector
- Parallel GC for young generation (from JDK 1.4 )
- Concurrent GC for old generation (from JDK 1.4)
- Incremental GC (Train GC)

1) Default Collector
이 GC 방법은 앞에서 설명한 전통적인 GC방법으로 Minor GC에 Scavenge를, Full GC에 Mark & compact 알고리즘을 사용하는 방법이다. 이 알고리즘에는 이미 앞에서 설명했기 때문에 별도의 설명을 하지는 않는다.

JDK 1.4에서부터 새로 적용되는 GC방법은 Parallel GC와 Concurrent GC 두가지 방법이 있다. Parallel GC는 Minor GC를 좀더 빨리하게 하는 방법이고 (Throughput 위주) Concurrent GC는 Full GC시에 시스템의 멈춤(Pause)현상을 최소화하는 GC방법이다.

2) Parallel GC
JDK1.3까지 GC는 하나의 Thread에서 이루어진다. Java가 Multi Thread환경을 지원함에도 불구하고, 1 CPU에서는 동시에 하나의 Thread만을 수행할 수 밖에 없기때문에, 예전에는 하나의 CPU에서만 GC를 수행했지만, 근래에 들어서 하나의 CPU에서 동시에 여러개의 Thread를 실행할 수 있는 Hyper Threading기술이나, 여러개의 CPU를 동시에 장착한 HW의 보급으로 하나의 HW Box에서 동시에 여러개의 Thread를 수행할 수 있게 되었다.

JDK 1.4부터 지원되는 Parallel GC는 Minor GC를 동시에 여러개의 Thread를 이용해서 GC를 수행하는 방법으로 하나의 Thread를 이용하는것보다 훨씬 빨리 GC를 수행할 수 있다.


<그림 7. Parallel GC 개념도>


<그림 7> 을 보자 왼쪽의 Default GC방법은 GC가 일어날때 Thread들이 작업을 멈추고, GC를 수행하는 thread만 gc를 수행한다. (그림에서 파란영역), Parallel GC에서는 여러 thread들이 gc를 수행이 가능하기 때문에, gc에 소요되는 시간이 낮아진다.

Parallel GC가 언제나 유익한것은 아니다. 앞에서도 말했듯이 1CPU에서는 동시에 여러개의 thread를 실행할 수 없기 때문에 오히혀 Parallel GC가 Default GC에 비해서 느리다. 2 CPU에서도 Multi thread에 대한 지원이나 계산등을 위해서 CPU Power가 사용되기 때문에, 최소한 4CPU의 256M 정도의 메모리를 가지고 있는 HW에서 Parallel GC가 유용하게 사용된다.

Parallel GC는 크게 두가지 종류의 옵션을 가지고 있는데,Low-pause 방식과 Throughput 방식의 GC방식이 있다.

Solaris 기준에서 Low-pause Parallel GC는 ?XX:+UseParNewGC 옵션을 사용한다. 이 모델은 Old 영역을 GC할때 다음에 설명할 Concurrent GC방법과 함께 사용할 수 있다. 이 방법은 GC가 일어날때 빨리 GC하는것이 아니라 GC가 발생할때 Application이 멈춰지는 현상(pause)를 최소화하는데 역점을 뒀다.

Throughput 방식의 Parallel GC는 ?XX:+UseParallelGC (Solaris 기준) 옵션을 이용하며 Old 영역을 GC할때는 Default GC (Mark and compact)방법만을 사용하도록 되어 있다.Minor GC가 발생했을때, 되도록이면 빨리 수행하도록 throughput에 역점을 두었다.

그외에도 ParallelGC를 수행할때 동시에 몇개의 Thread를 이용하여 Minor영역을 Parallel GC할지를 결정할 수 있는데, -XX:ParallelGCThreads= 옵션을 이용하여 Parallel GC에 사용되는 Thread의 수를 지정할 수 있다.

3) Concurrent GC

앞에서도 설명했듯이, Full GC즉 Old 영역을 GC하는 경우에는 그 시간이 길고 Application이 순간적으로 멈춰버리기 때문에, 시스템 운용에 문제가 된다.

그래서 JDK1.4부터 제공하는 Concurrent GC는 기존의 이런 Full GC의 단점을 보완하기 위해서 Full GC에 의해서 Application이 멈추어 지는 현상을 최소화 하기 위한 GC방법이다.
Full GC에 소요되는 작업을 Application을 멈추고 진행하는것이 아니라, 일부는 Application이 돌아가는 단계에서 수행하고, 최소한의 작업만을 Application이 멈췄을때 수행하는 방법으로 Application이 멈추는 시간을 최소화한다.


<그림 8. Concurrent GC 개념도>


그림 8에서와 같이 Application이 수행중일때(붉은 라인) Full GC를 위한 작업을 수행한다. (Sweep,mark) Application을 멈추고 수행하는 작업은 일부분 (initial-mark, remark 작업)만을 수행하기 때문에, 기존 Default GC의 Mark & Sweep Collector에 비해서 Application이 멈추는 시간이 현저하게 줄어든다.

Solaris JVM에서는 -XX:+UseConcMarkSweepGC Parameter를 이용해 세팅한다.

4) Incremental GC (Train GC)

Incremental GC또는 Train GC라고도 불리는 GC방법은 JDK 1.3에서부터 지원된 GC방법이다. 앞에서 설명한 Concurrent GC와 비슷하게, 의도 자체는 Full GC에 의해서 Application이 멈추는 시간을 줄이고자 하는데 있다.

Incremental GC의 작동방법은 간단하다. Minor GC가 일어날때 마다 Old영역을 조금씩 GC를 해서 Full GC가 발생하는 횟수나 시간을 줄이는 방법이다.


<그림 9. Incremental GC 개념도>


그림 9에서 보듯이. 왼쪽의 Default GC는 FullGC가 일어난후에나 Old 영역이 Clear된다. 그러나, 오른쪽의 Incremental GC를 보면 Minor GC가 일어난후에, Old 영역이 일부 Collect된것을 볼 수 있다.

Incremental GC를 사용하는 방법은 JVM 옵션에 ?Xinc 옵션을 사용하면 된다.
Incremental GC는 많은 자원을 소모하고, Minor GC를 자주일으키고, 그리고 Incremental GC를 사용한다고 Full GC가 없어지거나 그 횟수가 획기적으로 줄어드는 것은 아니다. 오히려 느려지는 경우가 많다. 필히 테스트 후에 사용하도록 하자.

※ Default GC이외의 알고리즘은 Application의 형태나 HW Spec(CPU수, Hyper threading 지원 여부), 그리고 JVM 버전(JDK 1.4.1이냐 1.4.2냐)에 따라서 차이가 매우 크다. 이론상으로는 실제로 성능이 좋아보일 수 있으나, 운영환경에서는 여러 요인으로 인해서 기대했던것만큼의 성능이 안나올 수 있기 때문에, 실환경에서 미리 충분한 테스트를 거쳐서 검증한후에 사용해야 한다.


5. GC 로그는 어떻게 수집과 분석


JVM에서는 GC 상황에 대한 로그를 남기기 위해서 옵션을 제공하고 있다.
Java 옵션에 ?verbosegc 라는 옵션을 주면되고 HP Unix의 경우 ?verbosegc ?Xverbosegc 옵션을 주면 좀더 자세한 GC정보를 얻을 수 있다. GC 정보는 stdout으로 출력이 되기 때문에 “>” redirection등을 이용해서 file에 저장해놓고 분석할 수 있다.

Example ) java ?verbosegc MyApplication

그럼 실제로 나온 GC로그를 어떻게 보는지를 알아보자.


<그림 5. 일반적인 GC 로그, Windows, Solaris>


<그림 5>는 GC로그 결과를 모아논 내용이다. (실제로는 Application의 stdout으로 출력되는 내용과 섞여서 출력된다.)
Minor GC는 ”[GC “로 표기되고, Full GC는 “[Full GC”로 표기된다.
그 다음값은 Heap size before GC인데,GC 전에 Heap 사용량 ( New/Young 영역 + Old 영역 + Perm 영역)의 크기를 나타낸다.

Heap size after GC는 GC가 발생한후에 Heap의 사용량이다. Minor GC가 발생했을때는 Eden과 Survivor 영역으 GC가 됨으로 Heap size after GC는 Old영역의 용량과 유사하다.(Minor GC에서 GC되지 않은 하나의 Survivor영역내의 Object들의 크기도 포함해야한다.)

Total Heap Size는 현재 JVM이 사용하는 Heap Memory양이다. 이 크기는 Java에서 ?ms와 ?mx 옵션으로 조정이 가능한데. 예를 들어 ?ms512m ?mx1024m로 해놓으면 Java Heap은 메모리 사용량에 따라서 512~1024m사이의 크기에서 적절하게 늘었다 줄었다한다. (이 늘어나는 기준과 줄어드는 기준은 (-XX:MaxHeapFreeRatio와 ?XX:MinHeapFreeRation를 이용해서 조정할 수 있으나 JVM vendor에 따라서 차이가 나기때문에 각 vendor별 JVM 메뉴얼을 참고하기 바란다.) Parameter에 대한 이야기는 추후에 좀더 자세히하도록 하자.

그 다음값은 GC에 소요된 시간이다.

<그림 5>의 GC로그를 보면 Minor GC가 일어날때마다 약 20,000K 정도의 Collection이 일어난다. Minor GC는 Eden과 Suvivor영역 하나를 GC하는 것이기 때문에 New/Young 영역을 20,000Kbyte 정도로 생각할 수 있다.

Full GC때를 보면 약44,000Kbyte에서 1,749Kbyte로 GC가 되었음을 볼 수 있다. Old영역에 큰 데이타가 많지 않은 경우이다. Data를 많이 사용하는 Application의 경우 전체 Heap이 512이라고 가정할때, Full GC후에도 480M정도로 유지되는 경우가 있다. 이런 경우에는 실제로 Application에서 Memory를 많이 사용하고 있다고 판단할 수 있기 때문에 전체 Heap Size를 늘려줄 필요가 있다.

이렇게 수집된 GC로그는 다소 보기가 어렵기 때문에, 좀더 쉽게 분석할 수 있게 하기 위해서 GC로그를 awk 스크립트를 이용해서 정제하면 분석이 용이하다.


<표 2. gc.awk 스크립트>


이 스크립트를 작성한후에 Unix의 awk 명령을 이용해서

% awk ?f gc.awk GC로그파일명

을 쳐주면 아래<표 3>와 같이 정리된 형태로 GC 로그만 추출하여 보여준다.


<표 3. gc.awk 스크립트에 의해서 정재된 로그>


Minor와 Major는 각각 Minor GC와 Full GC가 일어날때 소요된 시간을 나타내며, Alive는 GC후에 남아있는 메모리양, 그리고 Freed는 GC에 의해서 collect된 메모리 양이다.

이 로그파일은 excel등을 이용하여 그래프등으로 변환해서 보면 좀더 다각적인 분석이 가능해진다.

※ JDK 1.4에서부터는 ?XX:+PrintGCDetails 옵션이 추가되어서 좀더 자세한 GC정보를 수집할 수 있다.


※ HP JVM의 GC Log 수집

HP JVM은 전체 heap 뿐 아니라 ?Xverbosegc 옵션을 통해서 Perm,Eden,Old등의 모든 영역에 대한 GC정보를 좀더 정확하게 수집할 수 있다.

Example ) java ?verbosegc ?Xverbosegc MyApplication ß (HP JVM Only)

HP JVM의 GC정보는 18개의 필드를 제공하는데 그 내용을 정리해보면 <표 4.>와 같다.

<GC : %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16 %17 %18>


<표 4. HP JVM GC 로그 필드별 의미>


이 로그를 직접 보면서 분석하기는 쉽지가 않다. 그래서, HP에서는 좀더 Visual한 환경에서 분석이 가능하도록 HPJtune이라는 툴을 제공한다. 다음 URL에서 다운로드 받을 수 있다.

http://www.hp.com/products1/unix/java/java2/hpjtune/index.html


<그림 6. HP Jtune을 이용해서 GC후 Old영역의 변화 추이를 모니터링하는 화면>




6. GC 관련 Parameter


GC관련 설정값을 보기전에 앞서서 ?X와 ?XX 옵션에 대해서 먼저 언급하자. 이 옵션들은 표준 옵션이 아니라, 벤더별 JVM에서 따로 제공하는 옵션이기 때문에, 예고 없이 변경되거나 없어질 수 있기 때문에, 사용전에 미리 JVM 벤더 홈페이지를 통해서 검증한다음에 사용해야한다.

1) 전체 Heap Size 조정 옵션

전체 Heap size는 ?ms와 ?mx로 Heap 사이즈의 영역을 조정할 수 있다. 예를 들어 ?ms512m ?mx 1024m로 설정하면 JVM은 전체 Heap size를 application의 상황에 따라서 512m~1024m byte 사이에서 사용하게 된다. 그림2의 Total heap size

메모리가 모자를때는 heap을 늘리고, 남을때는 heap을 줄이는 heap growing과 shirinking 작업을 수행하는데, 메모리 변화량이 큰 애플리케이션이 아니라면 이 min heap size와 max heap size는 동일하게 설정하는 것이 좋다. 일반적으로 1GB까지의 Heap을 설정하는데에는 문제가 없으나, 1GB가 넘는 대용량 메모리를 설정하고자 할 경우에는 별도의 JVM 옵션이 필요한 경우가 있기때문에 미리 자료를 참고할 필요가 있다.

※ IBM AIX JVM의 경우
%export LDR_CNTRL=MAXDATA=0x10000000
%java -Xms1500m -Xmx1500m MyApplication

2) Perm size 조정 옵션

Perm Size는 앞에서도 설명했듯이, Java Application 자체(Java class etc..)가 로딩되는 영역이다. J2EE application의 경우에는 application 자체의 크기가 큰 편에 속하기 때문에, Default로 설정된 Perm Size로는 application class가 loading되기에 모자른 경우가 대부분이기 때문에, WAS start초기나, 가동 초기에 Out Of Memory 에러를 유발하는 경우가 많다.

PermSize는 -XX:MaxPermSize=128m 식으로 지정할 수 있다.
일반적으로 WAS에서 PermSize는 64~256m 사이가 적절하다.

3) New 영역과 Old 영역의 조정New 영역은 ?XX:NewRatio=2 에 의해서 조정이 된다.
NewRatio Old/New Size의 값이다. 전체 Heap Size가 768일때, NewRatio=2이면 New영역이 256m, Old 영역이 512m 로 설정이 된다.
JVM 1.4.X에서는 ?XX:NewSize=128m 옵션을 이용해서 직접 New 영역의 크기를 지정하는 것이 가능하다.

4) Survivor 영역 조정 옵션
-XX:SurvivorRatio=64 (eden/survivor 의 비율) :64이면 eden 이 128m일때, survivor영역은 2m가 된다.

5) -server와 ?client 옵션
JVM에는 일반적으로 server와 client 두가지 옵션을 제공한다.
결론만 말하면 server 옵션은 WAS와 같은 Server환경에 최적화된 옵션이고, client옵션은 워드프로세서와 같은 client application에 최적화된 옵션이다. 그냥 언뜻 보기에는 단순한 옵션 하나로보일 수 있지만, 내부에서 돌아가는 hotspot compiler에 대한 최적화 방법과 메모리 구조자체가 아예 틀리다.

○ -server 옵션

server용 application에 최적화된 옵션이다. Server application은 boot up 시간 보다는 user에 대한 response time이 중요하고, 많은 사용자가 동시에 사용하기 때문에 session등의 user data를 다루는게 일반적이다. 그래서 server 옵션으로 제공되는 hotspot compiler는 java application을 최적화 해서 빠른 response time을 내는데 집중되어 있다.

또한 메모리 모델 역시, 서버의 경우에는 특정 사용자가 서버 운영시간동안 계속 서버를 사용하는게 아니기 때문에 (Login하고, 사용한 후에는 Logout되기 때문에..) 사용자에 관련된 객체들이 오래 지속되는 경우가 드물다. 그래서 상대적으로 Old영역이 작고 New 영역이 크게 배정된다. <그림 7. 참조 >

○ -client 옵션

client application은 워드프로세서 처럼 혼자 사용하는 application이다. 그래서 client application은 response time보다는 빨리 기동되는데에 최적화가 되어 있다. 또한대부분의 client application을 구성하는 object는GUI Component와 같이 application이 종료될때까지 남아있는 object의 비중이 높기 때문에 상대적으로 Old 영역의 비율이 높다.


<그림 7. ?server와 ?client 옵션에 따른 JVM Old와 New영역>


이 두옵션은 가장 간단한 옵션이지만, JVM의 최적화에 아주 큰부분을 차지하고 있는 옵션이기 때문에, 반드시 Application의 성격에 맞춰서 적용하기 바란다.
(※ 참고로, SUN JVM은 default가 client, HPJVM는 default가 server로 세팅되어 있다.)

○ GC 방식에 대한 옵션

GC 방식에 대한 옵션은 앞에서도 설명했지만, 일반적인 GC방식이외에, Concurrent GC,Parallel GC,Inceremental GC와 같이 추가적인 GC Algorithm이 존재한다. 옵션과 내용은 앞장에서 설명한 “다양한 GC알고리즘” 을 참고하기 바란다.


7.JVM GC 튜닝


그러면 이제부터 지금까지 설명한 내용을 기반으로 실제로 JVM 튜닝을 어떻게 하는지 알아보도록 하자.

STEP 1. Application의 종류와 튜닝목표값을 결정한다.

JVM 튜닝을 하기위해서 가장 중요한것은 JVM 튜닝의 목표를 설정하는것이다. 메모리를 적게 쓰는것이 목표인지, GC 횟수를 줄이는것이 목표인지, GC에 소요되는시간이 목표인지, Application의 성능(Throughput or response time) 향상인지를 먼저 정의한후에. 그 목표치에 근접하도록 JVM Parameter를 조정하는것이 필요하다.

STEP 2. Heap size와 Perm size를 설정한다.

-ms와 ?mx 옵션을 이용해서 Heap Size를 정한다. 일반적으로 server application인 경우에는 ms와 mx 사이즈를 같게 하는것이 Memory의 growing과 shrinking에 의한 불필요한 로드를 막을 수 있어서 권장할만하다.

ms와mx사이즈를 다르게 하는 경우는 Application의 시간대별 memory 사용량이 급격하게 변화가 있는 Application에 효과적이다.
PermSize는 JVM vendor에 따라 다소 차이가 있으나 일반적으로 16m정도이다. Client application의 경우에는 문제가 없을 수 있지만, J2EE Server Application의 경우 64~128m 사이로 사용이 된다.

Heap Size와 Perm Size는 아래 과정을 통해서 적정 수치를 얻어가야한다.

STEP 3. 테스트 & 로그 분석.

JVM Option에 GC 로그를 수집하기 위한 ?verbosegc 옵션을 적용한다. (HP의 경우 ?Xverbosegc 옵션을 적용한다.)

LoadRunner나 MS Strest(무료로 MS社의 홈페이지에서 다운로드 받을 수 있다.)와 같은 Strest Test툴을 통해서 Application에 Strest를 줘서. 그 log를 수집한다. 튜닝에서 있어서 가장 중요한것은 목표산정이지만, 그만큼이나 중요한것은 실제 Tuning한 Parameter가 Application에 어떤 영향을 주는지를 테스트하는 방법이 매우 중요하다. 그런 의미에서 적절한 Strest Tool의 선정과, Strest Test 시나리오는 정확한 Tuning을 위해서 매우 중요한 요인이다.

○ Perm size 조정
아래 그림8.은 HP JVM에서 ?Xverbosegc 옵션으로 수집한 GC log를 HP Jtune을 통해서 graph로 나타낸 그래프이다. 그림을 보면 Application이 startup되었을때 Perm 영역이 40m에서. 시간이 지난후에도 50m 이하로 유지되는것을 볼 수 있다. 특별하게 동적 classloading등이 수십m byte가 일어나지 않는등의 큰 변화요인이 없을때, 이 application의 적정 Perm 영역은 64m로 판단할 수 있다.


<그림 8. GC 결과중 Perm 영역 그래프>


○ GC Time 수행 시간 분석

다음은 GC에 걸린 시간을 분석해보자. 앞에 강좌 내용에서도 설명햇듯이. GC Tuning에서 중요한 부분중 하나가 GC에 소요되는 시간 특히 Full GC 시간이다.

지금부터 볼 Log는 모社의 물류 시스템의 WAS 시스템 GC Log이다. HP JVM을 사용하며, -server ?ms512m ?mx512m 옵션으로 기동되는 시스템이다.

그림 9를 보면 Peak 시간 (첫번째 동그라미) 14시간동안에 Full GC(동그란점)가 7번일어난것을 볼 수 있다. 각각에 걸린 시간은2.5~6sec 사이이다.
여기서 STEP 1.에서 설정한 AP Tuning의 목표치를 참고해야하는데.

Full GC가 길게 일어나서 Full GC에 수행되는 시간을 줄이고자 한다면 Old 영역을 줄이면 Full GC가 일어나는 횟수는 늘어나고, 반대로 Full GC가 일어나는 시간을 줄어들것이다.

반대로 Full GC가 일어나는 횟수가 많다면, Old 영역을 늘려주면 Full GC가 일어나는 횟수는 상대적으로 줄어들것이고 반대로 Full GC 수행시간이 늘어날 것이다.

특히 Server Application의 경우Full GC가 일어날때는 JVM자체가 멈춰버리기 때문에, 그림 9의 instance는 14시간동안 총 7번 시스템이 멈추고, 그때마다 2.5~6sec가량 시스템이 response를 못하는 상태가 된것이다. 그래서 멈춘 시간이 고객이 납득할만한 시간인지를 판단해야 하고, 거기에 적절한 Tuning을 해야한다.

Server Application에서 Full GC를 적게일어나게하고, Full GC 시간을 양쪽다 줄이기 위해서는 Old영역을 적게한후에, 여러개의 Instance를 동시에 뛰어서 Load Balancing을 해주면, Load가 분산되기 때문에 Full GC가 일어나는 횟수가 줄어들테고, Old 영역을 줄였기 때문에, Full GC에 드는 시간도 줄어들것이다. 또한 각각의 FullGC가 일어나는동안 하나의 서버 instance가 멈춰져 있어도, Load Balancing이 되는 다른 서버가 response를 하고 있기때문에, Full GC로 인한 Application이 멈추는것에 의한 영향을 최소화할 수 있다.


<그림 9. GC 소요시간>


데이타에 따라서 GC Tuning을 진행한후에는 다시 Strest Test를 진행해서 응답시간과 TPS(Throughput Per Second)를 체크해서 어떤 변화를 주었는지를 반드시 체크해봐야한다.


<그림 10. GC후의 Old 영역>


그림 10은 GC후에 Old 영역의 메모리 변화량을 나타낸다.

금요일 업무시간에 메모리 사용량이 올라가다가. 주말에가서 완만한 곡선을 그리는것을 볼 수 있다. 월요일 근무시간에 메모리 사용량이 매우 많고, 화요일에도 어느정도 메모리 사용량이 있는것을 볼 수 있다. 월요일에 메모리 사용량이 많은것을 볼때, 이 시스템의 사용자들이 월요일에 시스템 사용량이 많을 수 있다고 생각할 수 있고, 또는 다른 주의 로그를 분석해봤을때 이 주만 월요일 사용량이 많았다면, 특별한 요인이나 Application 변경등이 있었는지를 고려해봐야할것이다.

이 그래프만을 봤을때 Full GC가 일어난후에도 월요일 근무시간을 보면 Old 영역이 180M를 유지하고 있는것을 볼 수 있다. 이 시스템의 Full GC후의 Old영역은 80M~180M를 유지하는것을 볼 수 있다. 그래서 이 시스템은 최소 180M이상의 Old 영역을 필요로하는것으로 판단할 수 있다.

STEP 4. Parameter 변경.
STEP 3에서 구한 각 영역의 허용 범위를 기준으로 Old영역과 New 영역을 적절하게 조절한다.
PermSize와 New영역의 배분 (Eden,Survivor)영역등을 조정한다.
PermSize는 대부분 Log에서 명확하게 나타나기 때문에, 크게 조정이 필요가 없고 New영역내의 Eden과 Survivor는 거의 조정하지 않는다. 가장 중요한것은 Old영역과 New 영역의 비율을 어떻게 조정하는가가 관건이다.

이 비율을 결정하면서, STEP1에서 세운 튜닝 목표에 따라서 JVM의 GC Algorithm을 적용한다. GC Algorithm을 결정하는 기본적인 판단 내용은 아래와 같다.



이렇게 Parameter를 변경하면서 테스트를 진행하고, 다시 변경하고 테스트를 진행하는 과정을 거쳐서 최적의 Parameter와 GC Algorithm을 찾아내는것이 JVM의 메모리 튜닝의 이상적인 절차이다.


지금까지 JVM의 메모리 구조와 GC 모델 그리고 GC 튜닝에 대해서 알아보았다.

정리하자면 GC 튜닝은 Application의 구조나 성격 그리고, 사용자의 이용 Pattern에 따라서 크게 좌우 되기때문에, 얼마만큼의 Parameter를 많이 아느냐 보다는 얼마만큼의 테스트와 로그를 통해서 목표 값에 접근하느냐가 가장 중요하다.

 
Posted by 1010
01.JAVA/Java2008. 11. 28. 17:32
반응형

Extract All Classes Loaded in the JVM into a Single JAR

Today I needed to optimize the file size for a commercial Java applet (Web form signer) that my team is developing in the last month. We use few external libraries (Bouncy Castle Crypto API and few others) and these libraries are above 2 MB in several JAR files. If we were developing server side application, it would not be a problem, but when we are building an applet, the size of the applet and all its JARs matters.

I needed to remove all unused classes from the JARs that my applet was including a part of itself. For example the Bouncy Castle JARs were about 1,6 MB but the applet used only a small part of all algorithms and standards implemented by these JARs.

Extracting All Classes Loaded in the JVM

My final goal was not only to remove all unused classes from all JAR files but also merge these JARs along with the applet classes into a single JAR file that has the smallest possible size. I came with the idea to run the applet, to go through all its functionality and to get a list of all classes currently loaded into the JVM executing the applet. At this moment all classes required by the applet for its normal work will be loaded in the JVM and all classes that was never used by the applet will not be loaded in the JVM. If I package all these classes into a new JARs, it will contain the minimal set of classes nedded by the applet along with the applet classes.

As fas as I know how the JVM and the class loaders behave, this should be correct - we can expect all classes required by the applet to be loaded in the JVM after its entire functionality is accessed at least once.

I had a serious problem: how to get a list of all classes loaded in the JVM.

List All Classes Loaded in the JVM

Geting a list of all classes that are loaded in the JVM at some moment is not easy job. We can write Java agent through the java.lang.instrument API but I needed to do this at runtime (just to add few lines to the applet). I found in Google a very nice class for accessing all classes loaded in the JVM written by Vladimir Roubtsov and published in Java World (http://www.javaworld.com/javaworld/javaqa/2003-07/01-qa-0711-classsrc.html). With few modifications it successfully listed all classes loaded in my applet.

Create a Single JAR with All Classes Loaded in the JVM

The next step was to create a single JAR with all classes loaded in the JVM. This was not complex. I created a class with few methods for copying all currently loaded classes into some directory specified as parameter. Here is the source code:

  1. import java.io.File;  
  2. import java.io.FileOutputStream;  
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.io.OutputStream;  
  6. import java.net.URL;  
  7.  
  8. /***  
  9.  * This class extracts all classes loaded in the JVM and their binary contents  
  10.  * (.class files) into given directory so that you can create JAR archive later.  
  11.  * @author Svetlin Nakov - http://www.nakov.com  
  12.  */ 
  13. public class AllClassesInJVMExtractor {  
  14.  
  15.     private final static int BUFFER_SIZE = 4096;  
  16.  
  17.     public static void extractAllClassesFromJVM(String destFolder)  
  18.             throws IOException {  
  19.         ClassLoader appLoader = ClassLoader.getSystemClassLoader();  
  20.         ClassLoader currentLoader = AllClassesInJVMExtractor.class.getClassLoader();  
  21.  
  22.         ClassLoader[] loaders = new ClassLoader[] { appLoader, currentLoader };  
  23.         final Class< ?>[] classes = ClassScope.getLoadedClasses(loaders);  
  24.         for (Class< ?> cls : classes) {  
  25.             String className = cls.getName();  
  26.             URL classLocation = ClassScope.getClassLocation(cls);  
  27.             System.out.println("Extracting class: " + className + " from " +   
  28.                     classLocation);  
  29.             String destFileName = destFolder + "/" 
  30.                     + className.replace(".", "/") + ".class";  
  31.             copyFile(classLocation, destFileName);  
  32.         }  
  33.     }  
  34.  
  35.     private static void copyFile(URL sourceURL, String destFileName)  
  36.             throws IOException {  
  37.         File destFile = new File(destFileName);  
  38.         File destDirectory = destFile.getParentFile();  
  39.         destDirectory.mkdirs();  
  40.         InputStream srcStream = sourceURL.openStream();  
  41.         try {  
  42.             OutputStream destStream = new FileOutputStream(destFile);  
  43.             try {  
  44.                 copyStreams(srcStream, destStream);  
  45.             } finally {  
  46.                 destStream.close();  
  47.             }  
  48.         } finally {  
  49.             srcStream.close();  
  50.         }  
  51.     }  
  52.  
  53.     private static void copyStreams(InputStream srcStream,  
  54.             OutputStream destStream) throws IOException {  
  55.         byte[] buf = new byte[BUFFER_SIZE];  
  56.         while (true) {  
  57.             int bytesRead = srcStream.read(buf);  
  58.             if (bytesRead == -1) {  
  59.                 // End of stream reached  
  60.                 return;  
  61.             }  
  62.             destStream.write(buf, 0, bytesRead);  
  63.         }  
  64.     }  
  65.  
  66. }  
  67.  

It is not a rocket science. I go through all classes loaded by the current class loader and by the system class loader, get their fully qualified name (e.g. org.bouncycastle.cms.CMSSignedData) and their source URL location (e.g. jar:file:/C:/PROJECTS/GeneratePKCS7andVerify/lib/bcmail-jdk15-140.zip!/org/bouncycastle/cms/CMSSignedData.class) and I copy their binary contents (from the URL) to the destination folder (into a .class file). In the mean time I recreate the package structure (following the full class name with all its packages). Finally I get a directory containing all class files loaded in the JVM at the time of caling my method and I can manually package them in a JAR (removing beforehand all system Java classes). That’s all. I use slightly modified version of ClassScope.java.

You can download a fully functional example here (Eclipse project): ExtractAllClassesFromJVMIntoJAR.zip.

Posted by nakov in java, blog

Comments are closed.

Posted by 1010
01.JAVA/Java2008. 11. 27. 12:34
반응형


JAVA 환경변수 설정

시스템변수(S)에서 설정
1. 변수값 : path(맨끝에) : 수정
   변수값 : ;%JAVA_HOME%\bin

2. 변수이름 : CLASSPATH : 생성
   변수값 : .;%JAVA_HOME%\lib\tools.jar
   ※ 앞에 .; <= 주의할것 빼먹지말것

3. 변수이름 : JAVA_HOME : 생성
   변수값 : C:\Program Files\Java\jdk1.5.0_16

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

TOMCAT 환경변수 설정 (tomcat은 설치하는거 없이 압축풀면 설치끝)

시스템변수(S)에서 설정

   변수이름 : CATALINA_HOME - 생성
   변수값  : d:\www\apache-tomcat-5.5.27
   톰캣Start : apache-tomcat-5.5.27\bin\startup.bat
   톰캣Shutdown : apache-tomcat-5.5.27\bin\shutdown.bat

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Servlet 설정 (web.xml 수정)

   Tomcat이 설치된 폴더 => \conf\web.xml
     ex> apache-tomcat-5.5.27\conf\web.xml
   ※ 기본적으로 보안상 Servlet은 막혀있으므로 Servlet개발을 위해서
       주석을 제거하여 사용 환경을 만든다.

  1. "invoker" 찾기

########################################################
# <!--        #
#    <servlet>       #
#       <servlet-name>invoker</servlet-name>    #
#       <servlet-class>      #
#           org.apache.catalina.servlets.InvokerServlet   #
#          </servlet-class>      #
#          <init-param>      #
#             <param-name>debug</param-name>    #
#             <param-value>0</param-value>    #
#       </init-param>      #
#       <load-on-startup>2</load-on-startup>    #
#    </servlet>       #
# -->        #
########################################################
  [▲ 이부분을 찾는다]
  2. 주석제거 <!--, --> 이것만 삭제할것
########################################################
#    <servlet>       #
#       <servlet-name>invoker</servlet-name>    #
#       <servlet-class>      #
#           org.apache.catalina.servlets.InvokerServlet   #
#          </servlet-class>      #
#          <init-param>      #
#             <param-name>debug</param-name>    #
#             <param-value>0</param-value>    #
#       </init-param>      #
#       <load-on-startup>2</load-on-startup>    #
#    </servlet>       #
########################################################
  [▲ 이렇게 만들면 됨]
  3. Servlet Mapping의 주석도 제거("invoker" 찾기)

########################################################
#    <!-- The mapping for the invoker servlet -->   #
# <!--        #
#    <servlet-mapping>      #
#       <servlet-name>invoker</servlet-name>    #
#       <url-pattern>/servlet/*</url-pattern>    #
#    </servlet-mapping>      #
# -->        #
########################################################
     [▲ 이부분을 찾아서 주석제거,  ▼ 이렇게 만든다]
########################################################
#    <!-- The mapping for the invoker servlet -->   #
#        #
#    <servlet-mapping>      #
#       <servlet-name>invoker</servlet-name>    #
#       <url-pattern>/servlet/*</url-pattern>    #
#    </servlet-mapping>      #
#        #
########################################################
  4. web.xml을 저장하고 닫는다.
   ※ Servlet를 작성하기위해 API를 연결한다.
      여기서는 Tomcat에 있는 servlet-api.jar파일을 이용한다.
      EJB를 개발한다면 JavaEE를 설치하는게 좋다.
  5. tomcat\common\lib\
     위의 경로에서 servlet-api.jar파일을 복사하여
     Program Files/Java/jdk/lib/
     위의 lib폴더에 복사

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Servlet 환경변수 설정

시스템변수(S)에서 설정
1. 변수값 : CLASSPATH  : 지금까지 설정되있는것
   변수값 : .;%JAVA_HOME%\lib\tools.jar

2. jar파일 복사
    원본파일 : 톰캣경로\common\lib\servlet-api.jar (복사)
    사본파일 : C:\Program Files\Java\jdk1.5.0_16\lib\servlet-api.jar (여기에 복사할것)

3. 변수값 : CLASSPATH(맨끝에) : 수정
   변수값 : .;%JAVA_HOME%\lib\tools.jar;C:\Program Files\Java\jdk1.5.0_15\lib\servlet-api.jar
   위치 : 톰캣경로\common\lib\servlet-api.jar

   ※ 뒤에 servlet-api파일의 경로만 넣어주면된다.
      ;C:\Program Files\Java\jdk1.5.0_15\lib\servlet-api.jar

   ※ tomcat이설치된 경로\webaps\ROOT\WEB-INF\
      classes란 폴더를 만든다. (WEB-INF 폴더아래에)

   ※ Servlet은 웹 어플리케이션 폴더 아래에
      WEB-INF\classes폴더가 존재해야함

   ※ sevlet가 잘돌아가는지 확인하는것은
      classes폴더에 servlet파일을 넣고
      웹브라우저에 http://127.0.0.1/servlet/서블릿파일(확장자빼고)
      실행되는지 볼것 ( servlet도 java기반이기때문에 대소문자 구분.. 조심!! )

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Eclipse 설치

http://www.eclipse.org/downloads

Posted by 1010
01.JAVA/Java2008. 11. 19. 14:19
반응형

■ 환경 설정

개발 환경 설정에는 다음과 같이 3가지 방법이 있다.

1. JSDK 1.3이하 버젼 & JCE 1.2.1 글로벌 버전 JCE 1.2.1 버전은 http://java.sun.com 사이트에서 회원가입을 해야지 Down 받을수 있다. JCE는 미국에서 무기로 관주 되기 때문에 글로벌 버전은 미국, 케나다 버전과 다르다.

2. JSDK 1.4에는 Java Cryptography Extension 1.2.1 버전이 포함되어 있다.

3. 다운 받은 JCE의 압축을 풀고 lib방 밑에 있는 모든 jar파일을[JavaHome]jrelibext방으로 카피한다. 그리고 JCE 알고리즘을 사용하기 위해서 SunJCE 프로바이터를 설치 해야 한다.

설치하기 위해서는 [JavaHome]jrelibsecurityjava.security 파일에 다음을 추가한다.

security.provider.1=sun.security.provider.Sun,

security.provider.2=com.sun.crypto.provider.SunJCE


■ 서로 다른 개념들

1. 보안 != 암호 : 애플리케이션에 암호를 추가하는 것이 애플리케이션을 안전하게 하지 못한다.

2. 올바른 보안 모델 != 버그가 없는 구현 : 훌륭한 보안 모델이라고 해도 구현 과정에서의 버그는 공겨자의 대상이 될 수 있다.

3. 테스트 != 공식적인 증명

4. 컴포넌트 보안 != 전반적인 시스템 보안

5. 자바 보안 != 애플릿 통제

Base 64

Base64는 바이트 배열을 아스키 문자로 표현하기 위한 시스템이다. Base64체계는 RFC1521의 Section5.2에 완전하게 기술되어 있다. sun.misc.BASE64Encoder는 바이트 배열을 가지고 base64 disit를 가지 문자열을 생성한다. 이에 대응하는 클래스 BASE64Decoder는 문자열을 가지고 원래 바이트 배열을 생성한다.

Sun에서는 Base64를 지원할 의무가 없다.

■ 예제 프로그램

//-- Masher 메시지 축약

package test.crypto.part1;

import java.security.*;

import java.io.*;

import sun.misc.*;

public class Masher {

public Masher() {

}

private void exec(String targetFileName) {

try {

// MD5알고리즘을 이용한 메시지 축약 객체를 생성한다.

MessageDigest md = MessageDigest.getInstance("MD5");

FileInputStream fin = new FileInputStream(targetFileName);

byte[] buffer = new byte[8192];

int length;

while ( (length = fin.read(buffer)) != -1 ) {

md.update(buffer,0,length);

}

byte[] raw = md.digest();

// 출력 가능한 문자열로 변환한다.

BASE64Encoder encoder = new BASE64Encoder();

String base64 = encoder.encode(raw);

System.out.println(base64);

} catch(NoSuchAlgorithmException nalgoe) {

System.err.println(nalgoe);

} catch(FileNotFoundException fnote) {

System.err.println(fnote);

} catch(IOException ioe) {

System.err.println(ioe);

}

}

public static void main(String[] args) {

if(args.length < 1) {

System.out.println("메시지 축약의 대상 파일을 선택하여 주십시요");

System.exit(0);

}

String targetFileName = args[0];

Masher masher = new Masher();

masher.exec(targetFileName);

}

}


매시지 축약은 데이터를 증명하지만, 메시지에 대해서는 전혀 알 수 없다.

// DES알고리즘을 이용한 대칭키 암복호화

package test.crypto.part1;

import javax.crypto.*;

import java.io.*;

import java.security.*;

import sun.misc.*;

public class SecretWriting {

public SecretWriting() {

}

public void exec(String args[]) throws Exception{

// key를 얻어 생성한다.

Key key;

try{

ObjectInputStream in = new ObjectInputStream(new FileInputStream("SecretKey.ser"));

key = (Key)in.readObject();

in.close();

} catch (FileNotFoundException fnote) {

// ser Key 파일이 없으면 실행된다.

// DES 대칭키 알고리즘을 정의해서 키 제너레이터를 생성한다.

KeyGenerator keygenerator = KeyGenerator.getInstance("DES");

// 랜덤 함수로 초기화한다.

keygenerator.init(new SecureRandom());

// 키를 생성한다.

key = keygenerator.generateKey();

// 생성된 키 객체를 바이널리 파일로 저장한다.

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("SecretKey.ser"));

out.writeObject(key);

out.close();

}

Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");

// -e문자열이 없으면 복호화 한다. 다음은 암호화 한다.

if(args[0].indexOf("e") != -1) {

cipher.init(Cipher.ENCRYPT_MODE,key);

String amalgam = args[1];

for(int i = 2; i < args.length; i++) {

amalgam += " "+args[i];

}

byte[] stringBytes = amalgam.getBytes("UTF8");

byte[] raw = cipher.doFinal(stringBytes);

BASE64Encoder encoder = new BASE64Encoder();

String base64 = encoder.encode(raw);

System.out.println(base64);

} else if(args[0].indexOf("d") != -1) {

cipher.init(Cipher.DECRYPT_MODE,key);

BASE64Decoder decoder = new BASE64Decoder();

byte[] raw = decoder.decodeBuffer(args[1]);

byte[] stringBytes = cipher.doFinal(raw);

String result = new String(stringBytes, "UTF8");

System.out.println(result);

}

}

public static void main(String[] args) throws Exception{

if(args.length < 2) {

System.out.println("사용예 java tesst.crypto.part1.SecretWriting -[e|d] STRING1 STRING2 ...");

System.exit(0);

}

SecretWriting secretWriting = new SecretWriting();

secretWriting.exec(args);

}

}

DES 알고리즘은 동일한 키를 생성하여 암호화와 복호화가 이루워지는 대칭키 방식이다.

Chapter 2 기본기

■ 보안의 3대 규칙

1. 기밀성(Confidentially) : 비인가 된 사람은 내용을 볼 수 없어야 한다.

① 대칭암호화 : 송신측과 수신측이 동일한 키를 사용한다.(비밀키, 개인키 방식)


② 비대칭 암호화 : A의 공개키로 암호화 한 내용은 A의 개인키로만 풀 수 있다. 비대칭 암호화는 대칭 암호화에 비해 상당히 느리다.


2. 무결성(Integrity) : Date가 불법수정되지 못하게 보장한다. 암호화된 메시지 축약을 서명(signature)이라고 한다.


3. 인증(Authentication) : 상호 교신하는 사람이 각각 믿을수 있는 객체임을 보장한다.

인증서는 한 사람에 의해 발급되는 문장으로 다른 사람의 공개키를 가지는 어떤 값이다. 필수적으로 인증서는 서명된 공개키이다.


■ 알고리즘

비대칭 암호화와 서명은 다양한 키 크기를 가진다. 적절한 키 크기를 선태가하는 것은 사용자와 애플리케이션에 달려 있다.

명 칭
타 입
비 고

MD-5
메시지 축약
128비트 메시지 축약 생성

저항력에 약간의 허점을 발견

SHA-1
메시지 축약
160비트의 메시지 축약 생성

저항력이 증가

HmacMD5와 HmacSHA1
메시지 인증 코드


DSA
서명
512 ~ 1024비트 까지의 키 생성

ElGamal 서명
서명


DES
대칭 암호화


DESede
대칭 암호화


PBEWithMD5AndDES
대칭 암호화


ElGamal 암호
비대칭 암호화


DH
키 교환

표) 암호화 알고리즘

클래스/인터페이스
정의

java.security.cert.Certificate
암호인증

javax.crypto.Cipher
암호화

java.security.Key. java.security.PrivateKey, java.security.PublicKey, javax.crypto.SecretKey
서명이나 암호화에 사용되는키

javax.crypto.KeyAgreement
비밀키 교환 프로토콜

java.security.KeyFActory
공개키와 비밀키의 형식 변환

javax.crypto.KeyGenerator
대칭암호문에 사용될 키 생성

java.security.KeyPairGenerator
암호화와 인증에 사용된 공개키와 비밀키 생성

javax.crypto.Mac
메시지 인증 코드(MAC)

java.security.MessageDigest
암호화 해시함수

javax.crypto.SecretKeyFactory
비밀키의 형식 변환

java.security.SecureRandom
난수생성

java.security.Signature
전자서명

표) JDK와 JCE에 포함된 암호화 클래스

개념클래스
Sun이 지원하는 암호화 알고리즘
SunJCE가 지원하는 암호화 알고리즘

Cipher

DES, Desede, PBEWithMD5AndDES

KeyAgreement

DH

KeyFactory
DSA


KeyGenerator

DES,DESede

KeyPairGenerator Mac
DSA


Mac

HmacMD5, HmacSHA1

MessageDigest
MD5, SHA-1


SecretKeyFactory

DES, Desede, PBEWithMD5AndDES

Signature
DSA

표) 표준 알고리즘 이름

Chapter 3 난수

Chapter 4 키관리

■ Key 인터페이스

1. java.security.Key 인터페이스( 암호화 키를 캡슐화 )

- public String getAlgorithm() : 키가 사용된 암호 알고리즘 이름을 리턴

- public byte[] getEncoded() : 키의 암호화 값을 구할 수 있다.

- public String getFormat() : 암호화 하는데 사용된 키 포맷의 이름을 리턴한다.

- 자식 인터페이스 : java.security.PublicKey, java.security.PrivateKey,

javax.crypto.SecretKey(JCE)

2 java.security.KeyPair 클래스( 공개키와 개인키 )

- publicKeyPair(PublicKey publicKey, PrivateKey, privateKey) : 주어진 공개키와 개인키로 KeyPair를 생성한다.

- public PublicKey getPublic() : 공개키를 리턴한다.

- public PrivateKey getPrivate() : 개인키를 리턴한다.

■ 키 생성기

1. java.security.KeyPairGenerator( 비대칭 방식 JDK에 포함 )

- public KeyPair generateKeyPair () : 열쇠 페어를 생성합니다.

- public final KeyPair genKeyPair () : 열쇠 페어를 생성합니다.

- public String getAlgorithm () : 이 열쇠 페어 제네레이터의 알고리즘의 표준명을 돌려줍니다.

- public static KeyPairGenerator getInstance (String algorithm) : 지정된 다이제스트 알고리즘을 구현하는 KeyPairGenerator 오브젝트를 작성합니다.

- public static KeyPairGenerator getInstance (String algorithm, String provider) : 지정된 프로바이더로부터 지정된 알고리즘이 사용 가능한 경우에, 그 프로바이더가 제공한 알고리즘을 구현하는 KeyPairGenerator 오브젝트를 작성합니다.

- public final Provider getProvider () : 이 열쇠 페어 제네레이터 오브젝트의 프로바이더를 돌려줍니다.

- pubic void initialize (AlgorithmParameterSpec params) : 제공된 파라미터를 사용하여 KeyPairGenerator를 초기화 한다.

- public void initialize (AlgorithmParameterSpec params, SecureRandom random) : 제공된 파라미터를 사용하여 KeyPairGenerator를 초기화 한다.

- public void initialize (int keysize) : 랜덤 비트의 소스 역할을 하는 새로운 SecureRandom을 생성하는 것을 제외하고 임의의 키 사이즈에 대하여 열쇠 페어 제네리이터를 초기화합니다.

- pubic void initialize (int keysize, SecureRandom random) : 제공된 랜덤 비트 소스를 사용하여 임의의 키 사이즈 대하는 열쇠 페어 제네레이터를 초기화합니다.

KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");

kpg.initialize(1024);

KeyPair pair = kpg.getKeyPair();


2. javax.crypto.KeyGenerator( 대칭 방식 JCE에 포함 )

- public final SecretKey generateKey() : 새로운 랜덤 SecretKey를 생성한다.

- public String getAlgorithm() : KeyGenerator 객체에 사용된 알고리즘의 이름을 리턴합니다.

- public static KeyGenerator getInstance(.String algorithm) : 주어진 알고리즘으로 인스턴스 생성

- pubic static KeyGenerator getInstance(String algorithm, String provider) : 주어진 알고리즘과 프로바이터로 인스턴스 생성

- pubic Provider getProvider() : 사용된 프로바이터 리턴

- public void init(AlgorithmParameterSpec params) : 제공된 파라미터를 사용하여 초기화 한다.

- public void init(AlgorithmParameterSpec params, SecureRandom random) : 제공된 파라미터를 사용하여 초기화 한다.

- public void init(int keysize) : 주어진 크기로 키를 생성하기 위하여 초기화한다.

- public void init(int keysize, SecureRandom random) : 주어진 키와 랜덤소스로 초기화 한다.

- public void init(java.security.SecureRandom random) : 주어진 랜덤소스로 초기화 한다.

KeyGenerator kg = KeyGenerator.getInstance("DES");

kg.init(new SecureRandom());

SecretKey key = kg.generateKey()


■ 키 해석기

키를 저장하는 방법중 하나는 키를 직렬화 하여 저장하는것과 단순히 바이트의 배열과 같이 키를 저장하거나 전송하는 방법이 있다. 키 객체를 바이트로 또는 역으로 변환하는 방법이 다음의 클래스에 정의 되어있다.

1. javax.crypto.spec.SecretKeySpec( 바이트 배열을 비밀키로 변환하는 가장 간단한 방법 )

- SecretKeySpec(byte[] key, int offset, int len, String algorithm) : offset과 제공된 바이트의 배열의 len 바이트를 사용하여 SecretKeySpec을 생성한다. 키는 제공된 알고리즘을 따른다.

- SecretKeySpec(byte[] key, String algorithm) : 제공된 바이트 배열을 사용하여 SecretKeySpec을 생성한다. 키는 제공된 알고리즘을 따른다.

SecureRandom sr = new SecureRandom();

byte[] keyBytes = new byte[20];

sr.nextBytes(keyBytes);

SecretKey key = new SecretKeySpec(keyBytes, "HmacSHA1");


2. javax.crypto.SecretKeyFactory

- public static final SecretKeyFactory getInstance(String algorithm) : 주어진 알고리즘에 대하여 새로운 SecretKeyFactory를 생성한다 알고리즘은 “DES"와 같은 대칭 암호 알고리즘이다.

- pubic static final SecretKeyFactory getInstance(String algorithm, String provider) : 주어진 프로바이더로 SecretKeyFActory를 생성한다.

- public final SecretKey generateSecret(KeySpec keySpec) : KeySpec를 SecretKey로 변환하기 위하여 사용된다.

public SecertKey makeDESKey(byte[] input, int offset)

throws NoSuchAlgorithmException, InvalidkeyException, InvalidkeySpecException{

SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES");

KeySpec spec = new DESKeySpec(input, offset);

return desFactory.generateSecret(spec);

}


- public final KeySpec getKeySpec(SecretKey key, java.lang.Class keySpec) : 주어진 SecretKey로부터 KeySpec을 생성한다.

public byte[] makeBytesFromDESKey (SecretKey key)

throw NoSuchAlgorithmException, InvaildKeySpecException {

SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES");

DESKeySpec spec =

(DESKeySpec)desFactory.getKeySpec(Key, DESKeySpec.class);

return spec.getKey();

}


3. java.security.KeyFactory

- public static final KeyFactory getInstance (String algorithm) : 지정된 다이제스트 알고리즘을 구현하는 KeyFactory 오브젝트를 작성합니다. 알고리즘 이름은 비대칭 암호 알고리즘 이름이거나 “DSA"와 같은 서명 알고리즘 이어야 합니다.

- public static final KeyFactory getInstance (String algorithm, String provider) : 지정된 프로바이더로부터, 지정된 알고리즘의 KeyFactory 오브젝트를 작성합니다.

- public final PrivateKey generatePrivate (KeySpec keySpec) : 지정된 열쇠 사양 (열쇠 데이터)으로부터 개인키를 생성하는데 사용

- public final PublicKey generatePublic (KeySpec keySpec) : 지정된 열쇠 사양 (열쇠 데이터)으로부터 공개키를 생성하는데 사용

- public final KeySpec getKeySpec (Key key, Class keySpec) : 주어진 키로부터 키 스펙을 생성한다.

■ 키 교환 프로토콜

1. javax.crypto.KeyAgreement

- public static final KeyAgreement getInstance(String algorithm) : 주어진 알고리즘을 사용하여 새로운 KeyAgreement를 생성한다. 이름은 “DH"와 같은 키교환 알고리즘이어야 한다.

- public static final KeyAgreement getInstance(String algorithm, String provider) : 주어진 알고리즘과 프로바이더로 새로운 KeyAgreement를 생성한다.

- public final void init(Key key) : 제공된 키를 사용하는 KeyAgreement를 초기화 한다.

- public final void init(Key key, AlgorithmParameterSpec params) : 주어진 키와 알고리즘 지정 파라미터를 사용하여 KeyAgreement를 초기화 한다.

- public final void init(.Key key, AlgorithmParameterSpec params, SecureRandom random) : 주어진 키와 알고리즘 지정 파라미터 그리고 랜덤 소스를 사용해서 KeyAgreement를 초기화 한다.

- public final void init(Key key, SecureRandom random)

- public final Key doPhase(Key key, boolean lastPhase)

- public final byte[] generateSecret()

- public final int generateSecret(byte[] sharedSecret, int offset)

- public final SecretKey generateSecret(java.lang.String algorithm)

- public final String getAlgorithm()

- public final Provider getProvider()

// --< Skip Server >

package test.crypto.part5;

import java.security.*;

import java.net.*;

import java.io.*;

import java.security.spec.*;

import javax.crypto.*;

import sun.misc.*;

public class SkipServer {

public SkipServer() {

}

public void exec(int port) throws Exception {

//Diffie-Hellman 키 쌍 생성

KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");

kpg.initialize(Skip.sDHParameterSpec);

KeyPair keypair = kpg.genKeyPair();

// 연결을 기다린다.

ServerSocket ss = new ServerSocket(port);

System.out.println("요청을 기다리고 있습니다.... port : "+port);

Socket s = ss.accept();

DataOutputStream out = new DataOutputStream(s.getOutputStream());

DataInputStream in = new DataInputStream(s.getInputStream());

/**

* 첫번째 클라이언트는 서버에 바이트 배열의 코드화된

* Difie-Hellman공개키를 보낸다.

* KeyFactory가 그 키를 재구성하기 위해 사용된다.

*/

// 공개키를 받는다.

byte[] keyBytes = new byte[in.readInt()];

in.readFully(keyBytes);

KeyFactory kf = KeyFactory.getInstance("DH");

X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(keyBytes);

PublicKey theirPublicKey = kf.generatePublic(x509Spec);

// 우리의 키를 클라이언트에게 보낸다.

keyBytes = keypair.getPublic().getEncoded();

out.writeInt(keyBytes.length);

out.write(keyBytes);

/**

* 이제 개인키와 클라이언트의 공개키를 사용해서 보안값을 계산할수 있다.

* 그 보안 값은 Diffie-Hellman 키를 생성

* 하기 위해 사용된 계수만큼 길다. 이 경우 보안값은

* 1024비트 길이를 가진다.

*/

KeyAgreement ka = KeyAgreement.getInstance("DH");

ka.init(keypair.getPrivate());

ka.doPhase(theirPublicKey,true);

byte[] secret = ka.generateSecret();

out.close();

in.close();

BASE64Encoder encoder = new BASE64Encoder();

System.out.println(encoder.encode(secret));

}

public static void main(String[] args) throws Exception {

if(args.length < 1) {

System.out.println("포트 번호를 입력해주셔야 합니다...");

System.exit(0);

}

SkipServer skipServer = new SkipServer();

skipServer.exec(Integer.parseInt(args[0]));

}

}


//--- < Skip Client >

package test.crypto.part5;

import java.security.*;

import java.net.*;

import java.io.*;

import java.security.spec.*;

import javax.crypto.*;

import sun.misc.*;

public class SkipClient {

public SkipClient() {

}

public void exec(String ip, int port) throws Exception {

//Diffie-Hellman 키 쌍 생성

KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");

kpg.initialize(Skip.sDHParameterSpec);

KeyPair keypair = kpg.genKeyPair();

// 네트워크 연결

Socket s = new Socket(ip, port);

DataOutputStream out = new DataOutputStream(s.getOutputStream());

DataInputStream in = new DataInputStream(s.getInputStream());

// 공개키 전송

byte[] keyBytes = keypair.getPublic().getEncoded();

out.writeInt(keyBytes.length);

out.write(keyBytes);

// 공개키 수신

keyBytes = new byte[in.readInt()];

in.readFully(keyBytes);

KeyFactory kf = KeyFactory.getInstance("DH");

X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(keyBytes);

PublicKey theirPublicKey = kf.generatePublic(x509Spec);

/**

* 이제 개인키와 클라이언트의 공개키를 사용해서 보안값을 계산할수 있다. 그 보안 값은 Diffie-Hellman 키를 생성

* 하기 위해 사용된 계수만큼 길다. 이 경우 보안값은 1024비트 길이를 가진다.

*/

KeyAgreement ka = KeyAgreement.getInstance("DH");

ka.init(keypair.getPrivate());

ka.doPhase(theirPublicKey,true);

byte[] secret = ka.generateSecret();

out.close();

in.close();

BASE64Encoder encoder = new BASE64Encoder();

System.out.println(encoder.encode(secret));

}

public static void main(String[] args) throws Exception {

if(args.length < 2) {

System.out.println("서버 아이피와 포트 번호를 입력해주셔야 합니다...");

System.exit(0);

}

SkipClient skipClient = new SkipClient();

skipClient.exec(args[0], Integer.parseInt(args[1]));

}

}

■ KeyStore 클래스의 키 관리 패러다임

1. 인스턴스 생성

- public static final KeyStore getInstance (String type) : 지정된 타입의 키 스토어 오브젝트를 작성합니다. KeyStore ks = KeyStore.getInstance("JKS");

- public static final KeyStore getInstance (String type, String provider) : 지정된 프로바이더로부터, 지정된 키 스토어 타입의 키 스토어 오브젝트를 작성합니다. KeyStore ks = KeyStore.getInstance("JKS", "SUN");

2. 로딩과 저장

- public final void load (InputStream stream, char[] password) : 지정된 입력 Stream로부터 이 키 스토어를 로드합니다.

- public final void store (OutputStream stream, char[] password) : 지정된 출력 Stream에 이 키 스토어를 격납 해, 지정된 패스워드로 그 완전성을 보호합니다

향후 내용 추가 요망....

Chapter 5 인증

인증에 유요한 세가지 암호화 개념

- 메시지 축약(message digest)은 대용량 데이터 집합을 나타내는 식별자를 생성한다.

- 전자 서명(digital signature)은 데이터의 무결성을 증명하는데 사용한다.

- 인증서(certificate)는 암호적으로 공개키의 안전한 컨테이너로 사용된다.

■ 변경된 메시지 축약

package test.crypto.part6;

import java.security.*;

import java.io.*;

import sun.misc.*;

public class MessageDigestTester {

public MessageDigestTester() {

}

private void exec(String targetFileName) {

try {

// MD5알고리즘을 이용한 메시지 축약 객체를 생성한다.

MessageDigest md = MessageDigest.getInstance("MD5");

// 주어진 파일에 대한 축약 값 계산

DigestInputStream in = new DigestInputStream(new FileInputStream(targetFileName), md);

byte[] buffer = new byte[8192];

while ( in.read(buffer) != -1 ) {

}

byte[] raw = md.digest();

// 출력 가능한 문자열로 변환한다.

BASE64Encoder encoder = new BASE64Encoder();

String base64 = encoder.encode(raw);

System.out.println(base64);

} catch(NoSuchAlgorithmException nalgoe) {

System.err.println(nalgoe);

} catch(FileNotFoundException fnote) {

System.err.println(fnote);

} catch(IOException ioe) {

System.err.println(ioe);

}

}

public static void main(String[] args) {

if(args.length < 1) {

System.out.println("메시지 축약의 대상 파일을 선택하여 주십시요");

System.exit(0);

}

String targetFileName = args[0];

MessageDigestTester masher = new MessageDigestTester();

masher.exec(targetFileName);

}

}


■ 암호화된 패스워드 로그인

클라이언트에서 서버로 평문을 전송하는 것을 피하기 위해서, 클라이언트는 평문 대신에 패스워드 메시지를 축약하여 보내게 되며, 서버는 보유하고 있는 패스워드 사본의 메시지 축약을 비교하여 두 개의 메시지 축약 내용이 같을 경우에 클라이 언트를 인증하게 된다.

//--- < masher Server >

package test.crypto.part6;

import java.util.*;

import java.net.*;

import java.io.*;

import java.security.*;

public class MasherServer {

public MasherServer() {

}

public void exec(int port) throws Exception {

ServerSocket ss = new ServerSocket(port);

System.out.println("요청을 기다리고 있습니다.... port : "+port);

Socket s = ss.accept();

DataInputStream in = new DataInputStream(s.getInputStream());

// 클라이언트가 보낸 순서로받는다.

String user = in.readUTF();

long time = in.readLong();

double randomQ = in.readDouble();

int leng = in.readInt();

byte[] masherBytesIn = new byte[leng];

in.readFully(masherBytesIn);

byte[] masherBytesOut = userMasher.makeDigest(user, getPassword(), time, randomQ);

if(isUser(masherBytesIn, masherBytesOut)) {

System.out.println("Login");

} else {

System.out.println("No password");

}

in.close();

}

private String getPassword() {

return "rlarudwls";

}

private boolean isUser(byte[] inBytes, byte[] outBytes){

return MessageDigest.isEqual(inBytes, outBytes);

}

public static void main(String[] args) throws Exception {

if(args.length < 1) {

System.out.print("포트를 입력하여 주십시요....");

System.exit(0);

}

MasherServer ms = new MasherServer();

ms.exec(Integer.parseInt(args[0]));

}

}

//---< masherClient >

package test.crypto.part6;

import java.util.*;

import java.net.*;

import java.io.*;

public class MasherClient {

public MasherClient() {

}

public void exec(String user, String password, String host, int port) throws Exception {

Date date = new Date();

long time = date.getTime();

double randomQ = Math.random();

byte[] masherBytes = userMasher.makeDigest(user, password, time, randomQ);

Socket s = new Socket(host, port);

DataOutputStream out = new DataOutputStream(s.getOutputStream());

out.writeUTF(user);

out.writeLong(time);

out.writeDouble(randomQ);

out.writeInt(masherBytes.length);

out.write(masherBytes);

out.flush();

out.close();

}

public static void main(String[] args) throws Exception {

if(args.length < 2) {

System.out.print("서버 주소와 포트를 입력하여 주십시요....");

System.exit(0);

}

String user = "inter999";

String password = "rlarudwls";

MasherClient mc = new MasherClient();

mc.exec(user, password, args[0], Integer.parseInt(args[1]));

}

}

package test.crypto.part6;

import java.io.*;

import java.security.*;

public class userMasher {

public static byte[] makeDigest(String user, String password, long time, double randomQ)

throws NoSuchAlgorithmException {

MessageDigest md = MessageDigest.getInstance("SHA");

md.update(user.getBytes());

md.update(password.getBytes());

md.update(makeBytes(time, randomQ));

return md.digest();

}

public static byte[] makeBytes(long time, double randomQ) {

try {

ByteArrayOutputStream byteout = new ByteArrayOutputStream();

DataOutputStream dataout = new DataOutputStream(byteout);

dataout.writeLong(time);

dataout.writeDouble(randomQ);

return byteout.toByteArray();

} catch (IOException ioe) {

return new byte[0];

}

}

}

■ 이중 암호화 패스워드 로그인

메시지 축약을 사용하여 패스워드 정보를 보호하는 강력한 방법으로 이중 암호화 기법이 있는데, 이의 구성은 “암호화 패스워드 로그인”에 추가적으로 타임스템프와 난수를 포함한다. 즉 두 번째 축약에서 처음 축약메시지와 타임스템프, 난수를 이용하여 축약한다.

■ MAC

이 클래스는 메시지 인증 코드(MAC)에 대한 API를 정의한다. MAC은 비밀키를 공유하는 두 집단 사이에서 전송되는 정보의 무결성을 검사할 수 있다. MAC은 공개키/개인키가 아니라 비밀키와 함께 생성된다는 점을 제외하면 디지털 서명과 비슷하다. MAC 클래스는 알고리즘과 무관하며 제공자-기반이다. 정적 getInstance() 팩토리 메소드 중 하나를 호출하여 희망하는 MAC 알고리즘의 이름을 지정하여 MAC 객체를 얻는다.

“SunJCE"제공자는 ”HmacMD5", "HmacSHA1"이라는 두 개의 알고리즘을 구현한다.

1. Mac 객체를 얻은 후에는 init() 메소드를 호출하여 SecretKey를 지정하여 이 Mac 객체를 초기화 한다.

2. Mac 객체를 얻고 초기화 한 후에는, Mac이 계산될 데이터를 지정한다. 단순히 단일 바이트 배열을 처리 할때는 doFinal()에 전달하고, 스트리밍이나 다양한 위치에 저장된다면 update()를 여러번 호출하여 처리한다.

SecureRandom sr = new SecureRandom();

byte[] keyBytes = new byte[20];

sr.nextBytes(keyBytes);

SecretKey key = new SecretKeySpec(keyBytes, "HmacSHA1");

Mac m = Mac.getInstance("HmacSHA1");

m.init(key);

m.update(inputData);

byte[] mac = m.doFinal();


■ 서명(java.security.Signature)

서명은 두 가지 보안 서비스(인증과 무결성) 즉, 메시지가 변조되어 오지 않는 것과 메시지가 어떤 사람으로부터 보내졌는지를 보장해준다. 서명은 서명한 사람의 개인키를 가지고 암호화한 메시지 축약이다. 서명한 사람의 공개키만이 서명을 복호화 할 수 있으며, 이것이 인증을 제공한다. 메시지 축약이 서명으로부터 복호화한 메시지 축약과 같다면, 무결성 역시 보장되는 것이다.

1. Signature 객체는 정적 팩토리 메소드인 getInstance()중의 하나를 원하는 전자 서명 알고리즘과 선택적으로 알고리즘의 제공자를 지정하면서 호출하여 얻을 수있다.

2. 전자 서명은 본질적으로 공개키 암호화 알고리즘으로 암호화된 메시지 축약이다. 따라서 전자 서명 알고리즘을 지정하려면, 축약 알고리즘과, 암호화 알고리즘을 모두 지정해야한다.

3. 기본 “SUN"제공자에서 지원되는 유일한 알고리즘은 ”SHA1WwithDSA"이다.

4. 전자 서명의 생성을 위한 초기화를 하려면 initSign()을 호출하고 서명 생성을 위한 개인키를 지정해야한다.

5. 서명의 검증을 위한 초기화를 하려면 initVerify()를 호출하고 사인자(signer)의 공개키를 지정해야 한다.

6. Signature 객체가 초기화 되었으면 update()를 한번 이상 호출하여 사인되거나 검증될 바이트를 지정한다.

7. 마지막으로 전자 서명을 생성하기 위해 sign()을 호출하면서 사인이 저장될 바이트 배열을 넘겨준다.


// 핼퍼클래스

package acdpu.pki;

import java.io.Serializable;

import java.security.PrivateKey;

import java.security.PublicKey;

public class PKIHelper implements Serializable {

private String userId;

private String passwd;

private String createDate;

private String destroyDate;

private PrivateKey privatekey;

private PublicKey publickey;

public String getUserId() {

return userId;

}

public void setUserId(String newUserId) {

userId = newUserId;

}

public String getPasswd() {

return passwd;

}

public void setPasswd(String newPasswd) {

passwd = newPasswd;

}

public String getCreateDate() {

return createDate;

}

public void setCreateDate(String newCreateDate) {

createDate = newCreateDate;

}

public String getDestroyDate() {

return destroyDate;

}

public void setDestroyDate(String newDestroyDate) {

destroyDate = newDestroyDate;

}

public PrivateKey getPrivatekey() {

return privatekey;

}

public void setPrivatekey(PrivateKey newPrivatekey) {

privatekey = newPrivatekey;

}

public PublicKey getPublickey() {

return publickey;

}

public void setPublickey(PublicKey newPublickey) {

publickey = newPublickey;

}

}

// 컨트롤러

package acdpu.pki;

import java.security.*;

import acdpu.pki.exception.*;

public class PKIController {

public PKIController() {

}

public PrivateKey createKeyPair(String userid, String passwd)

throws PKICreateException, IncorrectUserException, TermExpirationException, PKIDBHandlerException {

try{

KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA", "SUN");

kpg.initialize(1024,SecureRandom.getInstance("SHA1PRNG"));

KeyPair keypair = kpg.genKeyPair();

PKIDBHandler.userConfirm(userid, passwd);

PKIHelper pkihelper = new PKIHelper();

pkihelper.setUserId(userid);

pkihelper.setPasswd(passwd);

pkihelper.setPrivatekey(keypair.getPrivate());

pkihelper.setPublickey(keypair.getPublic());

PKIDBHandler.saveKeyPair(pkihelper);

return pkihelper.getPrivatekey();

} catch (NoSuchProviderException nope) {

throw new PKICreateException("NoSuchProvider");

} catch (NoSuchAlgorithmException noae) {

throw new PKICreateException("NoSuchAlgorithm");

}

}

public void loginPKI(String userid, String passwd, byte[] signature)

throws PKIInvalidException, IncorrectUserException, TermExpirationException, PKIDBHandlerException {

PKIDBHandler.userConfirm(userid, passwd);

PKIDBHandler.verify(userid, passwd, signature);

}

public static void main(String[] args) {

PKIController pKIController = new PKIController();

}

}

// 데이터 모델

package acdpu.pki;

import java.sql.*;

import java.io.*;

import acdpu.pki.exception.*;

import java.security.*;

import java.security.spec.*;

public class PKIDBHandler {

public static boolean saveKeyPair(PKIHelper pkihelper)

throws PKIDBHandlerException {

Connection con = null;

PreparedStatement pstmt = null;

ResultSet rs = null;

try {

Class.forName("oracle.jdbc.driver.OracleDriver");

con = DriverManager.getConnection("jdbc:oracle:thin:@ 218.145.231.3:1522:ora8", "itdev", "itdev");

con.setAutoCommit(false);

StringBuffer sbSQL = new StringBuffer();

sbSQL.append("update TB_PKI set privatekey = ?, publickey = ?, indate=sysdate where userid='"+pkihelper.getUserId()+"'");

pstmt = con.prepareStatement(sbSQL.toString());

ByteArrayInputStream privateKeywriter = new ByteArrayInputStream(pkihelper.getPrivatekey().getEncoded());

ByteArrayInputStream publicKeywriter = new ByteArrayInputStream(pkihelper.getPublickey().getEncoded());

pstmt.setBinaryStream(1,privateKeywriter,privateKeywriter.available());

pstmt.setBinaryStream(2,publicKeywriter,publicKeywriter.available());

pstmt.executeUpdate();

privateKeywriter.close();

publicKeywriter.close();

con.commit();

return true;

} catch(ClassNotFoundException cnote) {

throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+cnote.getMessage());

} catch(IOException io) {

throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+io.getMessage());

} catch(SQLException e) {

try{

con.rollback();

con.setAutoCommit(true);

}catch(Exception er){}

throw new PKIDBHandlerException("PKIDBHandler.saveKeyPair Method Error : Message = "+e.getMessage());

} finally {

try {

con.setAutoCommit(true);

if(rs !=null ) {

rs.close();

rs=null;

}

if(pstmt !=null ) {

pstmt.close();

pstmt = null;

}

if(con!=null && !con.isClosed()) {

con.close();

con =null;

} else {

con=null;

}

} catch(Exception e) {

rs = null;

pstmt = null;

con = null;

}

}

}

public static void verify(String userid, String passwd, byte[] signature)

throws PKIInvalidException, PKIDBHandlerException {

Connection con = null;

PreparedStatement pstmt = null;

ResultSet rs = null;

try {

Class.forName("oracle.jdbc.driver.OracleDriver");

con = DriverManager.getConnection("jdbc:oracle:thin:@ 218.145.231.3:1522:ora8", "itdev", "itdev");

StringBuffer sbSQL = new StringBuffer();

sbSQL.append("select publickey from TB_PKI where userid ='"+userid+"'");

pstmt = con.prepareStatement(sbSQL.toString());

rs = pstmt.executeQuery();

&

Posted by 1010
01.JAVA/Java2008. 11. 19. 13:55
반응형




1. 폰트파일(nGulim.ttf)를 폰트 매트릭스 파일(nGulim.xml)로 바꾼다

java -cp D:\work\FopToPdf\WebContent\WEB-INF\lib\fop.jar;D:\work\FopToPdf\WebContent\WEB-INF\lib\avalon-framework.jar;D:\work\FopToPdf\WebContent\WEB-INF\lib\commons-logging.jar;D:\work\FopToPdf\WebContent\WEB-INF\lib\commons-io.jar org.apache.fop.fonts.apps.TTFReader D:\Temp\fop\nGulim.ttf D:\Temp\fop\nGulim.xml


2. 폰트파일과 폰트매트릭스 파일을 컨피그에 등록.

<font metrics-url="D:\Temp\fop\nGulim.xml" embed-url="D:\Temp\fop\nGulim.ttf" kerning="yes">
    <font-triplet name="NewGulim" style="normal" weight="normal"/>
    <font-triplet name="NewGulim" style="normal" weight="bold"/>
</font>

3. 컨피그를 사용하겠다고 알림

// 유저가 설정한 컨피그 파일을 사용한다. 밑에서 유저 에이전트를 생성하기 전에 컨피그 파일을 설정해야 폰트 등이 적용된다
fopFactory.setUserConfig(new File("d:/Temp/fop/mycfg.xml")); 


FOUserAgent foUserAgent = fopFactory.newFOUserAgent();

.

.

.


4. fo 파일을 EUC-KR로 사용, 사용할 폰트를 설정

<?xml version="1.0" encoding="EUC-KR"?>
<fo:root font-family="NewGulim" font-size="12pt" font-style="normal" font-weight="normal" text-align="center" xmlns:fo="http://www.w3.org/1999/XSL/Format">
  <fo:layout-master-set>
    <fo:simple-page-master master-name='simpleA4' page-height='29.7cm' page-width='21cm' margin-top='2cm' margin-bottom='2cm' margin-left='2cm' margin-right='2cm'>
      <fo:region-body/>
    </fo:simple-page-master>
  </fo:layout-master-set>
  <fo:page-sequence master-reference='simpleA4'>
    <fo:flow flow-name='xsl-region-body'>
      <fo:block>안녕</fo:block>
    </fo:flow>
  </fo:page-sequence>
</fo:root>

Posted by 1010
01.JAVA/Java2008. 11. 19. 13:50
반응형

ⅰ. 엑셀을 자바로 읽을(쓰는것 포함) 수 있도록 하는 방법은 jxl과 poi 두가지 방법이 있는데 우선 그나마 간단한 jxl 방법을 몇자 적어 보도록 하겠다.
( jxl과poi차이나 특성, 프로젝트 정보등은 검색엔진을 이용하길 바란다. )

 우선
http://www.andykhan.com/jexcelapi/download.html 에 가서 최신jexcelapi.tar 파일을 다운로드 하고 압축을 풀면 파일안에 jxl.jar파일이 있다. 이 jar파일을 인식 시키기 위하여, 자바가 설치된 곳의 라이브러리 폴더에 이 파일을 넣는다. (예 : C:/Program Files/Java/jdk1.6.0_01/jre/lib/ext/jxl.jar).

 쓰기 등을 원하거나 다른 것을 원할때는
http://www.andykhan.com/jexcelapi/tutorial.html로 가면 tutorial이 있으니 이걸 참고하길 바란다.

--참고로 엔디칸인가 하는 주인장 한테 메일 보내 봤는데 답장이 없으니 편지 보내지는 말자.

ⅱ. 그럼 예문을 만들어 보면

/***************************************************************
*  기          능   :  엑셀파일 읽어 String으로 반환
*     Function    :  getData(int row) row : 읽을 시작행 번호
*  참          조   :  C:/Program Files/Java/jdk1.6.0_01/jre/lib/ext/jxl.jar
*  Return value  :  String
*  작    성    자  :  유 진 철
*  작    성    일  :  2007-08-17
/***************************************************************/

import jxl.*;
import java.io.*;
import java.util.*;

public class ReadXLS
{
   String filename = null;

   //ReadXLS 생성자
   public ReadXLS(String filename)
   {
      this.filename = filename.trim();  
   }

   //String 반환하는 메소드
   public String getData(int row) throws Exception
   {
      //파일 생성및 데이터 선언
      StringBuffer cellData = new StringBuffer();                     
      File file = new File(filename); 

      //존재여부 판단
      if(file.exists())
      {
         //파일여부 판단
         if(file.isFile())
         {
            StringTokenizer st = new StringTokenizer(filename, ".");
            String xls = null;

            //파일 확장자 구하기
            while(st.hasMoreTokens())
            {
               xls = st.nextToken();  
            }

            //확장자 엑셀파일 여부판단
            if(xls.equals("xls"))
            {
               //엑셀정보 에서 쉬트정보 가져오기
               Workbook workbook = Workbook.getWorkbook(file);
               Sheet sheet = workbook.getSheet(0);
              
              //데이터 생성및 구분자 삽입 col=>[xxx] / row=>\n
               for (int i=row; i<sheet.getRows(); i++ )
               { 
                  for (int j=0; j<sheet.getColumns(); j++ )
                  { 
                     Cell cell = sheet.getCell(j,i);
                     cellData.append("["+cell.getContents().trim()+"]");                                  
                  }
                  cellData.append("\n");
               }            
               workbook.close();
            }
            else
            {
               throw new Exception("엑셀파일이 아닙니다.");
            }

         }
         else
         {
            throw new Exception("파일이 아닙니다.");
         }
      }
      else
      {        
         throw new Exception("존재하지 않는 이름 입니다.");
      }           
      return new String(cellData);//String 반환
   }//end getData()

   public static void main(String [] args)
   {
      try
      {
         ReadXLS readxls = new ReadXLS("ex.xls");
         String xls = readxls.getData(0); //0번 행 부터 읽기      
         System.out.println(xls );
      }//End Of try
      catch (Exception e)
      {
         e.printStackTrace();
      }//End Of catch     
   }//End Of Main 
  
}


/*
다음과 같은 프로그램을 컴파일 해서 돌리면(물론 같은 폴더에 ex.xls라는 엑셀 파일을 하나 실험용으로 만들어서 넣어둔다.)
[xxx][xxx][xxx]...
[xxx][xxx][xxx]
[][][][][][][][]
[][][][][][][][]
.
.
.
이런 식으로 결과가 나온다.
프로그램에서 파일겍체를 만들거나 하는것은 안전한 실행을 위해서 한것이고 핵심은 색상이 다른 부분을 중점적으로 보면 될것이다.
*/


ⅰ. 엑셀을 자바로 읽는방법 두번째를 작성하여 보도록 하겠다.

  POI프로젝트는 아파치구룹의 자카르타 프로젝트 일환으로 poor object importalble과 비슷한 약자였다. (확실하지는 않다ㅡ.ㅡ;) 아무튼 혼자 만으로는 빈약하다는 뜻인거 같다.
그리고 poi자체로 토란이라는 뜻도 있단다. 역시 빈약하다.
아무튼 모든 아파치 구룹이 그러하듯 
http://mirror.apache-kr.org/poi/release/bin/
 가서 zip최신걸로, 덜렁 받아 압축풀고 클레스 패스 잡아준다.
 추신 : POI는 Microsoft Format File을 액세스 할 수 있는 API를 제공한다고 한다.
(엑셀만 할수 있는 것이 아니란 말이다.)

 참고로
http://apache.org/ 가서 poi 선택하고 javadocs가면 API있다.
영어되면 다른 무궁한 프로젝트도 참고 하길 바란다. 

ⅱ. 역시 예문을 만들어 보면


/***************************************************************
*  기          능    :  엑셀파일 읽어 String으로 반환
*     Function     :  getData(), getData(String sheets, String rows, String cols),
                       :  xlsFormDate..., getXLSDate...
*  참 조 :  C:/Program Files/Java/jdk1.6.0_01/jre/lib/ext/poi-3.0.1-FINAL-20070705.jar
            :  C:/Program Files/Java/jdk1.6.0_01/jre/lib/ext/poi-contrib-3.0.1-FINAL-20070705.jar
            :  C:/Program Files/Java/jdk1.6.0_01/jre/lib/ext/poi-scratchpad-3.0.1-FINAL-20070705.jar
*  Return  value :  String
*  작    성    자  :  유 진 철
*  작    성    일  :  2007-12-17
/***************************************************************/

import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.hssf.record.*;
import org.apache.poi.hssf.model.*;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.hssf.util.*;

import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;

public class ReadXLS
{
   //필드 선언 엑셀파일이름, 내용, 읽을범위
   private String filename = null;
   private HSSFWorkbook hssfworkbook = null;
   private int startSheet =  0;
   private int endSheet   = -1;
   private int startRow   =  0;
   private int endRow     = -1;
   private int startCol   =  0;
   private int endCol     = -1;

   //ReadXLS 생성자
   public ReadXLS(String filename) throws Exception
   {     
      File file = new File(filename); 

      //존재여부 판단
      if(file.exists())
      {
         //파일여부 판단
         if(file.isFile())
         {
            StringTokenizer st = new StringTokenizer(filename, ".");
            String xls = null;

            //파일 확장자 구하기
            while(st.hasMoreTokens())
            {
               xls = st.nextToken();  
            }

            //확장자 엑셀파일 여부판단
            if(xls.equals("xls"))
            {
               this.filename = filename;
               POIFSFileSystem fs = new POIFSFileSystem(new FileInputStream(filename));
               hssfworkbook = new HSSFWorkbook(fs);              
            }
            else
            {
               throw new Exception("엑셀파일이 아닙니다.");
            }

         }
         else
         {
            throw new Exception("파일이 아닙니다.");
         }
      }
      else
      {        
         throw new Exception("존재하지 않는 이름 입니다.");
      }        
   }

   //String 반환하는 메소드
   public String getData() throws Exception
   {
      return getData("ALL","ALL","ALL");
   }

   //String 반환하는 메소드
   public String getData(String sheets, String rows, String cols) throws Exception
   {
      //입력받은 값을 엑셀파일 읽을범위 값으로 전환
      StringTokenizer stTok = new StringTokenizer(sheets, ",");
      StringTokenizer rwTok = new StringTokenizer(rows,   ",");
      StringTokenizer clTok = new StringTokenizer(cols,   ",");

      String startSheetTok = null;
      String endSheetTok   = null;
      String startRowTok   = null;
      String endRowTok     = null; 
      String startColTok   = null;
      String endColTok     = null; 

      int sheetCounts = stTok.countTokens();
      int rowCounts   = rwTok.countTokens();
      int colCounts   = clTok.countTokens();

      //쉬트범위 판단
      if(sheetCounts == 2)
      {
         startSheetTok = stTok.nextToken();
         endSheetTok   = stTok.nextToken();
        
         try
         {
            startSheet = Integer.parseInt(startSheetTok);
            if (startSheet<0) { startSheet = 0; }    
         }
         catch(NumberFormatException ne){}

         try
         {  
            endSheet   = Integer.parseInt(endSheetTok);             
         }
         catch(NumberFormatException ne){}    
      }

      //행범위 판단
      if(rowCounts == 2)
      {
         startRowTok = rwTok.nextToken();
         endRowTok   = rwTok.nextToken();
        
         try
         {
            startRow = Integer.parseInt(startRowTok);
            if (startRow<0) { startRow = 0; }    
         }
         catch(NumberFormatException ne){}

         try
         {  
            endRow   = Integer.parseInt(endRowTok);             
         }
         catch(NumberFormatException ne){}    
      }

      //열범위 판단
      if(colCounts == 2)
      {
         startColTok = clTok.nextToken();
         endColTok   = clTok.nextToken();
        
         try
         {
            startCol = Integer.parseInt(startColTok);
            if (startCol<0) { startCol = 0; }    
         }
         catch(NumberFormatException ne){}

         try
         {  
            endCol   = Integer.parseInt(endColTok);             
         }
         catch(NumberFormatException ne){}    
      }
      //읽어서 처리하는 메소드 호출
      return execute();
   }

   //실행 메소드
   private String execute() throws Exception
   {
      StringBuffer cellData = new StringBuffer();
     
      //읽는범위 유효성검사 및 조정
      int sheetNum = hssfworkbook.getNumberOfSheets()-1;
      if((startSheet-endSheet)>0 || endSheet>sheetNum)
      {           
         endSheet = sheetNum;
      }
     
      //읽는범위 만큼 실행
      for (int k = startSheet; k <= endSheet; k++)
      {
        
         HSSFSheet sheet = hssfworkbook.getSheetAt(k);
         if(sheet!=null)
         {           
            int       rowNum  = sheet.getLastRowNum();
            if((startRow-endRow)>0 || endRow>rowNum)
            {           
               endRow = rowNum;
            }

            for (int r = startRow; r <= endRow; r++)
            {              
               HSSFRow row   = sheet.getRow(r);
               if(row!=null)
               {                 
                  int    colNum = row.getLastCellNum()-1;
                  if((startCol-endCol)>0 || endRow>colNum)
                  {           
                     endCol = colNum;
                  }

                 
                  for (int c = startCol; c <= endCol; c++)
                  {                    
                    HSSFCell cell  = row.getCell((short)c);
                     String   value = null;
                     if(cell!=null)
                     {
                        //셀타입 구분
                        switch (cell.getCellType())
                        {
                           case HSSFCell.CELL_TYPE_BLANK   :                             
                              value = "["+""
                              + "]";
                              break;

                           case HSSFCell.CELL_TYPE_BOOLEAN :                             
                              value = "["+cell.getBooleanCellValue()
                              + "]";
                              break;

                           case HSSFCell.CELL_TYPE_ERROR   :                            
                              value = "["+cell.getErrorCellValue()
                              + "]";
                              break;

                           case HSSFCell.CELL_TYPE_FORMULA :                             
                              value = "["+cell.getCellFormula()                             
                              + "]";
                              break;

                           case HSSFCell.CELL_TYPE_NUMERIC :                             
                              DecimalFormat dft = new DecimalFormat("########################.########");
                              value = "["+dft.format(cell.getNumericCellValue())
                              + "]";
                              break;

                           case HSSFCell.CELL_TYPE_STRING :                                                     
                              value = "["+cell.getRichStringCellValue().getString()
                              + "]";
                              break;

                           default :
                        }//End Of switch
                       
                        cellData.append(value);
 
                     }//End Of if cell
                    
                     else{cellData.append("[]");}                    

                  }//End Of for c++
                  cellData.append("\n");
               }//End Of if row
            }//End Of for r++
         }//End Of if sheet
      }//End Of for k++
     
      return new String(cellData);//String 반환
   }

   /*
   Date and Time Pattern        Result 
   "yyyy.MM.dd G 'at' HH:mm:ss z"   2001.07.04 AD at 12:08:56 PDT 
   "EEE, MMM d, ''yy"           Wed, Jul 4, '01 
   "h:mm a"                  12:08 PM 
   "hh 'o''clock' a, zzzz"       12 o'clock PM, Pacific Daylight Time 
   "K:mm a, z"               0:08 PM, PDT 
   "yyyyy.MMMMM.dd GGG hh:mm aaa"   02001.July.04 AD 12:08 PM 
   "EEE, d MMM yyyy HH:mm:ss Z"   Wed, 4 Jul 2001 12:08:56 -0700 
   "yyMMddHHmmssZ"              010704120856-0700 
   "yyyy-MM-dd'T'HH:mm:ss.SSSZ"   2001-07-04T12:08:56.235-0700
   */

   //원하는 페턴으로 문자형 날짜전달
   public static String xlsFormDate(String Pattern, String Date) throws Exception
   {     
      int date = Integer.parseInt(Date.trim());
      return xlsFormDate(Pattern, date);      
   }

   //원하는 페턴으로 문자형 날짜전달
   public static String xlsFormDate(String Pattern, int Date)
   {
      Calendar cal = Calendar.getInstance();
      cal.clear();  
      cal.add(cal.DATE, Date-25569);
      SimpleDateFormat sdt = new SimpleDateFormat(Pattern);
      return sdt.format(cal.getTime());
   }
  
   //Date 객체전달
   public static Date getXLSDate(String Date) throws Exception
   {     
      int date = Integer.parseInt(Date.trim());
      return getXLSDate(date);      
   }

   //Date 객체전달
   public static Date getXLSDate(int Date)
   {
      Calendar cal = Calendar.getInstance();
      cal.clear();  
      cal.add(cal.DATE, Date-25569);     
      return cal.getTime();
   }  

   public static void main(String [] args)
   {
      try
      {
         ReadXLS readxls = new ReadXLS("ex.xls");
         String xls = readxls.getData("0,0","ALL","ALL");        
         System.out.println(xls );

         /* //날짜 메소드 실험후 주석 처리함
         String Date = ReadXLS.xlsFormDate("yyyy-MM-dd hh:mm", "39423");
         System.out.println("Date : "+Date );

         Date aa = ReadXLS.getXLSDate("39423");
         System.out.println("Object Date : "+aa.toString());
        */

      }//End Of try
      catch (Exception e)
      {
         e.printStackTrace();
      }//End Of catch     
   }//End Of Main 
  
}

/*
똑같이 프로그램을 컴파일 해서 돌리면(물론 같은 폴더에 ex.xls라는 엑셀 파일을 하나 실험용으로 만들어서 넣어둔다.)
[xxx][xxx][xxx]...
[xxx][xxx][xxx]
[][][][][][][][]
[][][][][][][][]
.
.
.
이런 식으로 결과가 나온다.
이것 역시 필요없는 부분이 많은데 이것은 신경쓰지 말고 색상 있는 부분만 보면 된다. 좀더 편리하게 날짜를 데이터베이스에 넣거나(xlsFormDate), 지수를 소수로 표현하기 위하여(DecimalFormat) 사용한 겍체가 몇게 있다.  
*/

Posted by 1010
01.JAVA/Java2008. 11. 19. 13:48
반응형

Orinoco - A Java API to generate PDF documents

This is the home page of Orinoco: a Java API allowing Java developers to generate PDF documents.

Yet another PDF generator?

There are many Java PDF generators available - why bother with another one? The motivation arose when trying to use a commercially available PDF generator to deliver adhoc reports through a browser. The PDF generator offered everything: charts, drawings, images etc., but all that was required was merely to lay out some text in a tabular format with a few headings in bold. The tool we were using could certainly do this, but when generating larger reports (around 70 pages) it was taking so long to generate the PDF that the HTTP request timed out and the users ended up with nothing.

Rather than spend the time finding another generator and negotiating licensing agreements I decided to write a PDF generator which satisfied our meagre functional requirements and focussed on delivering the generated document quickly.

Orinoco offers the functionality needed to generate text reports (no graphics) and it is very quick: it can generate a document of a couple of hundred pages in a few seconds.

Features

  • Allows presentation of text in a variety of fonts and styles
  • Headers, footers and page numbering
  • Layout data in tables, with headings
  • Background shading
  • Tabs
  • Paper sizes and page orientation
  • Automatically handles line wrapping and page breaks

    Click here to see a sample

    This example shows the extended character set supported with orinoco, together their octal character codes

    Limitations

    Only the "standard" set of postscript fonts are supported (Times Roman, Helvetica,Courier etc). There is no support for drawing (other than straight lines) or embedding images.

    Using the library

    Orinoco requires a set of font information files, which are packaged along with the distribution in a subdirectory called fonts. Whenever the API is invoked it will look for this subdirectory under the system property called "resources".

    The easiest way to set this system property is to use the -D option when invoking the JVM eg.

    java -Dresources=/path/to/resources ...

    Orinoco will therefore expect to find the fonts directory on the system as /path/to/resources/fonts

    Demo

    To invoke the demo program which generates this PDF document, open a command line prompt in the "orinoco" directory, which was created when the distribution was untarred:

    java -classpath orinoco.jar -Dresources=resources orinoco.demo.Test -pdf test.pdf

    Note the use of -D option to set the resources system property. This points to the immediate subdirectory "resources" and orinoco will look in here for the subdirectory called "fonts".

    Examine the source code src/orinoco.demo/Test.java to see how the orinoco API can be used to generate PDF documents.

    Installation

    Orinoco comes packaged as a zipped tar file, called something like orinoco_1_0_3.tar.gz. To unpack on UNIX systems, at the command line type

    gunzip orinoco_1_0_3.tar.gz      followed by
    tar xf orinoco_1_0_3.tar

    On Linux systems this can be accomplished within the single command

    tar zxf orinoco_1_0_3.tar.gz

    On Windows/NT systems, the archive may be unpacked visually using a utility such as Winzip.

    Whatever the unpacking process, the application will be placed in a subdirectory called orinoco. The top level directory contains this html page and the pre-built jar file, orinoco.jar. The resources directory contains the fonts subdirectory, which in turn contains the important font information files. The docs directory contains the javadoc documentation for the public classes, the build directory contains the buildfile (requires ant ) and the src directory contains the source code for the java classes.

    Licensing

    Orinoco is issued on under the GNU Lesser General Public License. For further information click here.

    Download Orinoco

    Change History

    Return to my home directory

    This site last updated 18th December, 2007

  • Posted by 1010
    01.JAVA/Java2008. 11. 19. 13:47
    반응형

    Java Excel API - A Java API to read, write and modify Excel spreadsheets

    This is the home page of Java Excel API - open source Java API which allows Java developers to read Excel spreadsheets and to generate Excel spreadsheets dynamically. In addition, it contains a mechanism which allows java applications to read in a spreadsheet, modify some cells and write out the new spreadsheet.

    This API allows non Windows operating systems to run pure Java applications which can both process and deliver Excel spreadsheets. Because it is Java, this API may be invoked from within a servlet, thus giving access to Excel functionality over internet and intranet web applications.

    Features

  • Reads data from Excel 95, 97, 2000 workbooks
  • Reads and writes formulas (Excel 97 and later only)
  • Generates spreadsheets in Excel 2000 format
  • Supports font, number and date formatting
  • Supports shading and colouring of cells
  • Modifies existing worksheets
  • Supports image creation
  • Preserves macros on copy
  • Customizable logging

    Limitations

  • JExcelApi does not generate or chart, graph or macro information. This information is however preserved when spreadsheets are copied
  • When adding images to a sheet, only PNG image formats are supported

    Getting started

    The distribution comes with a number of demo programs which illustrate how the API may be used.

    Reading Spreadsheets

    When reading a spreadsheet, the demo programs may be used "as is" to convert Excel files to CSV and XML formats in a reasonable manner.

    java -jar jxl.jar -csv myspreadsheet.xls

    To view the spreadsheet as XML, invoke the demo as follows

    java -jar jxl.jar -xml myspreadsheet.xls

    In order to present the data the demo uses the classes xlrd/CSV.java and xlrd/XML.java respectively. For more sophisticated processing, these classes may be used as a starting point. Click here for a tutorial.

    Generating Spreadsheets

    The write demo illustrates the functionality accessible within JExcelApi to generate spreadsheets. The spreadsheet generated by the demo includes different fonts, number formatting, date formatting, colours and borders.

    To generate the demo spreadsheet, invoke JExcelApi as follows:

    java -jar jxl.jar -write myspreadsheet.xls

    This will generate the sample spreadsheet called myspreadsheet.xls in the current working directory. The class used to generate this spreadheet is jxl/Write.java. This may be used as a starting point for bespoke processing.

    Copying Spreadsheets

    JExcelApi may be used to copy and modify a spreadsheet. Included with the JExcelApi is a spreadsheet called jxlrwtest.xls, which is hardcoded into the demo program. If this spreadsheet is passed as a command line argument to the demo, then a copy of this spreadsheet will be generated, with the second sheet containing modified values.

    DO NOT MODIFY THE DEMO SPREADSHEET otherwise the modification demo will not work.

    To run this demo, from the directory containing jxlrwtest.xls type

    java -jar jxl.jar -rw jxlrwtest.xls myoutput.xls

    This will generate an output spreadsheet called myoutput.xls. The first sheet (called "original") is unchanged, but the second sheet (called "modified" has had its cell contents changed, as indicated by the labels.

    The class for the modification demo is jxl/ReadWrite.java.

    Requirements

    JExcelApi requires Java 2 JDK to run.

    When dealing with large spreadsheets, particularly when using the copy functionality, it is recommended that users allocate sufficient memory to the JVM using the -Xms and -Xmx options on the java command line.

    Installation

    JExcelApi comes packaged as a zipped tar file, called something like jexcelapi_2_0.tar.gz. To unpack on UNIX systems, at the command line type

    gunzip jexcelapi_2_0.tar.gz      followed by
    tar xf jexcelapi_2_0.tar

    On Linux systems this can be accomplished within the single command

    tar zxf jexcelapi_2_0.tar.gz

    On Windows/NT systems, the archive may be unpacked visually using a utility such as Winzip.

    Whatever the unpacking process, the application will be placed in a subdirectory called jexcelapi. The top level directory contains this html page and the pre-built jar file, jxl.jar. The docs directory contains the javadoc documentation for the public classes, the build directory contains the buildfile (requires ant ) and the src directory contains the source code for the java classes.

    Excel Versions

    JExcelApi will read workbooks created in Excel 95, 97 and 2000, and will generate workbooks that can be read by Excel 97 and later.

    Licensing

    JExcelApi is issued on under the GNU Lesser General Public License. For further information click here.

    Further Notes

    A tutorial is available for those who wish guidance on how to use the API for reading, writing and copying spreadsheets.

    When writing out spreadsheets, JExcelApi has limited support for graphs charts and macros during the copy process, but they cannot be created via the API.

    For further technical information, including how JExcelApi handles dates and unicode characters, read the technical notes

    Download JExcelApi

    Change History



    I endeavour to answer all emails, but it's taking up more and more time. Before emailing me with a question please check the FAQ first.

    Eric Jung has set up a yahoo group  here. As well as providing a discussion forum I will also post a message to this group whenever a new version of JExcelApi is released.

    Stefano has ported JExcelApi to .net. You can access the software here
    Evgeniy has ported 2.6.2 to C#, which you can download here

    Acknowledgements

    From version 2.6.5, JExcelApi uses JFlex to parse Excel formulas. The JFlex runtime is distributed with JExcelApi, should you wish attempt a rebuild of JXL. However, at runtime JXL only uses the generated lexical parser and none of the actual JFlex runtime code, so users of JExcelApi are not tied to the JFlex GPL rules, only the JExcelApi LGPL agreements.

    More information about JFlex can be here

    Make a Donation

    JExcelApi is free software and will remain so. If JExcelApi has been of benefit to you or your company, perhaps you might like to make a voluntary donation to JExcelApi using PayPal

    Return to my home directory

    This site last updated 27th October, 2008

  • Posted by 1010
    01.JAVA/Java2008. 11. 19. 13:39
    반응형

    출처 : http://pcguy7.springnote.com/pages/420735

    jxl_read.java

    jxl_write.java


    안녕하세요. Sharing Java 운영자 아기팬더 입니다. 자바를 이용하여 엑셀을 다루는 방법이 여러가지 있습니다.


    관심이 많아서 네이버를 통해서 학습을 하고자 했는데 일관된 예제들과 생각보다는 부족한 설명으로 인해 많은 이해하기 위해


    많은 시간을 보낸 것 같습니다. 개인적으로 공부차원에서 오늘 맘잡고 정리하여 공유합니다. 주석을 완전히 달면서 API 문서를


    보려고 하니 생각보다 시간이 많이 걸리더군요. 이클립스를 주로 다루기 때문에 코딩하고 실행하였을 때 이클립스 상에서는


    오류가 없었습니다. 혹시라도 실행이 안 되면 답글 달아주시면 바로 수정하겠습니다.


    저는 jxl api를 이용하여 정리하였습니다. 참고 바랍니다.


    1. 준비사항


        당연히 이클립스를 사용하신다면 jxl api(링크주소) 중에서 jexcelapi_2_6_4.zip 를 다운 받아서 사용하시는 프로젝트 파일

        주변에 압축을 풀어주세요.

        이클립스 메뉴바에서 현재의 프로젝트명을 오른쪽 클릭 - Build Path - Add External Archives 클릭한 후
        jxl API를 다운받아서 압축을 푼 곳에서 jxl.jar 파일을 찾아서 삽입시켜야 해요.

        배포를 고려한다면 엄한 곳에서 가져오지는 마세요^^


    2. 실전


        jxl api 폴더 내의 doc폴더 안을 보시면 java api와 똑 같은 형태의 api문서를 확인하실 수 있습니다. 너무 자세하죠 ?

        이제부터 주석을 꼼꼼히 보시면서 실행해 보시면 기분이 좋아지실 겁니다. ^^


    3. Jxl_Write.java (엑셀 파일을 생성하는 부분)


    //babypanda(2007.07.24)


    import java.io.File; // 파일 생성을 위해서 필요
    import java.io.FileNotFoundException; // 파일 생성을 위해서 필요
    import java.io.IOException; // 파일 생성을 위해서 필요


    // 아래의 jxl 부분을 이클립스에서 사용하시기 위해서는 메뉴바에서 현재의 프로젝트명을 오른쪽 클릭 - Build Path - Add External

        Archives 클릭한 후
    // jxl API를 다운받아서 압축을 푼 곳에서 jxl.jar 파일을 찾아서 삽입시켜야 해요. 꼭.... 반드시.... 무조건....
    // 배포를 고려한다면 엄한 곳에서 가져오지는 마셈. 가까운 곳에 압축을 풀어서 불러오셈. ^^


    import jxl.Workbook; // 엑셀 파일 관리를 위해 처음과 끝에 해당하는 기본이 되는 추상 클래스

    import jxl.write.WritableWorkbook; // 실제 엑셀 파일 관리를 위해 Workbook, Sheet을 생성하는 Swing의 Frame과 같은 사막의 오아시스 역할하는 추상 클래스
    import jxl.write.WritableSheet; // 쉬트를 관리하는 인터페이스
    import jxl.write.WritableCellFormat; // 셀의 포멧 관련 클래스
    import jxl.write.WriteException; // 셀의 쓰기 오류를 관리하기 위한
    import jxl.write.Label; // 라벨 관리 클래스
    //import jxl.write.Blank; // 빈셀 관리 클래스

    import jxl.format.*; // 셀 정렬, 보더, 컬러 부분 관련 클래스 임포트


    public class Jxl_Write{

         public static void  main(String args[]) throws FileNotFoundException, IOException, WriteException{
         // 예외처리를 아예 해주고 내려옵니다.
     
         WritableWorkbook myWorkbook = Workbook.createWorkbook(new File("jxl_Smile.xls")); // 파일이름을 정하여 생성한다.

         WritableSheet mySheet = myWorkbook.createSheet("first sheet", 0); // WritableSheet는 인터페이스
         // WritableWorkbook에서 메소드를 이용하여 생성. 0번, 즉 첫번째 쉬트를 first sheet라는 이름으로 생성한다.

       

         WritableCellFormat numberFormat = new WritableCellFormat(); // 번호 셀 포멧 생성
         WritableCellFormat nameFormat = new WritableCellFormat(); // 이름 셀 포멧 생성
         WritableCellFormat dataFormat = new WritableCellFormat(); // 데이터 셀 포멧 생성

       

         // 번호 레이블 셀 포멧 구성(자세한 것은 링크 된 API를 참조하셈) 참고사항 : 여기 부분에서 WriteException이 발생하네요.
         // 그러나 상단에서 미리 예외 처리를 해서 상관 없겠네요.

         numberFormat.setAlignment(Alignment.CENTRE); // 셀 가운데 정렬
         numberFormat.setVerticalAlignment(VerticalAlignment.CENTRE); // 셀 수직 가운데 정렬
         numberFormat.setBorder(Border.ALL, BorderLineStyle.THICK); // 보더와 보더라인스타일 설정
         numberFormat.setBackground(Colour.ICE_BLUE); // 여름에 맞는 색깔 ? ^^
     
         // 이름 레이블 셀 포멧 구성(자세한 것은 링크 된 API를 참조하셈)
         nameFormat.setAlignment(Alignment.CENTRE); // 셀 가운데 정렬
         nameFormat.setVerticalAlignment(VerticalAlignment.CENTRE); // 셀 수직 가운데 정렬
         nameFormat.setBorder(Border.BOTTOM, BorderLineStyle.HAIR); // 보더와 보더라인스타일 설정
         nameFormat.setBackground(Colour.GOLD); // 여름에 맞는 색깔 두번째 ? ^^


         // 데이터 셀 포멧 구성
         dataFormat.setAlignment(Alignment.CENTRE); // 셀 가운데 정렬
         dataFormat.setVerticalAlignment(VerticalAlignment.CENTRE); // 셀 수직 가운데 정렬


         // 쉬트의 컬럼 넓이 설정

         mySheet.setColumnView(0, 8); // 쉬트의 번호 컬럼(0번째)의 넓이 설정. setCloumnView(몇번째 컬럼, 넓이)
         mySheet.setColumnView(1, 15); // 쉬트의 이름 컬럼(1번째)의 넓이 설정
         mySheet.setColumnView(2, 20); // 쉬트의 비고 컬럼(2번째)의 넓이 설정


         // 라벨을 이용하여 해당 셀에 정보 넣기 시작

         Label numberLabel = new Label(0, 0, "학번", numberFormat); // 학번 라벨(열,행,"문장",포멧)
         mySheet.addCell(numberLabel); // 쉬트의 addCell 메소드를 사용하여 삽입

         Label nameLabel = new Label(1, 0, "성명", nameFormat); // 성명 라벨(열,행,"문장",포멧)
         mySheet.addCell(nameLabel); // 쉬트의 addCell 메소드를 사용하여 삽입


         //Blank blank = new Blank(2, 0, numberFormat); // 빈 셀(열,행,포멧) -- 필요 시 주석 처리 풀고 사용하셈. ^^
         //sheet.addCell(blank);


         Label postScript = new Label(2, 0, "비고", nameFormat); // 비고 라벨(열,행,"문장",포멧)
         mySheet.addCell(postScript); // 쉬트의 addCell 메소드를 사용하여 삽입

           for(int no=1; no<6; no++){
              Label numberLabels = new Label(0, no, "["+no+"]", dataFormat); // 데이터 포멧에 맞게 1에서 5까지 번호 생성
              mySheet.addCell(numberLabels); // 셀에 삽입
           
              Label nameLabels =new Label(1, no, (char)(no+64)+"", dataFormat); // 데이터 포멧에 맞게 대문자 A에서 E까지 생성
              mySheet.addCell(nameLabels); // 셀에 삽입
          }

         // 라벨을 이용하여 해당 셀에 정보 넣기 끝


         myWorkbook.write(); // 준비된 정보를 엑셀 포멧에 맞게 작성
         myWorkbook.close(); // 처리 후 메모리에서 해제 처리
         }
    }


    4. Jxl_Read.java (엑셀파일을 읽어오는 부분)


    //babypanda(2007.07.24)

    import java.io.*; // 파일 입력 관련 클래스
    import jxl.*; // jxl 관련 클래스


    public class Jxl_Read{
         public static void  main(String args[]) throws FileNotFoundException, IOException,jxl.read.biff.BiffException{
        // 이번에도 파일과 엑셀 파일 입력 관련 예외 선언을 미리 해주고 들어옵니다.


         Workbook myWorkbook = Workbook.getWorkbook(new File("jxl_Smile.xls")); // 파일을 읽어 와서...
         Sheet mySheet = myWorkbook.getSheet(0); // 정확한 쉬트를 입력 받아서...
     
         System.out.print("학번\t성명\t비고\t\n"); // 엑셀 제목을 적어 주고
     
              for(int no=1;no<6;no++){ // 행의 갯수 만큼 돌리고
                   for(int i=0;i<3;i++){ // 열의 갯수 만큼 돌려서
                            Cell myCell = mySheet.getCell(i,no); // 셀의 행과 열의 정보를 가져온 후...
                            System.out.print(myCell.getContents()+"\t"); // 텝의 거리 만큼 열을 나열 하고...
              //getContents()메소드에 대해

              //Quick and dirty function to return the contents of this cell as a string. 이라고 API에 쓰여 있더군요.
                   }
                System.out.println(); // 행이 바뀔 때 마다 개행하여 출력해 보셈.
              }
         }
    }


    5. 실행화면


    1) 엑셀에 쓰기




     

    2) 엑셀 파일의 비고란에 글자를 입력한 후 저장하여 읽어오면 ?





    Posted by 1010
    01.JAVA/Java2008. 11. 19. 13:38
    반응형

    자바 JXL API 를 이용한 Excel 파일 만들기
     
    1. JXL API 다운 받기
     
    2. JSP에서 사용할 class 만들기
     
    3. JSP에서 적용하기
     
    4. 요구 사항에 맞게 확장하기
     
     
    1. 환경 설정
     
    JAVA에서 Excel을 만들기 위해서는 JXL API 나 POI를 이용해야 한다.
     
    우선 이번장에서는 JXL을 이용하여 만드는 방법에 대해서 소개하기로 한다.
     
    Java Excel API - A Java API to read, write and modify Excel spreadsheets ( 이하 JXL )은
     
    다음과 같은 홈페이지에서 다운 받을 수 있다
     
     
    파일 다운 받기 : 
     
     
    API 정보  :
     
     
     
    우선 사이트에서 가장 최신 버젼의 JExcelApi를 다운 받는다.
     
    압축을 해제한 후에 각 사용자 환경에 맡게 jar 파일을 옮긴다.
     
    ( 일반 JAVA User - j2sdk 폴더 밑에 jrelibext 파일로 옮긴다. )
     
     
    2. JSP에서 사용할 class 만들기
     
     
    1. Parameter 정보
     
    JSP에서 class로 넘겨주는 정보는 다음과 같다.
     
    Vector data  :: Query를 통해 나온 Recordset 집합
     
    String[] column  ::  column 이름 <- Excel 에서 Head 역할
     
    int[] columntype :: column 타입 <- 문자형, 정수형, 실수형 으로 구분하였다.
     
    String FilePath :: Excel 파일을 저장하기위한 절대 경로
     
    String SheetName :: Sheet 이름
     
     
    2. Excel File 생성하기
     
    Excel 파일음 다음과 같이 생성을 한다
     
    WritableWorkbook workbook = Workbook.createWorkbook(new File(FilePath));
    WritableSheet sheet = workbook.createSheet(SheetName, 0);
     
    - FilePath :: Excel 파일을 저장하기위한 절대 경로
    - SheetName :: 시트 이름
    - 0 :: 시트 인덱스 번호
     
     
    3. Cell Type 지정하기
     
    a. Text 형
    jxl.write.WritableCellFormat format_column = new WritableCellFormat();
    jxl.write.WritableCellFormat format_data = new WritableCellFormat();

     
    b. 정수형 ( 1000단위 마다 , 찍기 X )
    jxl.write.WritableCellFormat format_integer1 = new WritableCellFormat(NumberFormats.INTEGER);

     
    c. 정수형 ( 1000단위 마다 , 찍기 O )
    jxl.write.NumberFormat moneytype1 = new NumberFormat("###,##0");
    jxl.write.WritableCellFormat format_integer2 = new WritableCellFormat(moneytype1);

     
    d. 실수형 ( 1000단위 마다 , 찍기 X )
    jxl.write.WritableCellFormat format_float1 = new WritableCellFormat(NumberFormats.FLOAT);
     
    e. 실수형 ( 1000단위 마다 , 찍기 0 , 소수점 2자리 허용 )
    jxl.write.NumberFormat moneytype2 = new NumberFormat("###,##0.00");
    jxl.write.WritableCellFormat format_float2 = new WritableCellFormat(moneytype2);
     
    4. CELL Color & BackGround 지정하기
                           
    format_column.setBackground(jxl.format.Colour.GRAY_25 );
    format_column.setBorder(jxl.format.Border.ALL,jxl.format.BorderLineStyle.THIN );
       
    .
    .
     
     
    5. DB To Excel
     
    a. Head 부분 생성하기
     
       for ( cellnum = 0; cellnum < column.length; cellnum++ ) {
        label = new jxl.write.Label(cellnum,0,column[cellnum],format_column);
        sheet.addCell(label);
       }
     
    b. Data 집어 넣기
       for ( rownum = 1; rownum <= data.size() ; rownum++) {
        dataResult = (String[])data.get(rownum-1);
     
        for (cellnum = 0; cellnum < dataResult.length - 1; cellnum++) {
          num = new jxl.write.Number(cellnum, rownum, Long.parseLong(dataResult[cellnum+1]),format_integer2);
          sheet.addCell(num);
        }
       }
                                      
     
    3. JSP에서 사용하기
    <%@ page import="java.util.Vector, crm.util.Util, java.sql.*" contentType="text/html; charset=euc-kr" %>
    <jsp:useBean id="cExcel" class="crm.util.CreateExcel" scope="page"/>
     
     
    cExcel.CreateExcelFile(queryResult,column,columntype,file_path+file_name,sheet_name,str_search);
     
     
    4. 요구 사항에 맞게 확장하기

    JXL API를 제공하는 홈페이지에 가면 JXL API SDK를 다운 받을 수 있다.
     
    JXL API SDK를 다운 받은 후 사용자 요구 사항에 맞게 수정을 하면 될 것이다.
     
     
    원본 : #. 자바 JXL API 를 이용한 Excel 파일 만들기
    Posted by 1010
    01.JAVA/Java2008. 11. 19. 13:37
    반응형



    http://okjsp.pe.kr/seq/122183

    jxl은 대용량데이터를 엑셀문서를 통해 입력할때 속도가 빠릅니다.
    단점은 엑셀형식으로 데이터를 받았을때 제대로 된 엑셀이 아니라 조작이 좀 그렇습니다.
    반대로 poi는 데이터를 엑셀문서로 다운받았을때 제대로 된 엑셀문서라 현업들이 바로 조작하고 사용하기가 편합니다.
    하지만 데이터를 넣을때 소량은 상관없지만 수만개이상넣을때..좀 느립니다.
    그래서 저도 지금까지 4,5번해봤지만 데이터를 넣을때는 jxl로 데이터를 받을때는 poi로 합니다.
    Posted by 1010