Whiteship's Note

'Java'에 해당되는 글 135건

  1. 2010/03/11 [ClassLoader] Thread의 getContextClassLoader()
  2. 2010/03/09 Class.forName()와 ClassLoader.loadClass() 차이점 (2)
  3. 2010/03/02 톰캣 6.0 클래스로더 구조
  4. 2010/03/02 null은 캐스팅이 되는건야 안 되는거야? (9)
  5. 2010/02/25 클래스 로더의 loadClass()와 findClass()
  6. 2010/02/23 자바 클래스로더 입문 퀴즈 정리 (2)
  7. 2010/02/23 [ClassLoader 퀴즈 끝] SpringSprout와 WhiteshipFactory가 참조하는 Whiteship은 누구인가.
  8. 2010/02/22 [ClassLoader 퀴즈 6] SpringSprout는 과연 Whiteship의 이름을 알 수 있을까? (4)
  9. 2010/02/22 [ClassLoader 퀴즈 5] Whiteship은 언제 로딩 될까? (2)
  10. 2010/02/21 [ClassLoader 퀴즈 4] SpringSprout가 알고 있는 Whiteship은 누구인가? (10)
  11. 2010/02/19 [ClassLoader 퀴즈 3] Whiteship은 Whiteship일까 아닐까? (2)
  12. 2010/02/19 [ClassLoader 퀴즈 2] Whiteship은 내가 데려왔다!! (5)
  13. 2010/02/19 [ClassLoader 퀴즈 1] Whiteship은 대체 누가 데려온 것일까? (8)
  14. 2010/02/17 [Java] ClassLoader API (2)
  15. 2010/02/10 [NullPE] SpEL 때문에 고민 해결
  16. 2010/02/10 [NullPE] NullPointerException 때문에 고민 1 (2)
  17. 2010/02/09 WEB-INF/urlrewrite.xml 파일 설정하기
  18. 2010/02/07 UrlRewriterFilter 옵션
  19. 2010/02/07 UrlRewriterFilter 설치하기
  20. 2010/02/07 "서블릿 매핑 규칙"과 "필터 순서 정하기 규칙" (2)
  21. 2010/02/07 UrlRewriterFilter 소개
  22. 2010/01/25 자바 System.out.println 콘솔 출력 가로채기 (6)
  23. 2009/07/20 클래스파일 보기 (5)
  24. 2009/05/19 Double.MAX_VALUE는 좀 특이하군요 @_@ (4)
  25. 2009/03/13 EJ2E Item 20. 태그가 있는 클래스 대신 클래스 계층구조를 선호하라.
  26. 2009/01/28 EJ2E Item 19. 인터페이스는 오직 타입을 정의할 때만 사용하라
  27. 2009/01/19 EJ2E Item 18. 추상 클래스 보다는 인터페이스를 선호하라 (5)
  28. 2009/01/13 EJ2E Item 17. 상속에 대한 설계와 문서화를 제대로 하지 않을 거면 아예 상속을 허용하지 말라.
  29. 2008/12/28 EJ2E Item 16. 상속보다 컴포지션을 선호하라
  30. 2008/12/23 EJ2E Item 15. 변경을 최소화하라 (2)

[ClassLoader] Thread의 getContextClassLoader()

Java : 2010/03/11 17:57


pdf: http://www.theserverside.com/tt/articles/content/dm_classForname/DynLoad.pdf

먼저 간단한 퀴즈로 시작하는.. 긴 문서..


이 코드가 문제를 일으킬 수 있는 상황은 저 클래스를 자바 Extentions 폴더 (<JDK>/jre/lib/ext 또는 <JRE>/lib/ext)에 놓았을 경우입니다. 그렇지 않고 그냥 CLASSPATH에 둘었다면 별 문제가 없을 가능성이 큽니다. 대체 저런식으로 동적으로 로딩하려는 클래스들이 App CL의 CLASSLOADER에 있기 마련일 테니까요.

하지만, Extention 폴더로 이동하면 무슨일이 벌어질까요. ClassNotFoundExcepion이 발생합니다. 그 원인을 정확히 모르신다면 ClassLoader에 대한 아주 기본적인 개념이 부족한거라 봐도 무방하겠습니다.

이 글에서 그에 대한 해결책 두 개를 제공해주며, 그 중 하나가 바로 쓰레드 컨텍스트 클래스로더를 사용하는 것입니다.

http://java.sun.com/javase/6/docs/api/java/lang/Thread.html#getContextClassLoader%28%29

Returns the context ClassLoader for this Thread. The context ClassLoader is provided by the creator of the thread for use by code running in this thread when loading classes and resources. If not set, the default is the ClassLoader context of the parent Thread. The context ClassLoader of the primordial thread is typically set to the class loader used to load the application.

First, if there is a security manager, and the caller's class loader is not null and the caller's class loader is not the same as or an ancestor of the context class loader for the thread whose context class loader is being requested, then the security manager's checkPermission method is called with a RuntimePermission("getClassLoader") permission to see if it's ok to get the context ClassLoader.

별짓을 하지 않는다면 쓰레드 컨텍스트 클래스로더는 System(또는 Application) CL이 됩니다. 따라서 저 위의 코드를 자바 extentions 폴더에 넣고 실행하더라도 쓰레드 컨텍스트 클래스로더를 가져와서 loadClass를 호출하면 아무런 문제없이 동작합니다.

안그래도 대체 Thread Context ClassLoader가 왜 생긴건지 궁금했는데 이 글을 통해서 짐작할 수 있게 됐습니다. 클래스로더 계층 구조를 만들 때, 예를 들어 톰캣의 Common CL 같은 녀석은 자기 하위의 CL인 WebAPP CL에 어떤 클래스들이 들어올지 모를겁니다. 하지만 그런 상태에서 Common CL에서 WebAPP CL에서만 로딩할 수 있는 어떤 클래스를 로딩할 필요가 있다면... WebAPP CL을 직접 참조하지 않고 바로 Thread Context ClassLoader를 사용해서 로딩하도록 코딩을 하면 로딩할 수 있게 되는거죠.

흠.. 하지만 이.. 미적지근한 느낌은 뭘까요.. 왠지 상위 CL에서 하위 CL의 클래스패스에 있는걸 로딩해버리면 왠지 꼬여버릴 것만 같은 이 기분... 아.. 불안해. 왜 그럴까나..
저작자 표시
top

Java : 2010/03/11 17:57 Trackback. : Comment.

Class.forName()와 ClassLoader.loadClass() 차이점

Java : 2010/03/09 18:04


http://www.javaworld.com/javaworld/javaqa/2003-03/01-qa-0314-forname.html?page=1

일단 Class.forName() 메서드는 인자가 한 개 짜리인 것과 세 개 짜리인 것이 있습니다.

static Class<?>     forName(String className)
          Returns the Class object associated with the class or interface with the given string name.

static Class<?>     forName(String name, boolean initialize, ClassLoader loader)
          Returns the Class object associated with the class or interface with the given string name, using the given class loader.

1. 클래스를 로딩할 때 사용하는 클래스로더 차이

인자가 한개 짜리인 forName(String) 메서드는 클래스를 로딩할 때 사용하는 클래스로더가 저 코드를 실행하는 클래스로더가 됩니다. 하지만 ClassLoader.loadClass()를 사용하면 당연히 자기 자신을 사용해서 클래스 로딩을 실행하게 되죠. (그렇다고 해서 반드시 해당 클래스로더가 읽어온다는 보장은 없죠. 그 부모가 읽어올 수도 있고 클래스 패스에 없을 수도 있고 암튼 여기서 로딩한다는 건 로딩을 시도한다고 보시기 바랍니다.)

하지만 Class.forName(String, boolean, ClassLoader)를 사용하면 클래스 로더를 지정해 줄 수 있습니다.

2. 초기화

Class.forName(String) 메서드를 사용하면 곧바로 클래스의 static 초기화 블럭과 static 멤버 변수의 값을 초기화 합니다. 하지만 ClassLoader.loadClass()를 사용하면 해당 클래스를 처음으로 사용하기 전까지 초기화가 지연됩니다.

이것 역시 Class.forName(String, boolean, ClassLoader)의 두번째 인자값을 이용하여 조절할 수 있습니다.

- 클래스 초기화 에러

만약 Class.forName(String)을 사용해서 로딩할 때 static 영역에서 에러가 난다면 해당 클래스는 다시 로딩할 수가 없습니다. 특정 클래스로더가 일단 로딩한 클래스는 다시 로딩할 수가 없죠. 그래서 NoClassDefinitionFound 에러가 날 수도 있습니다.

이때는 해당 클래스로더 인스턴스를 버리고 새로 만들어야 하는데 그럴 때를 대비해 인자 세개짜리 forName을 쓰라는군요.

결국 forName()으로 클래스를 로딩할 떄는 별 개의 클래스로더를 쓰라는건데... 흠.. 그렇게 단순해 보이지가 않는데;; 머 클래스로더를 지정해 둔다고 해봤자. 보통 App CL로 읽어올테고 그럼 App CL 인스턴스를 버리라고?? 에이;; 그건 좀..

forName으로 읽어올 클래스를 클래스패스를 가지고 있으면서 parent로 위임하지도 않는 CL을 이용해서 forName으로 읽은 경우라면 뭐 괜찮을지도.. 어쨋거나 직접 통제가 가능한 클래스로더를 사용해야 겠군요.


저작자 표시
top


톰캣 6.0 클래스로더 구조

Java : 2010/03/02 18:10


http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html

BootStrap CL: JVM 코어, 확장 라이브러리용 클래스 로더

System CL: $CATALINA_HOME/bin/bootstrap.jar와 CATALINA_HOME/bin/tomcat-juli.jar를 가지고 시스템 클래스 로더를 생성함. 일반적인 애플리케이션의 시스템 클래스 로더와는 동작 방식이 다름. 일반적인 애들은 시스템 클래스로더에서 CLASSPATH에 있는 것들을 로딩하지만, 톰캣의 스크립트(($CATALINA_HOME/bin/catalina.sh 또는 %CATALINA_HOME%\bin\catalina.bat)가 이를 무시해버림.

Common CL: $CATALINA_HOME/lib에 위치한 톰캣 내부 클래스와 웹 애플리케이션이 공통으로 참조할 라이브러리를 로딩한다. 

웹 애플리케이션 CL:  각각의 웹 애플리케이션당 해당 애플리케이션의 /WEB-INF/classes와 /WEB-INF/lib 디렉토리에 있는 클래스와 리소스를 로딩한다. 
SRV.9.7.2 Web Application Classloader

The classloader that a container uses to load a servlet in a WAR must allow the
developer to load any resources contained in library JARs within the WAR
following normal J2SE semantics using getResource. It must not allow theWAR to
override J2SE or Java servlet API classes. It is further recommended that the loader
not allow servlets in theWAR access to the web container’s implementation classes.
It is recommended also that the application class loader be implemented so that classes and resources packaged within the WAR are loaded in preference to classes and resources residing in container-wide library JARs.
위와 같은 서블릿 2.3 스펙에 따라 웹 애플리케이션 CL은 다음의 순서로 클래스를 로딩한다.

1. Bootstrap classes of your JVM
2. System class loader classes (described above)
3. /WEB-INF/classes of your web application
4. /WEB-INF/lib/*.jar of your web application
5. $CATALINA_HOME/lib
6. $CATALINA_HOME/lib/*.jar

1번은 웹 애플리케이션 CL의 최상위 CL인 BootStrap CL에 의해 로딩될 것이고, 거기서 못찾는다면
2번 System CL에 의해서 로딩 된다. 거기서도 못찾는거라면 일단 자바의 코어 라이브러리나 확장 라이브러리도 아니며 톰캣의 내부 클래스(톰캣 서버를 실행하는 클래스와 톰캣의 Commons logging API와 java.util.logging LogManager를 찾는게 아닐것이다. 그런 경우.
3번과 4번에서 웹 애플리케이션 CL 자신의 클래스패스에 있는 라이브러리를 찾아보고.. 거기에 없는 경우
5, 6번 Commons CL의 클래스패스에서 찾아본다.

클래스 로더의 동작 방식이 웹 애플리케이션이 아닌 경우가 웹 애플리케이션인 경우가 다르며, 이것 마저도 웹 서버 마다 다를 수 있으니 자신이 사용하는 컨테이너의 클래스로더 동작 방식을 알고 있어야겠다. 

흠.. 스프링의 TC 서버는 어떨지 궁금하다. 그것도 톰캣을 확장한 거니까 이것과 비슷하지 않을까. DM 서버는 왠지 완전히 다를 것 같다.
저작자 표시
top

Java : 2010/03/02 18:10 Trackback. : Comment.

null은 캐스팅이 되는건야 안 되는거야?

Java : 2010/03/02 13:38


    @Test
    public void nullCasting() throws Exception {
        Class nullClass = (Class)null;
        Class thisClass = this.getClass();
        assertThat(nullClass, is(nullValue()));
        assertThat(thisClass, is(instanceOf(Class.class)));
        assertThat(nullClass, is(not(instanceOf(Class.class))));
    }

캐스팅이 된것 같다. 하지만 안 됐다. 

저 상태에서 컴파일 에러가 날 것 같지만 컴파일 에러가 발생하지 않았다.

Whiteship whiteship = (Whiteship)thisClass;

하지만 이런 코드를 적어보면 컴파일 에러가 난다. 

그렇다면 null을 어느 타입으로든 캐스팅 하는건 문법적인 에러가 아닌가본데... 사실 그 안의 인스턴스를 사용하려들면 에러가 난다.

nullClass.getDeclaredMethods();

즉 이런 코드를 실행하면 NullPointerException이 발생하게 된다.

 따라서 null을 다른 타입으로 캐스팅을 하는 코드를 작성 하더라도 컴파일 에러가 나지는 않지만 그렇다고 해서 실제로 캐스팅이 된 것은 아니다.

대체 이런 짓을 왜 할까? 언제 null을 다른 타입으로 캐스팅 하는 코드를 써먹을 수 있을까?
퀴즈로 남겨줄까 한다. 후훗.

힌트이자 정답을 알려주자면...스프링의 ClassPathResource의 소스코드를 보면 나와있다.
저작자 표시
top


클래스 로더의 loadClass()와 findClass()

Java : 2010/02/25 13:30


참조: http://onjava.com/pub/a/onjava/2005/01/26/classloading.html?page=1

클래스와 데이타

모든 클래스는 first-class 자바 객체를 통해서 자신의 코드를 참조할 수 있다. 컴파일 할 때 컴파일러가 public static final class 라는 이름의 java.lang.Class 타입 필드를 추가해준다.

일단 한 번 클래스가 JVM으로 로딩되면, 같은 클래스는 다시 로딩 되지 않는다. 여기서 "같은 클래스"란 해당 클래스를 로딩한 클래스로더와 해당 클래스의 풀네임으로 구성된 식별자로 구분한다. 

예를 들어, WhiteshipClassLoader가 로딩한 springsprout 패키지의 Whiteship클래스는 (WhiteshipClassLoader, springsprout, Whiteship) 식별자를 가지게되고, SpringSproutClassLoader가 로딩한 springsprout 패키지의 Whiteship클래스는 (SpringSproutClassLoader, springsprout, Whiteship) 식별자를 가지기 때문에 별개의 클래스로 인식된다는 것이다.

클래스 로더

모든 클래스는 java,lang.ClassLoader의 인스턴스가 로딩한다.

부트스트랩 클래스 로더: java.lang.Object 같은 핵심 자바 클래스를 로딩한다. 런타임 클래스들은 JRE\lib\rt.jar에 들어있다. 접근이 안 됨. sout(String.class.getClassLoader())를 호출하면 null이 나온다. 

확장 클래스 로더: 핵심 자바 런타임 코드 이외의 확장 라이브러리를 java.ext.dirs 속성의 경로에 넣어둘 수 있다. ExtClassLoader는 java.ext.dirs에 위치한 모든 .jar 파일을 로딩한다.

애플리케이션 클래스 로더: AppClassLoader는 java.class.path 시스템 속성에 해당하는 경로의 모든 클래스를 로딩한다.

컨텍스트 클래스 로더: java.lang.Thread는 getContextClassLoader()라는 메서드를 가지고 있다. 이 메서드는 특정 쓰레드에서 사용중인 클래스로더를 반환해준다. 컨텍스트 클래스 로더는 쓰레드를 만들 때 사용한 클래스 로더이며, 기본적으로 parent 쓰레드의 클래스로더가 할당 된다. setContextClassLoader로 변경 가능하다. 

클래스로더 동작 방식

protected synchronized Class<?> loadClass
    (String name, boolean resolve)
    throws ClassNotFoundException{

    // First check if the class is already loaded
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
            // If still not found, then invoke
            // findClass to find the class.
            c = findClass(name);
        }
    }
    if (resolve) {
   resolveClass(c);
    }
    return c;
}

java.lang.ClassLoader의 loadClass() 메서드 코드이며 이 클래스를 확장하는 클래스는 findClass()를 재정의하는 것이 기본적이다. java.lang.ClassLoader의 findClass()는 ClassNotFoundException을 던진다.

findClass()를 구현할 떄는 특정 소스에서 바이트 코드를 가져오거나, 소스를 바이트 코드로 생성하거나, BCEL(Byte Code Engineering Library)를 사용할 수도 있다. 암튼.. 그렇게 해서 바이트 코드를 가져온 다음 defineClass() 메서드를 호출해서 byte 배열을 Class 인스턴스로 변환해야 한다. 

defineClass() 메서드 호출이 중요한데, 이 것을 호출한 클래스 로더가 바로 해당 클래스의 defining 클래스 로더가 되는 것이며 이 defining 클래스로더가 다를 경우 다른 클래스로 인식하게 된다. Whiteship.class.getClassLoader()를 실행했을 때 반환되는 클래스 로더가 바로 defining 클래스 로더다.

findClass 구현 예제 코드

    public Class findClass(String name)throws
        ClassNotFoundException{

        byte[] classBytes = findClassBytes(name);
        if (classBytes==null){
            throw new ClassNotFoundException();
        }
        else{
            return defineClass(name, classBytes,
                0, classBytes.length);
        }
    }


저작자 표시
top

Java : 2010/02/25 13:30 Trackback. : Comment.

자바 클래스로더 입문 퀴즈 정리

Java : 2010/02/23 22:30


  1. 21:15:40 [ClassLoader 퀴즈 끝] SpringSprout와 WhiteshipFactory가 참조하는 Whiteship은 누구인가.
  2. 2010/02/22 [ClassLoader 퀴즈 6] SpringSprout는 과연 Whiteship의 이름을 알 수 있을까? (2)
  3. 2010/02/22 [ClassLoader 퀴즈 5] Whiteship은 언제 로딩 될까? (2)
  4. 2010/02/21 [ClassLoader 퀴즈 4] SpringSprout가 알고 있는 Whiteship은 누구인가? (9)
  5. 2010/02/19 [ClassLoader 퀴즈 3] Whiteship은 Whiteship일까 아닐까? (2)
  6. 2010/02/19 [ClassLoader 퀴즈 2] Whiteship은 내가 데려왔다!! (5)
  7. 2010/02/19 [ClassLoader 퀴즈 1] Whiteship은 대체 누가 데려온 것일까? (8)

입문 과정 퀴즈를 푸는데 필요한 학습 자료
- ClassLoader APi, 소스 학습
- URLClassLoader API, 소스 학습
- http://docs.jboss.org/jbossas/jboss4guide/r1/html/ch2.chapter.html#d0e2314

과제
- 위 Jboss 문서 2.2.2를 참조하여 클래스 로딩 관련 주요 예외 3종 세트 재현하기
- ClassCastException
- IllegalAccessException
- LinkageError

파이팅입니다.
저작자 표시
top


[ClassLoader 퀴즈 끝] SpringSprout와 WhiteshipFactory가 참조하는 Whiteship은 누구인가.

Java : 2010/02/23 21:15


참조: http://www.redhat.com/docs/manuals/jboss/jboss-eap-4.2/doc/Server_Configuration_Guide/Class_Loading_and_Types_in_Java-LinkageErrors___Making_Sure_You_Are_Who_You_Say_You_Are.html#LinkageErrors___Making_Sure_You_Are_Who_You_Say_You_Are-Classes_demonstrating_the_need_for_loading_constraints

   @Test
    public void linkageError() throws Exception {
        FlaggedFileUrlClassLoader cl1 = new FlaggedFileUrlClassLoader("C:/intellij9-workspace/springsprout2/temp2/");
        FileUrlClassLoader cl0 = new FileUrlClassLoader("C:/intellij9-workspace/springsprout2/temp/", cl1);
      
        Class springSproutClass = cl0.loadClass("SpringSprout");
        Object springSprout = springSproutClass.newInstance();
        springSprout .getMethod("link").invoke(o1);
    }



public class SpringSprout {

    public void link(){
        System.out.println(Whiteship.class.getClassLoader() + " SpringSprout's Whiteship");
        Whiteship w = WhiteshipFactory.getWhiteship();
    }

}

public class Whiteship{
   
}

public class WhiteshipFactory {

    public static Whiteship getWhiteship(){
        System.out.println(Whiteship.class.getClassLoader() + " WhiteshipFactory's Whiteship");
        return new Whiteship();
    }
}

굵은 글씨 부분에서 아주 기가막힌 일이 벌어지도록 FlaggedFileUrlClassLaoder(FCL)를 만들었고, 그것으로 delegate하여 클래스를 로딩하는 UrlClassLoader(UCL)를 사용하여 SpringSprout 클래스를 로딩했습니다.

(parent 구조)

UCL -> FCL -> null

FCL은 temp2 폴더를 클래스패스로 취하며, UCL은 temp 폴더를 클래스패스로 취하고 있습니다.
temp2에는 SpringSprout가 참조하는 WhiteshipFactory가 들어있고..
temp에는 SpringSprout와 Whiteship이 들어있습니다.

(dependency)

SpringSprout -> WhiteshipFactory, Whiteship
WhiteshipFactoru -> Whiteship

1. SpringSprout는 UCL이 로딩합니다. UCL이 SpringSprout의 Defining CL이 되며, 동시에 Initial CL이 됩니다.

2. SpringSprout의 Defining CL인 UCL이 SpringSprout가 의존하는 WhiteshipFactory와 Whiteship을 로딩합니다.
- 이때 WhiteshipFactory는 UCL의 parent인 FCL의 클래스패스에 있기 때문에 FCL이 최초로 로딩합니다. 따라서 Defining CL는 FCL이 됩니다.
- Whiteship은 UCL의 클래스패스에 있기 떄문에 Defining CL은 UCL입니다.

3. WhiteshipFactory가 의존하는 Whiteship을 로딩합니다.
- 이때 WhiteshipFactory의 defining CL인 FCL이 Whiteship 로딩을 시도하는데 원래대로라면 FCL의 클래스패스에 Whiteship이 없기 때문에 ClassNotFoundException이 나와야 정상이지만 제가 만든 FCL은 자기가 로딩해버립니다. 따라서 WhiteshipFactory가 참조하는 Whiteship의 Defining CL은 FCL입니다.

4. SpringSprout의 link() 메서드를 실행합니다.
- Whiteship(UCL) = Whiteship(FCL) 최종적으로 이런 공식이 되버립니다.
- 이 위험한 순간 레퍼런스를 대입하기 전에.. loader constraint violation이 발동하여 LinkageError를 발생시킵니다.
- Whiteship 타입을 여러 클래스로더에서 안전하게 사용할 수도록 확인해주는 이 에러는 Liang와 Bracha가쓴 논문을 바탕으로 개선되었습니다. JVM 1.1 이하 버전에서는 이런걸 그냥 허용했다고 합니다.

loader constraint violation
- 레퍼런스를 대입하기 전에 해당 타입을 정의한 defining CL들이 다른지 확인하여 다르다면 에러를 발생시켜 줍니다.
- 이때는 initative CL이 다르던 말던 상관없습니다.


LinkageError를 재현해 보느라.. 오늘 하루를 몽땅 소진했습니다.

이번 퀴즈는.. 이 방법 말고 좀 더 간단하게 LinkageError를 재현해주는 분이 맞추시는 겁니다.
부디 나타나 주시길....
저작자 표시
top

Java : 2010/02/23 21:15 Trackback. : Comment.

[ClassLoader 퀴즈 6] SpringSprout는 과연 Whiteship의 이름을 알 수 있을까?

Java : 2010/02/22 15:15


    @Test
    public void illegalAccessException() throws Exception {
        FileUrlClassLoader fUCL1 = new FileUrlClassLoader(CLASSPATH, null);
        Class whiteshipClass = fUCL1.loadClass(WHITESHIP);
        Object whiteship = whiteshipClass.newInstance();

        System.out.println(this.getClass() + " " + this.getClass().getClassLoader());
        System.out.println(whiteshipClass + " " + whiteshipClass.getClassLoader());

        whiteshipClass.getDeclaredField("name").get(whiteship);
    }

public class Whiteship2 {

    String name = "keesun";
}


이 테스트를 실행한 결과 중 일부는 다음과 같습니다.

class sandbox.classloader.ClassloaderTest sun.misc.Launcher$AppClassLoader@19821f <- CL1
class sandbox.classloader.Whiteship2 ㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁ<- CL

1. CL의 값은 CL1일까요? 아니면 전혀 다른 값?
2. 그 뒤 코드의 굵은 줄을 실행한 결과는 어찌됐을까요? 무사히 실행하고 keesun을 가져왔을까요? 에러가 났을까요? 에러가 났다면 어떤 에러가 났을까요?


저작자 표시
top


[ClassLoader 퀴즈 5] Whiteship은 언제 로딩 될까?

Java : 2010/02/22 15:07


public class SpringSprout {

    Whiteship2 whiteship;

    public void makeWhiteship(){
        whiteship = new Whiteship2();
    }
}

이런 클래스가 있을 때 SpringSprout가 참조하는 Whiteship2는 과연 언제 클래스로더에 의해 로딩이 될까요? SpringSprout를 로딩할 때? SpringSprout 객체를 생성할 때? Whiteship2 인스턴스를 생성할 때. 이 중 하나가 아닐까요?

   @Test
    public void whenDoseTheDependeciesWillBeLoaded() throws Exception {
        FileUrlClassLoader fUCL1 = new FileUrlClassLoader(WIN_TEMP_CLASSPATH);
        Class springSproutClass1 = fUCL1.loadClass(TEMP_SPRING_SPROUT);
        Object springSprout1 = springSproutClass1.newInstance();

        assertThat(fUCL1.isLoadedClass(TEMP_WHITESHIP), is(B1));

        Object whiteship1 = springSproutClass1.getMethod("makeWhiteship").invoke(springSprout1, null);

        assertThat(fUCL1.isLoadedClass(TEMP_WHITESHIP), is(B2));

        System.out.println(fUCL1.loadClass(TEMP_WHITESHIP).getClassLoader());
        System.out.println(fUCL1.loadClass(TEMP_SPRING_SPROUT).getClassLoader());
    }

그래서 위와 같은 테스트를 만들어봤습니다.
이 테스트가 통과하려면 B1과 B2에는 각각 어떤 값이 들어가야 할까요?

기타 조건은 이전 글과 동일합니다.
저작자 표시
top


[ClassLoader 퀴즈 4] SpringSprout가 알고 있는 Whiteship은 누구인가?

Java : 2010/02/21 21:29


퀴즈 3번을 다시 낸거라 생각히셔도 되겠습니다. 2, 3번을 푸셨다면 이 문제도 역시 간단하게 푸실 수 있을 겁니다.

   @Test
    public void classCastException() throws Exception {
        URLClassLoader uCL1 = new FileUrlClassLoader(TEMP_CLASSPATH);
        Class springSproutClass = uCL1.loadClass(TEMP_SPRING_SPROUT);
        Object springSprout = springSproutClass.newInstance();
        Class whiteshipClass1 = uCL1.loadClass(TEMP_WHITESHIP);
        Object whiteship1 = whiteshipClass1.newInstance();

        URLClassLoader uCL2 = new FileUrlClassLoader(TEMP_CLASSPATH);
        Class whiteshipClass2 = uCL2.loadClass(TEMP_WHITESHIP);
        Object whiteship2 = whiteshipClass2.newInstance();

        Method cast = springSproutClass.getMethod("castWhiteship", Object.class);
        cast.invoke(springSprout, whiteship1); // C1
        cast.invoke(springSprout, whiteship2); // C2
    }

TEMP_CLASSPATH는 이 프로젝트의 기본 클래스패스가 아닙니다.
TEMP_SPRING_SPROUT는 SpringSprout.java 클래스의 이름이고
TEMP_WHITESHIP은 Whiteship.java 클래스의 이름을 줍니다.

SpringSprout 클래스에 있는 castWhiteship 메서드는 다음과 같습니다.

    public void castWhiteship(Object object){
        Whiteship2 whiteship = (Whiteship2)object;
    }

C1에서는 아무 일 없지만 C2에서는 바로 저 부분에서 ClassCastException이 발생하죠. 그 이유는 퀴즈 3에 대한 답으로 성윤군이 달아줬지요.

그럼 이번 문제는 조금 다르게...

public void hi(Whiteship whipteship){
    // 띵까 띵까..
}

SpringSprout에 이런 메서드가 있을 때 저 메서드를 호출할 때 whiteship1와 whiteship2 객체를 각각 전달해 준다면 어떤 일이 벌어질까요?

        Method cast = springSproutClass.getMethod("hi", whiteshipClass1);
        cast.invoke(springSprout, whiteship1); // H1
        cast.invoke(springSprout, whiteship2); // H2


저작자 표시
top


[ClassLoader 퀴즈 3] Whiteship은 Whiteship일까 아닐까?

Java : 2010/02/19 16:46


퀴즈 2번 문제의 정답을 맞추신 분이라면 클래스패스와 클래스로더의 관계에 대해 잘 알고 계신 것 같습니다. temp 폴더는 클래스패스에 들어있지 않다고 했었죠. 따라서 URLClassLoader.loadClass를 실행했을 때 그것의 상위 클래스로더인 AppClassLoader가 읽어오지 못하고 자기 자신이 가져오게 됩니다. 

따라서 결국 제가 원하던대로 whiteshipClass1과 whtieshipClass2는 각각 다른 클래스로더(uCL1, uCL2)들이 가져오게 됐습니다.

이쯤 말씀드렸으니.. 정답은 뭐... 당연한 거니까 패스.

    @Test(expected = ClassCastException.class)
    public void classCastException() throws Exception {
        URL url = new URL("file:C:/intellij9-workspace/springsprout2/temp/");
        URLClassLoader uCL1 = new URLClassLoader(new URL[]{url});
        Class whiteshipClass1 = uCL1.loadClass("Whiteship");
        Object whiteship1 = whiteshipClass1.newInstance();

        URLClassLoader uCL2 = new URLClassLoader(new URL[]{url});
        Class whiteshipClass2 = uCL2.loadClass("Whiteship");
        Object whiteship2 = whiteshipClass2.newInstance();

        assertThat(whiteshipClass1, is(not(whiteshipClass2)));
        whiteshipClass1.cast(whiteship2);
    }

이번에는 주관식입니다.

위 테스트는 통과 합니다.왜 통과하는 걸까요? 
즉, 왜 ClassCastException이 발생하는 걸까요?
저작자 표시
top


[ClassLoader 퀴즈 2] Whiteship은 내가 데려왔다!!

Java : 2010/02/19 16:34


첫번째 문제를 맞추셨다면 클래스로더 계층 구조를 이해하고 있다고 볼 수 있겠습니다. 제가 작성한 코드를
보면 제가 어떤걸 원했는지 짐작하실 수 있을 겁니다.

    @Test
    public void loadLocal() throws Exception {
        URL url = new URL("file:C:/intellij9-workspace/springsprout2/test/sandbox/classloader");
        URLClassLoader uCL1 = new URLClassLoader(new URL[]{url});
        Class whiteshipClass1 = uCL1.loadClass("sandbox.classloader.Whiteship");

        URLClassLoader uCL2 = new URLClassLoader(new URL[]{url});
        Class whiteshipClass2 = uCL2.loadClass("sandbox.classloader.Whiteship");

        System.out.println("URLClassLoader1: " + uCL1);
        System.out.println("URLClassLoader2: " + uCL2);
        System.out.println("URLClassLoader1's parent : " + uCL1.getParent());
        System.out.println("URLClassLoader2's parent : " + uCL2.getParent());
        System.out.println("whiteshipClass1 loaded by: " + whiteshipClass1.getClassLoader());
        System.out.println("whiteshipClass2 loaded by: " + whiteshipClass2.getClassLoader());
    }

whiteshipClass1과 whiteshipClass2를 각기 다른 ClassLoader(URLClassLoader)를 사용해서 가져오고 싶었던 겁니다. 하지만 결과는? 두 클래스 모두 uCL1과 uCL2의 상위 클래스로더가 가져왔기 때문에 정답은 5번이며 제가 원하던 대로 동작하지 않았습니다. 좀 더 정확하게 보고 싶으시다면 위와 같이 uCL1과 uCL2의 parent까지 찍어보시면 C3, C4와 같다는 걸 확인할 수 있을 겁니다.

    @Test
    public void load() throws Exception {
        URL url = new URL("file:C:/intellij9-workspace/springsprout2/temp/");
        URLClassLoader uCL1 = new URLClassLoader(new URL[]{url});
        Class whiteshipClass1 = uCL1.loadClass("Whiteship");

        URLClassLoader uCL2 = new URLClassLoader(new URL[]{url});
        Class whiteshipClass2 = uCL2.loadClass("Whiteship");

        System.out.println("URLClassLoader1: " + uCL1); // C1
        System.out.println("URLClassLoader2: " + uCL2); // C2
        System.out.println("URLClassLoader1's parent : " + uCL1.getParent()); // C3
        System.out.println("URLClassLoader2's parent : " + uCL2.getParent()); // C4
        System.out.println("whiteshipClass1 loaded by: " + whiteshipClass1.getClassLoader()); // C5
        System.out.println("whiteshipClass2 loaded by: " + whiteshipClass2.getClassLoader()); // C6
    }

자 이번에는 조금 다릅니다. temp라는 폴더는 클래스패스로 잡혀있지 않습니다. 그 안에 Whtieship.java를 넣어뒀고 콘솔에서 컴파일해서 .class 파일을 만들어뒀습니다.

다음 중 참인 것은?
1. C1과 C5가 같다.
2. C2과 C6이 같다.
3. C3과 C5가 같다.
4. C4와 C6이 같다.
5. C3과 C4가 같다.
6. C5와 C6이 같다.

과연 이번에는 내가 원했던 결과를 얻을려나...
정답은 역시 다음 글에서 공개. to be continued~!

저작자 표시
top


[ClassLoader 퀴즈 1] Whiteship은 대체 누가 데려온 것일까?

Java : 2010/02/19 14:48


어제 면접 볼 때 받은 질문이기도 하다. 지금 내가 있는 회사에 어떻게 들어가게 됐으며 누가 데려갔고 그 사람을 어떻게 알게 됐느냐는 질문을 받았었다. 당시 난 클래스로더가 떠올랐다. 대체 누가 날 로딩한 걸까. 사부? Toby? 그 둘은 같은 사람일까?

    @Test
    public void appClassLoader() throws Exception {
        URL url = new URL("file:C:/intellij9-workspace/springsprout2/test/sandbox/classloader");
        URLClassLoader uCL1 = new URLClassLoader(new URL[]{url});
        Class whiteshipClass1 = uCL1.loadClass("sandbox.classloader.Whiteship");
       
        URLClassLoader uCL2 = new URLClassLoader(new URL[]{url});
        Class whiteshipClass2 = uCL2.loadClass("sandbox.classloader.Whiteship");
       
        System.out.println("URLClassLoader1: " + uCL1); // C1
        System.out.println("URLClassLoader2: " + uCL2); // C2
        System.out.println("whiteshipClass1 loaded by: " + whiteshipClass1.getClassLoader()); // C3
        System.out.println("whiteshipClass2 loaded by: " + whiteshipClass2.getClassLoader()); // C4
    }

편의상 콘솔에 찍히는 객체 레퍼런스를 C1, C2, C3, C4라고 표기하겠다.
test 이하의 폴더는 현재 클래스패스로 잡혀있는 상태이다.

다음 중 참인 것은?
1. C1과 C3은 같다.
2. C2와 C4는 같다.
3. C1과 C3, C4가 같다.
4. C2와 C3, C4가 같다.
5. C3과 C4는 같다.

정답은 다음 글에서 공개. To be continued!


top


[Java] ClassLoader API

Java : 2010/02/17 18:36


http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html

클래스를 로딩하는 책임을 지니고 있는 추상 클래스. 

기본전략: 바이너리 이름(String)을 받아서 파일 이름으로 바꾸고 파일 시스템에서 해당하는 이름의 클래스 파일을 읽어들인다.

위임 모델(delegation model)을 사용하여 클래스와 리소스를 찾는다. 각각의 ClassLoader 인스턴스는 연관된 상위(parent) 클래스 로더를 가지고 있다. 자신이 찾아 보기전에 상위 클래스 로더에 요청하여 먼저 찾아본다. VM 내장 클래스 로더인 "부트스트랩 클래스 로더"는 상위 클래스 로더가 없고 자신이 다른 ClassLoader 인스턴스의 상위가 된다.

보통 JVM은 플랫폼-독립적인 방식으로 로컬 파일 시스템에서 클래스를 읽어들인다. 예를 들어 유닉스 시스템에서 VM은 CLASSPATH 환경 변수에 정의되어 있는 디렉토리에서 클래스를 로딩한다.

하지만 어떤 클래스들은 파일에서 읽어오지 않고 네트워크에서 가져오거나 애플리케이션이 동작하면서 만들어지는 것도 있다. defineClass 메서드는 바이트 배열을 Class 클래스 인스턴스로 변환한다. Class.newInstance를 사용하여 그렇게 새로 정의된 클래스 인스턴스를 만들 수 있다.

클래스로더에 의해 만들어지는 객체의 메소드와 생성자는 다른 클래스를 참조할 수도 있다. 그렇게 참조하는 클래스들을 판단하기 위해 VM은 원래 클래스를 생성한 클래스 로더의 loadClass 메서드를 호출한다.

예를 들어 네트워크 클래스 로더를 만들어 다른 서버에서 클래스 파일을 다운로드 할 수도 있다. 다음은 예제 코드다.

ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();

네트워크 클래스 로더는 반드시 findClass와 네트워크에서 클래스를 읽어올 loadClassData를 정의해야한다. 바이트코드를 다운로드 한다음 defineClass를 사용하여 class 인스턴스를 만들어야 한다. 다음은 예제 구현체다.

     class NetworkClassLoader extends ClassLoader {
         String host;
         int port;

         public Class findClass(String name) {
             byte[] b = loadClassData(name);
             return defineClass(name, b, 0, b.length);
         }

         private byte[] loadClassData(String name) {
             // load the class data from the connection
              . . .
         }
     }
 

바이너리 이름

클래스로더에 전달되는 문자열로 표현한 클래스 이름은 다음에 정의된 자바 언어 표준을 따라야 한다.

예)
   "java.lang.String"
   "javax.swing.JSpinner$DefaultEditor"
   "java.security.KeyStore$Builder$FileBuilder$1"
   "java.net.URLClassLoader$3$1"

defineClass

- protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError

바이트를 Class 클래스의 인스턴스로 변환한다. Class는 resolve를 한 다음에 사용해야 한다. 

loadClass

- public Class<?> loadClass(String name) throws ClassNotFountException

loadClass(naem, false); 호출

- public Class<?> loadClass(String name, boolean resolve) throws ClassNotFountException

기본 동작은 다음과 같다.
1. findLoadedClass(String)을 호출하여 클래스가 이미 로딩되었는지 확인한다.
2. 상위 클래스 로더의 loadClass 메서드를 호출한다. 만약 상위 클래스 로더가 null이면 VM 내장 클래스 로더를 사용한다.
3. findClass(String) 메서드를 사용하여 클래스를 찾는다.

만약에 위 과정을 통해 클래스를 찾은 뒤에 resolve 플래그가 true면 반환받은 Class 객체를 사용하여resolveClass(Class) 메서드를 호출한다.

클래스로더의 하위 클래스들은 이 메서드가 아니라 findClass(String)을 재정의 할 것을 권한다.
(이런걸 지켜야 리스코프 원칙을 지켰다고 하던가...)

findLoadedClass

- protected final Class<?> findLoadedClass(String name)

만약 이 클래스로더가 JVM에 initiating 로더로 기록되어 있다면 name에 해당하는 클래스를 반환한다. 그렇지 않으면 null을 반환한다.

findClass

- protected Class<?> findClass(String name) throws ClassNotFoundException

기본 구현체는 ClassNotFoundException을 던진다. 따라서 클래스 로더를 확장하는 클래스가 이것을 구현하여 delegation 모델을 따르는 구현체를 만드는데 사용해야 한다. loadClass 메서드에서 상위 클래스 로더를 확인 한 뒤에 호출된다.
(그냥 클래스만 찾으면 되지 꼭 delegation 모델을 따라야 하는건가... 사실 loadClass가 public이라 그것도 재정의하면 그만인것을. 강요하려면 하고 말려면 말지 어중간한거 아닌가..)

resolveClass

- protected final void resolveClass(Class<?> c)

해당 클래스를 링크한다. 이 메서드는 클래스 로더가 클래스를 링크할 때 사용된다. 만약 클래스 c가 이미 링크되어 있다면 이 메서드는 그냥 끝난다. 그렇지 않은 경우라면 자바 언어 스팩의 "Execution" 챕터에 기술되어 있는대로 클래스를 링크한다.

링크


링크하기(Linking)란 클래스 또는 인터페이스 타입의 바이너리를 가져와서 JVM의 런타임에 연결하여 실행 할 수 있는 상태로 만드는 것이다. 

링크 과정은 세 가지 세부 활동으로 구성된다: verification, preparation, resolution

링크 활동 구현내용은 달라질 수 있다. 예를 들어 클래스가 사용되는 순간에 개별적으로 클래스나 인터페이스에 있는 심볼릭 레퍼런스를 확정하거나(lazy or late resolution), 검증하고나서 바로 확정할 수도 있다.(static). 즉 어떤 구현체에서는 클래스나 인터페이스를 초기화 한 이후에도 확정(resolution) 프로세스가 계속 될 수 있다는 것이다.

Verification, Preparation, Resolution


Verification: 클래스나 인터페이스의 바이너리가 구조적으로 올바른지 확인한다. 검증에 실패하면 LinkageError의 하위 클래스 중 하나인 VerifyError가 발생한다.

Preparation: 클래스나 인터페이스의 static 필드를 만들고 그런 필드들을 기본 값으로 초기화 하는 과정이 포함된다. 이 과정 중에 JVM 코드를 실행할 필요가 없다. 명시적인 static 필드 initializer는 이 과정이 아니라 initialization 과정에서 실행된다.

Resolution: 심볼릭 레퍼런스는 resolution단계를 지나야지 사용될 수 있다. 심볼릭 레퍼런스가 유효하고 반복적으로 사용되면 다이렉트 레퍼런스로 교체되어 보다 효율적으로 처리된다.

만약 이 단계를 지나다가 IncompatibleClassChangeError를 포함한 이 하위 에러들이 발생할 수 있다. IllegalAccessErrorInstantiationError, NoSuchFieldError, NoSuchMethodError

추가적으로 구현체를 찾을 수 없는 native 메서드를 선언한 클래스에서는 UnsatisfiedLinkError가 발생할 수 있다. 

하악 하악.. 오늘은 여기까지.
top


[NullPE] SpEL 때문에 고민 해결

Java : 2010/02/10 18:47


애초에 MVEL.eval() 내부에서 레퍼런스를 타고 갈 때 null 처리를 해줄 수 있는 방법을 찾았다면 Null Object 패턴까지 적용할 생각은 안 들었을테고, 그럼 Null Object 패턴을 어떻게 적용해야 깔끔한가?에 대한 고민도 안했을 텐데.. 그 실마리가 바로 MVEL 대신 SpEL을 사용하는 것이었습니다. 이론... OTL

SpEL은 스프링 3.0에 새로 추가된 기능인데 이 녀석이 할 수 있는 멋진 일들에 비해 아직 이것을 어떻게 활용해야 할지 제대로 파악이 되지 않은 상태입니다.(제가 파악이 안됐다는 것이지 일반적으로 그렇다는 것이 아니오니 오해 마시기 바랍니다.)


/**
 * Created by IntelliJ IDEA.
 * User: whiteship
 * Date: 2010. 2. 10
 * Time: 오후 6:38:11
 */
public class SpELNullTest {

    @Test
    public void SafeNavigation(){
        ExpressionParser parser = new SpelExpressionParser();

        SpringSprout ss = new SpringSprout();
        StandardEvaluationContext context = new StandardEvaluationContext(ss);

        String myName = parser.parseExpression("whiteship?.name").getValue(context, String.class);
        assertThat(myName, is(nullValue()));
    }

    class Whiteship{
        String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    class SpringSprout{
        Whiteship whiteship;

        public Whiteship getWhiteship() {
            return whiteship;
        }

        public void setWhiteship(Whiteship whiteship) {
            this.whiteship = whiteship;
        }
    }

}

흠냐.. 괜찮군요!

?. 가 아니라 그냥 . 로 연결하면 null 객체에 대고 getName()을 호출하는 격이라 에러가 납니다. ?, 연산식이 SpEL에서 지원하는 Safe Navigation이라는 녀석입니다.

이것 말고도 삼항 연산식도 제공하며 삼항 연산식에서 반복을 줄일 엘비스 연산식도 제공합니다. 아무튼 스프링은 참.. 멋지네요. +_+

덕분에 NullPE 고민 해결입니다.
top

TAG NullFE, SpEL
Java : 2010/02/10 18:47 Trackback. : Comment.

[NullPE] NullPointerException 때문에 고민 1

Java : 2010/02/10 11:46


레퍼런스를 사용하는 쪽에서 null 체크를 하면 되지만.. 그러지 못하는 경우가 있어서..

MVEL.eval(column.getPath(), entity, Integer.class)

column의 getPath()에는 item의 속성을 타고 타고 들어가는 경로도 들어옵니다. 그럴 때 타고 가는 중간에 null을 만나면 에러가 나죠. 제어할 수가 있다면 null 체크 하는 구문만 넣어서 버그를 수정하겠지만.. 지금은.. 좀.. @_@;;

그래서 Null Object Pattern이라는게 생각났고 이걸 적용해볼까 했습니다.

http://www.refactoring.com/catalog/introduceNullObject.html
http://en.wikipedia.org/wiki/Null_Object_pattern

Item 클래스의 레퍼런스 타입 변수를 반환하는 게터들이 다음과 같이 바꼈습니다.

    public Code getDelivery() {
        return NullObjectUtil.eval(delivery);
    }

    public Code getCar() {
        return NullObjectUtil.eval(car);
    }

    public Code getItemGroup() {
        return NullObjectUtil.eval(itemGroup);
    }

    public Supp getSupp() {
        return NullObjectUtil.eval(supp);
    }

public class NullObjectUtil {
   
    public static Code eval(Code code) {
        return code != null ? code : new NullCode();
    }

    public static Supp eval(Supp supp) {
        return supp != null ? supp : new NullSupp();
    }
}

그리고 domain.nullobject 패키지를 만들고

public class NullCode extends Code {
}

public class NullSupp extends Supp {
}

이렇게 NullObject 클래스들을 만들어 줬습니다.

이렇게 할지;;;

public Code getProcess(){
    return Code.basicIfNull(process);
}

이런식으로 할지. 고민입니다. 어떤게 왜 나은걸까나;; @_@


top


WEB-INF/urlrewrite.xml 파일 설정하기

Java : 2010/02/09 18:02


http://tuckey.org/urlrewrite/manual/3.0/

일욜에 보던건데. 이제서야 다시 정리하고 싶어져서;; 쿨럭;;

    <?xml version="1.0" encoding="utf-8"?>

    <!DOCTYPE urlrewrite
        PUBLIC "-//tuckey.org//DTD UrlRewrite 3.0//EN"
        "http://tuckey.org/res/dtds/urlrewrite3.0.dtd">

    <urlrewrite>

        <rule>
           <from>^/some/olddir/(.*)$</from>
           <to type="redirect">/very/newdir/$1</to>
        </rule>

        <rule match-type="wildcard">
           <from>/blog/archive/**</from>
           <to type="redirect">/roller/history/$1</to>
        </rule>

    </urlrewrite>

파일 이름은 urlrewrite.xml로 해야하고 위와 같은 형태로 작성하면 됩니다. urlrewrite 엘리먼트가 반드시 들어있어야 하며, 그 안에는 최소한 한 개의 rule 엘리먼트가 들어있어야 합니다.

rule 엘리먼트 안에는 from과 to가 각각 하나씩 반드시 있어야 하며, condition와 set 엘리먼트는 없거나 여러 개 있어도 됩니다.

rule 안에 정의한 condition이 만족하면 from이 요청 URL에 적용되고 set 엘리먼트가 실행되고 to가 적용 됨.

아래는 수도 코드

    Pattern.compile(<from> element);
    pattern.matcher(each request url);
    matcher.replaceAll(<to> element);
    if ( <condition> elements match && pattern matched ) {
       execute <run> elements (if any)
       perform <to> element (if any)
    }




top

Java : 2010/02/09 18:02 Trackback. : Comment.

UrlRewriterFilter 옵션

Java : 2010/02/07 20:12


http://tuckey.org/urlrewrite/manual/2.6/

UrlRewriterFilter 설정 파일을 다시 읽어 들일지 확인하는 주기 설정.
0은 계속해서 확인하고, 빈 값 또는 이 속성을 설정하지 않으면 다시 읽어 들일지 확인하지 않음.

    <init-param>
    <param-name>confReloadCheckInterval</param-name>
    <param-value>60</param-value>
    </init-param>

로그 수준을 설정한다.
사용할 수 있는 값: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, log4j, commons, sysout:{level} (ie, sysout:DEBUG)
기본값은 INFO

    <init-param>
    <param-name>logLevel</param-name>
    <param-value>DEBUG</param-value>
    </init-param>

status 페이지(UrlRewriter 설정 페이지 /rewrite-status)를 사용하지 않게 설정할 수 있다.
사용할 수 있는 값: true, false
기본값: true

    <init-param>
    <param-name>statusEnabled</param-name>
    <param-value>true</param-value>
    </init-param>


애플리케이션에서 필요한 경로와 중복되지 않도록 status 페이지 요청을 다른 경로로 변경할 수 있다.
반드시 /로 시작해야 한다.

    <init-param>
            <param-name>statusPath</param-name>
            <param-value>/status</param-value>
    </init-param>


top

Java : 2010/02/07 20:12 Trackback. : Comment.

UrlRewriterFilter 설치하기

Java : 2010/02/07 17:20


http://tuckey.org/urlrewrite/manual/2.6/

1. 다운로드 또는 메이븐

        <dependency>
            <groupId>org.tuckey</groupId>
            <artifactId>urlrewritefilter</artifactId>
            <version>3.1.0</version>
        </dependency>

2. web.xml에 필터 맵핑 추가하기

    <filter>
        <filter-name>UrlRewriteFilter</filter-name>
        <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>UrlRewriteFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

3. WEB-INF 폴더에 urlrewrite.xml 파일 만들기

4. 애플리케이션 재시작하고 http://127.0.0.1:8080/rewrite-status 확인하기
top

Java : 2010/02/07 17:20 Trackback. : Comment.

"서블릿 매핑 규칙"과 "필터 순서 정하기 규칙"

Java : 2010/02/07 16:36


둘 다 web.xml과 관련이 있는 것인데 '서블릿 매핑 규칙'은 /* 와 /foo 로 서블릿 매핑이 되어 있을 때 만약 http://springsprout.org/foo 라는 요청이 왔을 때 /*로 매핑되어 있는 서블릿으로 갈 것이냐... /foo로 매핑되어있는 서블릿으로 갈것이냐와 관련이 있는 규칙입니다. web.xml에 설정되어 있는 순서에 따라 달라질까요? 글쎄요..ㅋ

'필터 순서 정하기 규칙'은 위와는 다릅니다. 서블릿 매핑은 결국 어떤 것 하나를 선택하는 문제지만 이건 여러 개가 선택되어도 문제가 없기 때문이죠. 대신 그들 사이의 순서가... 중요할 수도 있을텐데 그 순서가 어떻게 정해지는지 이해해야될 것 같습니다. (스프링처럼 Ordered 인터페이스를 적용해 줄 것이지 복잡하게 이게 뭐람 @_@)

서블릿 매핑 규칙(헤드 퍼스트 서블릿과 JSP 영문판 2판 619 페이지)

1. 먼저 요청한 URL과 정확히 일치하는 매핑을 찾습니다. 그게 없다면 디렉토리가 일치하는 매핑을 찾습니다. 그게 없다면 확장자가 일치하는 매핑을 찾습니다.

2. 만약 요청이 여러 개의 디렉토리 매핑 <url-patterm> 과 일치하다면, 그 중에서 가장 긴 매핑을 선택합니다. 예를 들어 /foo/bar/myStuff.do 요청이 왔을 때 /foo/bar/* 매핑이 /foo/* 매핑을 이기게 됩니다. 가장 구체적인 것이 위너가 됩니다.

필터 순서 정하기 규칙(헤드 퍼스트 서블릿과 JSP 영문판 2판 710 페이지)

1) 해당 URL 패턴에 대응하는 모든 필터는 DD(보통 web.xml)에 정의되어 있는 순서대로 체인에 놓는다. 즉 필터 선언 중 <url-pattern>을 사용하여 해당 순서대로 필터를 놓는다.

2) 위 과정이 끝난 뒤 <servlet-name>에 대응하는 것들을 DD에 정의되어 있는 순서대로 그 뒤에 이어서 놓는다.

해당 페이지에 퀴즈도 있으니 풀어보시면... 재미납니다.ㅋ
top


UrlRewriterFilter 소개

Java : 2010/02/07 16:00


http://tuckey.org/urlrewrite/

아파치 mod_rewrite와 같은 기능을 레진, 오리온, 톰캣 같은 J2EE 호환 웹 애플리케이션 서버에서 사용할 수 있도록 해주는 자바 웹 필터이다.

URL 재작성은 아파치 웹 서버에서 매우 흔히 사용하지만 자바 웹 애플리케이션 서버에서는 이용할 수 없었다.  이것을 사용하여 할 수 있는 주요 작업들은 다음과 같다.

- URL 간편화 / URL 추상화: URL을 간단하게 구성할 수 있고 애플리케이션의 기반 프레임워크 기술을 가릴 수 있다.
- 브라우저 감지: HTTP 헤더를 기반으로 URL을 재작성 할 수 있다.
- 날짜 기반 재작성: 날짜/시간을 기반으로 다른 URL로 포워딩 또는 리다이렉트 할 수 있다.
- 이동된 컨텐츠: 컨텐츠 이동을 편리하게 할 수 있다.
- 짧고/친화적인 URL(예, blah.com/latest 를 blah.com/download/ver1.2.46.2/setup.exe로 리다이렉트 시킬 수 있다.)
- 서블릿 매핑 엔진(Method Invocation 참조)

WEB-INF 디렉토리에 있는 urlrewrite.xml 이라는 xml 파일 한 개를 사용하여 설정한다.

대부분의 매개변수는 Perl5 스타일 정규 표현식 또는 와일드카드 표현식을 사용할 수 있다. 이 점이 매우 유용하다.




top

Java : 2010/02/07 16:00 Trackback. : Comment.

자바 System.out.println 콘솔 출력 가로채기

Java : 2010/01/25 16:49


public class Sout {

    public void hi(){
        System.out.println("hi");
    }

}

이렇게 콘솔에 어떤 메시지를 출력하는 경우가 있을 때 저걸 애플리케이션에서 캡춰할 수 있는 걸 만들어 보는 과제가 떨어졌다.

public class SoutTest {

    SoutInterceptor soutInterceptor = new SoutInterceptor();

    @Test
    public void sout() throws IOException {
        soutInterceptor.active();

        Sout sout = new Sout();
       sout.hi();

        assertThat(soutInterceptor.getMessages(), is("hi"));
    }

}

간단하게 테스트를 만들고 돌려보기 시작했다. 캡춰한 메시지를 어떻게 가져올지가 고민이었는데 그냥 생각난 가장 단순한 방법으로 가져오게했다. 이제 남은건 SoutInterceptor라는 녀석을 만드는 일이다. 뭘 어찌해야 한담 @_@;

가장 먼저 떠오른 방법은 콘솔을 모니터링 하는것이다. 그런데.. 넘 복잡할 것같고 막연하다. 다음으로 떠오른게 AOP. out.println()을 할 때 가로챌 수 있지 않을까? 하지만 힌트가 전달됐다. out을 교체할 수 있단다. 크헉.. 이건 뭐 거의 정답 수준의 힌트이지만 그렇게 간단하지는 않다고 한다. 좋아 해보자.

코딩은 구글신과 함께.. (또는 사부님 말씀대로 이클립스 코드를 뒤지면 나올지도 모른다. 사부님은 이미 뒤져본 것 같다. 자신이 생각한 방법과 동일한 방법을 사용했다고 한다. 어떤 건지는 안 찾아봐서 모르겠다;; 수천 수만 개나 되는 소스 코드를 받아오기도 귀찮고 그걸 IDE에 로딩하는데 엄청 오래 걸릴 것이며 잘못해서 뻑나거나 빌드가 안되고 컴파일 에러잡고 그러면서 삼천포로 가고 싶진 않았다.)

public class SoutInterceptor {

    private PipedInputStream pipedInputStream;
    private PrintStream originalPrint;

    public SoutInterceptor() {
        originalPrint = System.out;
        this.pipedInputStream = new PipedInputStream();
    }

    public String getMessages() throws IOException {
        byte[] messages = new byte[pipedInputStream.available()];
        pipedInputStream.read(messages, 0, messages.length);
        return new String(messages);
    }

    public void active() throws IOException {
        final PipedOutputStream pipedOutputStream = new PipedOutputStream(pipedInputStream);
        PrintStream saveStream = new PrintStream(pipedOutputStream) {
            @Override
            public void println(String x) {
                try {
                    pipedOutputStream.write(x.getBytes());
                } catch (IOException e) {
                    System.out.println("error");
                }
                originalPrint.println(x);
            }
        };
        System.setOut(saveStream);
    }

}

오호.. 잘 돌아간다.. +_+.

테스트를 좀 더 해보자.

        sout.hello();
       
        assertThat(soutInterceptor.getMessages(), is("hello"));

아래에 이렇게 추가하고 hello() 메서드 안에서는 hello를 출력하게 했다. 또 테스트가 잘 돌아간다. 흠.. 이제 맞게 한건가?

println(Stirng x)를 재정의 했는데 print(int), println(boolean) 를 호출할 때도 잘 동작한다. 왜그럴까?

top


클래스파일 보기

Java : 2009/07/20 17:10


javap -c 클래스파일명(확장자빼고)

예) javap -c Sample

이클립스에서 보려면 네비게이터 뷰에서 타겟 폴더로 이동해서 보시면 되죠.

public class JavaPSample {

    public void test(){
        String a = "a";
        String b = "b";
        String c = a + b;
        String d = "ab";
    }
   
}

이 녀석을 컴파일 한 클래스의 모습니다.

// Compiled from JavaPSample.java (version 1.6 : 50.0, super bit)
public class sandbox.JavaPSample {
 
  // Method descriptor #6 ()V
  // Stack: 1, Locals: 1
  public JavaPSample();
    0  aload_0 [this]
    1  invokespecial java.lang.Object() [8]
    4  return
      Line numbers:
        [pc: 0, line: 3]
      Local variable table:
        [pc: 0, pc: 5] local: this index: 0 type: sandbox.JavaPSample
 
  // Method descriptor #6 ()V
  // Stack: 3, Locals: 5
  public void test();
     0  ldc <String "a"> [16]
     2  astore_1 [a]
     3  ldc <String "b"> [18]
     5  astore_2 [b]
     6  new java.lang.StringBuilder [20]
     9  dup
    10  aload_1 [a]
    11  invokestatic java.lang.String.valueOf(java.lang.Object) : java.lang.String [22]
    14  invokespecial java.lang.StringBuilder(java.lang.String) [28]
    17  aload_2 [b]
    18  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [31]
    21  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [35]
    24  astore_3 [c]
    25  ldc <String "ab"> [39]
    27  astore 4 [d]
    29  return
      Line numbers:
        [pc: 0, line: 6]
        [pc: 3, line: 7]
        [pc: 6, line: 8]
        [pc: 25, line: 9]
        [pc: 29, line: 10]
      Local variable table:
        [pc: 0, pc: 30] local: this index: 0 type: sandbox.JavaPSample
        [pc: 3, pc: 30] local: a index: 1 type: java.lang.String
        [pc: 6, pc: 30] local: b index: 2 type: java.lang.String
        [pc: 25, pc: 30] local: c index: 3 type: java.lang.String
        [pc: 29, pc: 30] local: d index: 4 type: java.lang.String
}

중간에 보시면 StringBuilder의 append를 사용하는 모습을 볼 수 있는데, 한 줄에있는 + 연산은 StringBuilder의 append를 이용함을 알 수 있습니다. 따라서, 연달아 append를 할 경우에는 굳이 StringBuilder를 사용할 필요 없이 + 연산만으로도 충분할 것 같습니다.

앞에 숫자들은 라인 넘버인것 같고, 그 다음 문자열들(idc, astore, aload, invokevirtual, invokestatic, invokespecial 등)은 JVM 명령어라고 합니다.

ASM, BECL, Javassist 이 세 가지가 대표적인 바이트 코드 조작 라이브러리 인데, CGLIB은 이 중에서 ASM을 사용하고, AspectJ는 BECL을 사용 한다네요.

음.. 기본은 항상 다져야 되나 봅니다. 모르는 것도 많고 예전에 본것도 다 까먹네요.
스펙이랑 자바퍼즐러라도 볼까봐요.
top


Double.MAX_VALUE는 좀 특이하군요 @_@

Java : 2009/05/19 21:07


System.out.println(Double.compare(Double.MAX_VALUE, Double.MAX_VALUE - 1.0));

이렇게 하면 무슨 값이 출력 될까요? 앞에 있는 인자가 더 크니까 양수가 나와야 합니다.
그러나 해보시면 아시겠지만, 0이 나옵니다.

난 너무 커서 1 같이 작은 수는 빼봤자 그게 그거야...

라는 건가요.. 이건 좀 @_@
왜이러는 걸까요?
정확하게 비교하려면 어떻게 해야 할까요?

한가지 찾은 방법은 longValue() 이용해서 long으로 바꾼 다음에 계산하는 겁니다.
그러나.. 불편하자나요~
top


EJ2E Item 20. 태그가 있는 클래스 대신 클래스 계층구조를 선호하라.

Java : 2009/03/13 20:09


참조: Effective Java 2nd Edition. Prefer class hierarchies to tageed classes

사용자 삽입 이미지

위와 같은 클래스의 단점:
- enum, switch 문, 태그 필드로 인해 지져분하다.
- 여러 구현체를 하나의 클래스로 합쳐놓았기 때문에 가독성이 떨어진다.
- 불필요한 필드까지 가지고 인스턴스를 만들어야 하기 떄문에 메모리 풋프린트가 증가한다.
- 생성자에서 불필요한 필드까지 초기화하지 않는 이상 필드를 final로 선언할 수 없다.
- 생성자에서 초기화를 잘못했을 때 컴파일 시점에 이것을 알 수 없다.
- 새로운 종류를 추가했을 때 switch문에 case를 추가해야 한다는 것을 기억해야 한다. 안그러면 런타임 에러가 발생한다.
- 인스턴스 데이터 타입이 실제 타입을 알려주지 못한다.
=> 즉 장황하고, 에러가 발생할 여지가 많고, 비효율적이다.

먼저 추상 클래스를 만들어서 일반적인 것들을 이 클래스로 이동시키고,
구체적인 하위 클래스를 정의한다.
해당 하위 클래스에서 추상 매서드를 구현한다.

사용자 삽입 이미지

이렇게 구성하면
- 깨끗하고 간단하다.
- 특정 타입에 관련된 속성은 해다 클래스가 가지게 된다. 따라서 불필요한 필드가 없다.
- 모든 필드를 final로 선언할 수 있다.
- 컴파일 시점에 생성자에서 데이터 필드를 초기화 하는지, 추상 매서드를 구현했는지 확인할 수 있다.

top

Java : 2009/03/13 20:09 Trackback. : Comment.

EJ2E Item 19. 인터페이스는 오직 타입을 정의할 때만 사용하라

Java : 2009/01/28 12:38


참조: Effective Java 2nd Edition

어떤 클래스가 인터페이스를 구현할 때 인터페이스는 해당 클래스의 인스턴스를 참조할 수 있는 타입을 제공한다. 즉 해당 클래스 인스턴스를 가지고 고객이 무엇을 할 수 있는지 알려주는 것이다. 이 경우 이외에 다른 의도로 인터페이스를 사용하는 것은 부적절하다.

상수 인터페이스(constant interface) 안티 패턴
- 매서드 없이 상수를 표현한 static final 필드만 있다.
- 그 상수를 사용하는 클래스는 해당 인터페이스를 구현하여 상수 이름을 클래스 이름으로 구분해야 할 필요를 없앤다.(A.C B.C 대신에 A와 B가 구현하는 인터페이스 Z로 C를 올려서 Z.C 형태로 사용하게 하는 건가보네요.)
- 클래스가 내부에서 어떤 상수를 사용하는지는 구체적인 내용에 해당하는데 클래스가 상수 인터페이스를 구현하면 구체적인 내용을 밖으로 노출하게 된다.
- 차후에 해당 상수가 필요없어지더라도 바이너리 호환성을 위해 해당 인터페이스를 그대로 구현하고 있어야 한다.
- 자바 라이브러리 중에 java.io.ObjectStreamConstants는 예외다.
- 상수를 다룰 땐 해당 상수가 속하는 클래스나 인터페이스를 잘 고려해야 하며 열거형이 아닌지 보고 열거형일 경우에는 enum type 사용을 고려하라.

상수를 자주 사용할 때는 static import를 사용할 수도 있겠다.

인터페이스로는 타입을 정의해야지 상수를 표현하지 않아야 한다.
top

Java : 2009/01/28 12:38 Trackback. : Comment.

EJ2E Item 18. 추상 클래스 보다는 인터페이스를 선호하라

Java : 2009/01/19 01:43


참조: Effective Java 2nd Edition. Item 18: Prefer interfaces to abstract classes

기존 클래스를 쉽게 수정하여 새로운 인터페이스를 구현할 수 있다.
- 인터페이스는 implements에 추가해주고 필요한 매서드를 구현하면 끝
- 하지만 새로운 추상 클래스를 만들어서 공통 로직을 상위로 올린다면, 하면 하위 클래스에는 자신에게 적당할지 안 할지도 모를 로직들을 상속받게 된다.

믹스인을 정의할 때 인터페이스가 제격이다.
- 믹스인(Mixin)이란 "원래 타입"에 어떤 부가적인 행위를 추가로 구현했다는 것을 나타내는 타입. ex) Comparable 인터페이스
- 추상 클래스는 믹스인으로 쓰기 어렵다.(단일 상속이니까)

인터페이스 계층구조가 아닌 타입 프레임워크를 구성할 수 있게 한다.

인터페이스는 Item 16에서 살펴본 wrapper class 개념을 통해 안전하고, 강력한 기능성 증진을 가져다준다.
- 추상 클래스를 사용하여 타입을 정의하면, 개발자가 기능을 추가하고 싶을 때 상속만을 써야 한다. 그 결과 클래스가 wrapper 클래스보다 점점 약해지고 깨지기 쉬워진다.

인터페이스 장점과 추상 클래스 장점을 추상 skeletal 구현체 클래스를 제공하여 얻을 수 있다.
- skeletal 인터페이스 = Abstact인터페이스, 해당 인터페이스를 구현할 때 필요한 모든 작업을 포함하고 있다.
- 개발자가 해당 인터페이스를 쉽게 구현할 수 있도록 도와준다.
- 보통은 skeletal 인터페이스를 extends 하여 만들지만, 그렇게 못할 경우에는 인터페이스를 직접 implements해도 된다.
- 인터페이스를 구현하고 그 것을 구현할 때 skeletal 클래스를 상속 받은 private inner class에 매소드 호출을 위임하는 식으로 구현할 수 있다. (simulated multiple inheritance)

인터페이스를 배포 한 뒤 그 구현체가 많아졌다면 그것을 변경하는 건 불가능에 가깝다.
- 따라서 주의해서 설계해야한다.
- 인터페이스를 정하기 전 가능한 여러 개발자가 그것을 구현해보게 하는 것이 최선이다.

top


EJ2E Item 17. 상속에 대한 설계와 문서화를 제대로 하지 않을 거면 아예 상속을 허용하지 말라.

Java : 2009/01/13 22:38


참조: Effective Java 2nd Edition Item 17: Design and document for inheritance or else prohibit it.

오버라이딩이 가능한 메소드에는 반드시 문서화를 해야 한다.
 - "This implementation ~~" 즉 "이 구현체에서는.." 이라는 식으로 다음 배포 때는 다르게 구현할 수도 있다는 의미를 내포하는 문서화와 그 내용을 자세히 알려줘야 한다.

상속을 고려한 클래스를 테스트하는 유일한 방법은 하위클래스를 작성하는 것이다.
- 배포하기 전에 반드시 하위클래스를 작성해서 테스트 해봐야 한다.

생성자에서 오버라이딩이 가능한 메소드를 직접적으로든 간접적으로든 호출하면 안 된다.
- 상위 클래스 생성자에서 하위 클래스가 오버라이딩한 메소드를 호출하다가 아직 필드 초기화도 안 된 하위 클래스의 필드를 사용해서 원치 않던 결과가 발생할 수 있다.

Cloneable과 Serializable 인터페이스를 구현한 클래스가 상위클래스가 될 여지가 있다면
- 생성자와 마찬가지로 clone과 readObject에서 오버라이딩이 가능한 메소드를 호출하지 않도록 한다.

안전하게 상속하도록 문서화 하거나 설계하지 않은 클래스를 상속할 수 없도록 하는 것이 최선책이다.
- 두 가지 방법 중에서 쉬운 방법은 클래스를 final로 만드는 것이다.
- 두 번째 방법은 모든 생성자를 private 또는 package-private으로 만들고 public static 팩터리를 추가한다. 이 방법은 내부적으로 상속이 가능해서 약간 더 유연하다. Item 15에서 다뤘다.




top

Java : 2009/01/13 22:38 Trackback. : Comment.

EJ2E Item 16. 상속보다 컴포지션을 선호하라

Java : 2008/12/28 00:25


참조: Effective Java 2nd Edition Item 16: Favor composition over inheritence

상속
- 상속은 코드 재사용을 하는 강력한 방법이지만 항상 최선의 방법은 아니다. 무분별하게 사용했다가는 연약한 소프트웨어가 된다.

- 상속은 동일한 패키지 내에서 하위 클래스와 상위 클래스 구현을 같은 프로그래머가 관리할 때 안전하다. 또한 상속은 상속하려는 클래스가 확장을 고려해서 설계되었고 문서화 되어 있을 때 안전하다.

- 이 책에서 말하는 상속은 구현체 간의 상속이지 인터페이스 상속을 말하는 건 아니다.

- 메소드 호출과 달리 상속은 캡슐활르 위반한다. 즉 하위 클래스가 상위 클래스의 구현에 의존한다.  상위 클래스의 구현 내용이 배포를 거듭하면서 바뀔 수 있는데.. 이로 인해 하위 클래스 코드는 건드리지도 않았는데 깨질 수가 있다.

- 오직 "is-a" 관계가 성립할 때만 적당하다. 자바 플랫폼에서 이를 위반 한 사례: Stack이 Vector를 상속받았다. Properties가 Hashtable을 상속 받았다.

컴포지션
- 컴포지션을 사용하면 앞서 말한 문제(깨지기 쉬운 코드, 상위 클래스에 새로운 메소드 추가 필요)를 해결할 수 있다.

- 포워딩(흔히 역할 위임이라고 일컫는 작업을 나타내는 듯)을 한다.

- 구현의 구체적인 내용과는 의존성이 없다.

- 래퍼(wrapper) 클래스와 데코레이터 패턴

- 래퍼 클래스 단점: 래퍼 클래스는 콜백 프레임워크에서 사용하기 적당하지 않다. 래퍼 클래스로 감싼 객체는 자기가 누구한테 감싸였는지 모르기 때문에 itself(this) 형태로 호출해도 래퍼는 무시된다. (만약 상속이었다면? 다형성 때문에 결국 상위 클래스를 상속받은 하위 클래스의 객체가 넘어갔겠지만..) => SELF problem





top

Java : 2008/12/28 00:25 Trackback. : Comment.

EJ2E Item 15. 변경을 최소화하라

Java : 2008/12/23 21:52


참조: Effective Java 2nd Editio Item 15: Minimize mutability

자바에는 다양한 Immutable 클래스들이 있는데, 그 이유는.. 불변 클래스가 변하는(mutable) 클래스보다 설계하고 구현하고 사용하기 쉽기 때문이다.

불변 클래스를 만드는 다섯 가지 규칙
1. 객체의 상태를 바꾸는 메소드를 제공하지 말라.
2. 클래스를 확장할 수 없도록 해라.
3. 모든 필드를 final로 하라.
4. 모든 필드를 private으로 하라.
5. 변경 가능한 요소에는 상호 배타적으로 접근하도록 하라.(불변 클래스가 변하는 필드를 가지고 있을 때, 클라이언트가 해당 객체에 대한 레퍼런스를 가지지 못하게 하라. defensive copies를 생성자나 aceessor에서 만들고 readObject 메소드를 만들어라. @_@)

별로 안 간단해 보이는데;;; 간단하데요

불변 객체는 당연히 쓰레드-세이프 하다. 따라서 동기화 필요 없다. 자유롭게 공유해서 쓰면 된다.

단점은 매번 별도의 객체를 필요로 한다는 것이다. 비싼 객체는 좀.. 글켔군요.

상속을 막는 방법이 클래스를 final로 만드는 거 말고 좀 더 유연한 방법이 있다. 모든 생성자를 private 또는 package-private으로 만들고 public static 팩터리를 만드는 거다. 같은 패키지에서는 상속해서 다양한 구현체를 만들 수 있지만 해당 패키지 밖에서 참조할 땐 final 클래스와 마찬가지니깐... 오호.. 천젠데;;

어떤 클래스를 불편 불변 클래스로 만들어야 할까? 고민해본 적이 없네... 집에 가면서 생각해보자.. 굳이 바뀔 필요가 없는 클래스도 필요 없이 setter를 전부 만들진 않았을까..


top




: 1 : 2 : 3 : 4 : 5 :