Whiteship's Note

'Java'에 해당되는 글 140건

  1. 2010.04.15 [JMX] JMX API 직접 사용하여 JMX MBean 공개하고 모니터링 및 관리하기
  2. 2010.03.29 EJ2E Item 24. unchecked 경고를 제거하자 (1)
  3. 2010.03.26 EJ2E Item 23. 새로 작성하는 코드에서는 raw type 쓰지 말자
  4. 2010.03.24 EJ2E Item 22. nonstatic 보다는 static 멤버 클래스를 선호하라
  5. 2010.03.24 EJ2E Item 21. 전략을 표현할 때는 함수 객체를 사용하라
  6. 2010.03.11 [ClassLoader] Thread의 getContextClassLoader() (2)
  7. 2010.03.09 Class.forName()와 ClassLoader.loadClass() 차이점 (2)
  8. 2010.03.02 톰캣 6.0 클래스로더 구조
  9. 2010.03.02 null은 캐스팅이 되는건야 안 되는거야? (10)
  10. 2010.02.25 클래스 로더의 loadClass()와 findClass()
  11. 2010.02.23 자바 클래스로더 입문 퀴즈 정리 (2)
  12. 2010.02.23 [ClassLoader 퀴즈 끝] SpringSprout와 WhiteshipFactory가 참조하는 Whiteship은 누구인가.
  13. 2010.02.22 [ClassLoader 퀴즈 6] SpringSprout는 과연 Whiteship의 이름을 알 수 있을까? (4)
  14. 2010.02.22 [ClassLoader 퀴즈 5] Whiteship은 언제 로딩 될까? (2)
  15. 2010.02.21 [ClassLoader 퀴즈 4] SpringSprout가 알고 있는 Whiteship은 누구인가? (10)
  16. 2010.02.19 [ClassLoader 퀴즈 3] Whiteship은 Whiteship일까 아닐까? (2)
  17. 2010.02.19 [ClassLoader 퀴즈 2] Whiteship은 내가 데려왔다!! (5)
  18. 2010.02.19 [ClassLoader 퀴즈 1] Whiteship은 대체 누가 데려온 것일까? (8)
  19. 2010.02.17 [Java] ClassLoader API (2)
  20. 2010.02.10 [NullPE] SpEL 때문에 고민 해결
  21. 2010.02.10 [NullPE] NullPointerException 때문에 고민 1 (2)
  22. 2010.02.09 WEB-INF/urlrewrite.xml 파일 설정하기
  23. 2010.02.07 UrlRewriterFilter 옵션
  24. 2010.02.07 UrlRewriterFilter 설치하기
  25. 2010.02.07 "서블릿 매핑 규칙"과 "필터 순서 정하기 규칙" (2)
  26. 2010.02.07 UrlRewriterFilter 소개
  27. 2010.01.25 자바 System.out.println 콘솔 출력 가로채기 (7)
  28. 2009.07.20 클래스파일 보기 (5)
  29. 2009.05.19 Double.MAX_VALUE는 좀 특이하군요 @_@ (4)
  30. 2009.03.13 EJ2E Item 20. 태그가 있는 클래스 대신 클래스 계층구조를 선호하라.

[JMX] JMX API 직접 사용하여 JMX MBean 공개하고 모니터링 및 관리하기

Java : 2010.04.15 18:39


참조: Spring Enterprise Recipe


오호.. JDK 1.5부터 그냥 들어있는 JMX 클라이언트 jconsole을 실행해봤습니다. 모니터링 정보가 나오는 군요. 머.. 이건 나중에 살펴보기로 하고;; 일단은 저렇게 모니터링 할 MBean을 공개하는 방법을 보겠습니다.

public class Main {

    public static void main(String[] args) throws IOException {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("sandbox/jmx/beans-jmx.xml");
        FileReplicator documentReplicator =
                (FileReplicator) context.getBean("documentReplicator");
        try {
            MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();

          ObjectName objectName = new ObjectName("bean:name=documentReplicator");
            
            RequiredModelMBean mbean = new RequiredModelMBean();
          mbean.setManagedResource(documentReplicator, "objectReference");
            
            Descriptor srcDirDescriptor = new DescriptorSupport(new String[]{
                    "name=SrcDir", "descriptorType=attribute",
                    "getMethod=getSrcDir", "setMethod=setSrcDir"});
            ModelMBeanAttributeInfo srcDirInfo = new ModelMBeanAttributeInfo(
                    "SrcDir", "java.lang.String", "Source directory",
                    true, true, false, srcDirDescriptor);
            
           Descriptor destDirDescriptor = new DescriptorSupport(new String[]{
                    "name=DestDir", "descriptorType=attribute",
                    "getMethod=getDestDir", "setMethod=setDestDir"});
            ModelMBeanAttributeInfo destDirInfo = new ModelMBeanAttributeInfo(
                    "DestDir", "java.lang.String", "Destination directory",
                    true, true, false, destDirDescriptor);

            ModelMBeanOperationInfo getSrcDirInfo = new ModelMBeanOperationInfo(
                    "Get source directory",
                    FileReplicator.class.getMethod("getSrcDir"));
            ModelMBeanOperationInfo setSrcDirInfo = new ModelMBeanOperationInfo(
                    "Set source directory",
                    FileReplicator.class.getMethod("setSrcDir", String.class));

            ModelMBeanOperationInfo getDestDirInfo = new ModelMBeanOperationInfo(
                    "Get destination directory",
                    FileReplicator.class.getMethod("getDestDir"));
            ModelMBeanOperationInfo setDestDirInfo = new ModelMBeanOperationInfo(
                    "Set destination directory",
                    FileReplicator.class.getMethod("setDestDir", String.class));

            ModelMBeanOperationInfo replicateInfo = new ModelMBeanOperationInfo(
                    "Replicate files",
                    FileReplicator.class.getMethod("replicate"));

            ModelMBeanInfo mbeanInfo = new ModelMBeanInfoSupport(
                    "FileReplicator", "File replicator",
                    new ModelMBeanAttributeInfo[]{srcDirInfo, destDirInfo},
                    null,
                    new ModelMBeanOperationInfo[]{getSrcDirInfo, setSrcDirInfo,
                            getDestDirInfo, setDestDirInfo, replicateInfo},
                    null);
            mbean.setModelMBeanInfo(mbeanInfo);

            mbeanServer.registerMBean(mbean, objectName);
        } catch (JMException e) {
            e.printStackTrace();
        } catch (InvalidTargetObjectTypeException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        System.in.read();
    }
}

꺄오... 

1. MBeanServer 인스턴스 가져오기
2. ObjectName 인스턴스 만들기 <- MBean의 이름(도메인:name=MBean이름)
2. MBean 인스턴스 만들기<- MBean으로 등록할 객체 설정.
3. ModelMBeanAttributeInfo 인스턴스 만들기 <- 객체의 속성에 해당함.
4. ModelMBeanOperationInfo 인스턴스 만들기 <- 객체의 메서드에 해당함.
5. ModelMBeanInfo 객체 만든 다음 2번에서 만든 MBean에 설정하기. <- 2, 3, 4번 조합
6. 1번에서 가져온 MBeanServer에 2번에서 만든 MBean 등록하기

이렇게 엄청난 과정과 Chekced Exception 들을 처리해주고..
실행할 때는 또 -Dcom.sun.management.jmxremote 이런 VM 인자까지 넘겨줘야지 모니터링이 됨.
그리고 마지막에 애플리케이션 꺼지지 않도록 System.in.read()로 대기 상태 만들어 둠.

콘솔로가서 jconsole 입력하고 MBeans 탭에서 bean 폴더 밑에 documentReplicator 찾으면 속성과 메서드를 바로 바로 실행해볼 수 있음.









top

TAG jmx
Java : 2010.04.15 18:39 Trackback. : Comment.

EJ2E Item 24. unchecked 경고를 제거하자

Java : 2010.03.29 16:16


참조: Effective Java 2nd Edition. Item 24: Eliminate unchecked warnings

Set<Lark> exaltation = new HashSet();

다음과 같은 코드는 컴파일 시 unchecked 경고를 발생시킨다. 

Set<Lark> exaltation = new HashSet<Lark>();

이렇게 수정하면 경고가 사라진다. 가능한 모든 unchecked 경고를 없애자. 모든 경고를 제거했다면 코드의 타입 안전성을 확보하는 것이다. 즉 실행시에 ClassCastException을 보지 않을 것이다.

만약 경고를 없앨 수 없지만 타입 안전성을 확신한다면 @SupressWarnings("unchecked") 애노테이션을 사용하여 경고를 무시할 수 있다.

SupressWarning 애노테이션을 사용할 때는 항상 최소 단위에 적용하도록 하자. 만약 클래스위에 붙여버리면 @_@.. 메서드에 붙여도 @_@. 메서드 내부에 코드가 많다면 그 중 어디선가 또 다른 타입 안전성 문제가 발생할 수 있다. 메서드 내부에 변수를 만들고 해당 변수에다가 붙이자. 단 한 줄짜리 메서드라면 뭐.. 

SupressWarning 애노테이션을 사용할 때마다 왜 타입 안전성이 보장되는지 주석을 달아두도록 하자. 그래야 다른 사람들도 이해할 수 있다.

요약: 경고는 중요하다. 무시하지 말자. 모든 unchecked 경고는 잠재적으로 런타임시의 ClassCastException에 해당한다. 이 경고들을 없애는데 주력하자. 만약 없앨 수 없지만 타입 안전성을 확신한다면 가장 최소한의 규모에 @SuppressWarning("unchecked") 애노테이션을 붙이자. 그리고 그렇게 결정한 이유를 주석으로 남기자.
 
top


EJ2E Item 23. 새로 작성하는 코드에서는 raw type 쓰지 말자

Java : 2010.03.26 17:44


참조: Effective Java 2nd Edition. Item 23:Don't use raw types in new code

먼저 용어 정리

class List<E> {
...
}

List<String> stList = ~~~~;

이런 코드가 있을 때...
- List<E>: Generic type
- List<String>: Parameterized type
- E: Formal type parameter
- String: Actual type parameter
- List: Raw type (제목에 쓴게 이거임)
- List<?>: Unbounded wildcard type
- 나머진 패스.

Generic 장점
- 컴파일 시에 타입 안전성을 보장한다.
- 부가적인 장점으로 컬렉션에 들어있는 것을 꺼낼 때 캐스팅할 필요가 없다.

그럼 Generic을 꼭 쓰게 하지 왜 Raw Type으로 쓸 수 있게 해뒀냐?
- 호환성 때문에. 이미 1.5전 버전 기준으로 만들어 놓은 코드가 많으니까.

Generic 특성
- List<String>은 List의 하위 타입이지만 List<Object>의 하위 타입은 아니다.
- 다음 item에서 더 자세히 설명 나옴.


Raw Type을 사용했을 때 발생할 수 있는 문제

    public static void main(String[] args) {
        List<String> strings = new ArrayList<String>();
        unsafeAdd(strings, new Integer(42));
        String s = strings.get(0); // Compiler-generated cast
    }

    private static void unsafeAdd(List list, Object o) {
        list.add(o);
    }

굵은 글씨에서 타입을 명시하지 않았기 때문에
list.add(o); 여기서 일단 컴파일 경고가 발생하지만 일단 무시하고 실행할 수는 있다 막상 실행하면 ClassCastException 발생한다. 하지만 만약 저기서 List<String>이라고 타입을 명시했다면 애초에 컴파일도 못했을 것이다.

어떤 종류가 들어있는지 모르는 컬렉션을 처리하는 메서드에서는 unbounded wildcard type을 사용하자.
   
    //위험
    static int numElementsInCommon(Set s1, Set s2) {
        int result = 0;
        for (Object o1 : s1)
            if (s2.contains(o1))
                result++;
        return result;
    }

    //안전
    static int numElementsInCommon(Set<?> s1, Set<?> s2) {
        int result = 0;
        for (Object o1 : s1)
            if (s2.contains(o1))
                result++;
        return result;
    }

위에껀 왜 위험하고 아래껀 왜 안전할까? 안전한 녀석에는 무언가 추가할 수가 없다. 위험한 녀석에는 아무 객체나 넣을 수 있지만 unbounded wildcard type을 사용한 경우에는 컬렉션에 아무것도 추가할 수 없다. 컴파일 에러다.

왜? Actual type parameter가 뭔지 모르는데 뭘 집어 넣을 수 있을까.. 생각해보면 당연하다.

Type parameter를 사용할 수 없는 경우
- List<String>.class
- instanceof


top

TAG Generic, Java
Java : 2010.03.26 17:44 Trackback. : Comment.

EJ2E Item 22. nonstatic 보다는 static 멤버 클래스를 선호하라

Java : 2010.03.24 11:16


참조: Effective Java 2nd Edition. Item 22: Favor static member class over nonstatic

중첩 클래스(nested class)에는 네 종류가 있다.
- static member class
- nonstatic member class
- anonymous class
- local class

Static Member Class

public class SpringSprout {
   static class Whiteship {
        private String name; 
        ...
    }
}

- 다른 클래스 내부에 선언된 클래스로 가장 많이 사용된다.
- 자신을 가지고 있는 클래스의 모든 멤버에 접근할 수 있다.
- 보통 helper 클래스로 사용된다.
- enclosing instance 유무와 상관없이 독립적으로 사용될 수 있다.

Nonstatic Member Class

public class SpringSprout {
   class Whiteship {
        private String name; 
        ...
    }
}

- 암묵적으로 자신을 감싸는 클래스의 인스턴스(enclosing instance)와 연관을 맺는다.
- nontatic member class와 enclosing instance와의 연관 관계는 enclosing instance에서 nonstatic member class의 객체를 만들 때 생긴다.
- 이 연관 관계로 인해 nonstatic member class 인스턴에 필요한 공간과 생성 시간이 소비된다.
- 보통 Adapter를 정의할 때 사용된다.
- Map의 keySet, entrySet, values 메서드가 반환하는 콜렉션 뷰들과 Set, List에서 Iterator를 구현할 때 nonstatic member class를 사용한다.

AbstractList 코드

    public Iterator<E> iterator() {
return new Itr();
    }

    private class Itr implements Iterator<E> {
    ...
    }

=> enclosing instance에 접근할 필요가 없는 member class는 static으로 선언하자.

Anonymous Class
- 이름도 없고 enclosing class의 멤버도 아니다.
- 표현식을 사용할 수 있는 곳이라면 어디서든 익명 클래스를 사용할 수 있다.
- nonstatic 문맥에서만 enclosing instance를 가진다.
- static 문맥에서는 static 멤버를 가질 수 없다.
- 주로 함수 객체를 만들 때 사용한다.

Local Class
- 익명 클래스랑 거의 같은데 클래스 이름 줄 수 있고 인스턴스 여러 번 만들어 사용할 수 있다.
- 걍 생략;

요약
- 중첩 클래스가 특정 메서드 외부에서도 사용될 피룡가 있거나 메서드 내부에 있기에는 너무 길다면 멤버 클래스로 만든다.
- 만약 멤버 클래스의 인스턴스가 enclosing instance를 참조할 필요가 있다면 nonstatic으로 만든다. 그렇지 않으면 static으로 만든다.
- 클래스가 메서드 내부에 속해 있으며 딱 한번만 사용할거면 익명 클래스로 만들고, 그렇지 않으면 로컬 클래스로 만든다.
top

Java : 2010.03.24 11:16 Trackback. : Comment.

EJ2E Item 21. 전략을 표현할 때는 함수 객체를 사용하라

Java : 2010.03.24 10:30


참조: Effective Java 2nd Edition. Item 20: Use function objects to represent strategies

자바는 함수 포인터를 제공하지 않는다. 하지만 객체 레퍼러스를 함수 객체와 비슷하게 사용할 수 있다. 

메서드를 호출하면 해당 객체에 대한 기능을 수행하는 것이고 함수 객체는 다른 객체들에서 해당 기능을 수행하게 해준다.

(사족. 메서드는 특정 객체에 종속적인 것이고, 함수 객체는 독립적으로 여러 다른 객체에서 사용할 수 있는 것으로 구분할 수 있겠다.)

함수 객체들은 Concrete Stratege 역할을 하기 때문에 보통 상태 정보가 없고(stateless), 어디서나 동일하게 동작하기 때문에 불필요한 객체 생성 비용을 줄이기 위해 싱글톤으로 사용되기 좋다.

(사족. 스프링을 사용한다면 굳이 싱글톤 구현하느라 애쓰지 말고 그냥 싱글톤 스코프 빈으로 등록하면 되겠다.)

대표적인 예로 Comparator 인터페이스 구현체들이 있다.

// Strategy interface
public interface Comparator<T> {
    public int compare(T t1, T t2);
}

이 구현체는 종종 익명 클래스로 사용되기도 한다. 하지만 익명 클래스를 사용하면 매번 새로운 인스턴스를 만들게 된다. 반복해서 사용할 꺼라면 차라리 함수 객체를 priate static final field에 저장해 놓고 재사용하는 것이 좋겠다. 그렇게 하면 명시적인 이름을 줄수도 있다.

전략이 여러개를 제공하는 "Host class"에서 외부로 공개할( public static final) strategy의 반환 타입은 Strategey 인터페이스 타입이어야 하고 구체적인 Stategy 구현체들은 굳이 public 일 필요가 없다. 

요약
- 전략 패턴을 구현할 때 함수 객체를 주로 사용한다.
- 자바에서 이 패턴을 구현할 때는 전략 인터페이스를 만들고, 각 전략들은 그 인터페이스의 구현체로 만든다.
- 만약 어떤 전략을 한번만 사용할 거면 익명 클래스로 만들고 반복해서 사용할거면 prviate static member 클래스로 만든 다음 전략 인터페이스의 public sttic final field로 제공해준다.
- 대표적인 클래스 String


top

Java : 2010.03.24 10:30 Trackback. : Comment.

[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


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.



: 1 : 2 : 3 : 4 : 5 :