스프링의 빈 스캐너는 애노테이션이나 클래스 이름패턴 필더를 이용해서 조건을 만족하는 모든 클래스를 빈으로 자동 등록해주는 기능을 가지고 있다. 아.. 사실 스캐너는 등록까지는 아니고 스캔을 통해서 후보 빈 메타정보를 생성하는 것 까지가 책임이긴 하다. 아무튼 편하다. XML선언 없이도 간단히 빈을 등록할 수 있으니 말이다.

보통 @Component라는 애노테이션을 빈 후보 선정용 필터로 사용하는데 이게 필수는 아니다. 원한다면 애노테이션 없이도 특정 패키지 아래의 모든 클래스를 모두 빈으로 등록하게 할 수도 있다.

그런데 빈 스캐닝 방식에 대해서 보통 성능상의 손해가 있을 것이라고 생각한다. 그런 생각은 스캐닝이라는 작업이 주는 일종의 선입관이 아닌가 싶다. XML문서를 파싱하고 이를 해석하는 것보다 특정 패키지 아래의 모든 클래스를 뒤져서 애노테이션을 확인하는 것이 더 느린 작업이라는 전제가 깔려있다. 아마도 그런 판단은 클래스의 정보를 읽는 리플렉션 API가 사용됐을 것이라는 추측에서 비릇됐을 것이다. 리플렉션 API는 그 성능에 대해 좋지 않은 이미지를 가지고 있다. 마치 JDK프록시처럼 말이다. 물론 두가지 다 JDK버전이 업그레이드 되면서 상당히 성능이 개선되었고, 이제는 왠만한 툴과 프레임워크에서 리플렉션 API를 사용해서 일종의 메타프로그래밍을 시도하는 일이 많아졌을만큼 충분히 좋아졌다.

리플렉션API가 보편화 되는 과정에서 애노테이션의 등장도 한 몫했다. 대부분의 애노테이션은 런타임 시에 그 정보를 소화한다. 애노테이션은 타겟의 타입에 변화를 주지 못한다. 그래서 조쉬 블록은 애트리뷰트가 없는 마커 애노테이션을 쓸바엔 마커 인터페이스를 쓰라고 했다. 인터페이스는 타입의 변화를 주고 그래서 검증이 쉽다. 반면에 애노테이션은 일일히 리플렉션 API를 이용해서 확인해야 한다.

아무튼 그래서 스프링의 빈 스캐닝은 특정 패키지 내의 모든 클래스의 애노테이션을 일일히 리플렉션을 통해서 검사해야 하므로 느릴 것이라는 나름의 추정을 하고 있는 사람들이 많은지 모르겠다. 여기에 한가지 더 하자면 리플렉션을 적용하려면 클래스를 일단 메모리로 로딩해야 한다. 굳이 당장에 스프링의 빈으로 등록될 필요가 없는, 어쩌면 애플리케이션 개발에서 만들기는 했으나 정작 사용하지 않는 클래스까지 빈 스캐너가 리플렉션으로 애노테이션을 검사하게 하기 위해서 모두 메모리로 로딩한다는 것은 매우 비효율적이라고 밖에 느껴지지 않을 것이다.

개발중이라면 테스트 클래스도 같은 클래스 패스의 패키지에 존재한다. 따라서 테스트 중에 빈 스캐닝을 적용한 컨텍스트 설정을 해버리면 모든 패키지내의 테스트도 몽땅 메모리로 로딩된다. 이 얼마나 어이없는 일인가.

이렇게 성능상의 손해를 보더라도 빈 스캐너의 편리함 때문에 그냥 사용해야 하는 것일까?

 

아니다.

성능을 떨어뜨리고 초기 기동시간을 많이 잡아먹으니 사용하지 말라는 얘기가 아니다. 위에서 추정한 식의 성능상의 심각한 손해는 없다는 말이다. 스캐닝 과정에서 모든 대상 클래스를 로딩하고 리플렉션을 사용해서 일일히 애노테이션을 확인하는 일 따윈 발생하지 않고, 그래서 성능상의 영향이 별로 없다는 얘기다.

그렇다면 스프링의 빈 스캐너는 클래스를 로딩하지도 않고, 리플렉션을 사용하지도 않은채로 클래스 안에 어떤 컴포넌트가 있는지를 확인할 수 있다는 말인가? 그렇다.

스프링의 빈 스캐너는 클래스를 일절 로딩하지 않고 리플렉션API를 쓰지 않고도 클래스 정보를 알아내고 애노테이션을 분석한다. SimpleMetadataReaderFactory가 만들어주는 SimpleMetatdataReader가 그런 일을 가능하게 해준다.

com.mycomponent라는 패키지와 그 하위 패키지의 아래의 모든 클래스의 이름과 클래스 레벨의 애노테이션을 출력하는 프로그램을 만들어보자. 를래스를 로딩도 하지 않고 리플렉션도 사용하지 않도록 할 것이다.

먼저 패키지 아래 모든 클래스 파일을 리소스 형태의 목록으로 만든다.

PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] rs = resolver.getResources("com/mycompany/**/*.class”);

리소스 로더가 지원하는 앤트 방식의 패턴매칭을 사용했다. 리소스는 그 자체로 파일을 읽어들인 것은 아니다. 단지 리소스 형태로 포인팅만 하고 있다.

이제 클래스 파일에 대한 리소스 정보를 가지고 클래스이름과 애노테이션을 읽고 출력해보겠다.

for(Resource r : rs) {
    MetadataReader mr = new SimpleMetadataReaderFactory().getMetadataReader(r);
    System.out.println(mr.getClassMetadata().getClassName());
    for(String an : mr.getAnnotationMetadata().getAnnotationTypes()) {
        System.out.println(" – " + an);
    }
}

먼저 리소스에 대해서 메타정보리더를 만들어 둔다.  그리고 그 안의 클래스 메타데이터와 애노테이션 메타데이터 정보를 가져와서 사용하면 된다.

그렇다면 이 MetadataReader는 어떻게 클래스 정보와 애노테이션 정보를 읽어들이는 것일까? 그것은 클래스 바이트코드를 직접 분석해서 필요한 메타정보만 가져오는 방법을 사용한 것이다. 정식으로 클래스 바이너리를 JVM에 로딩하는 과정없이 바로 바이트코드에 접근하는 방법을 쓴 것이다. 이 방법을 사용하면 클래스, 메소드, 애노테이션 등에 대한 메타정보를 로딩이나 리플렉션API사용 없이도 알아낼 수있다.

사실 뒤에서 이 일을 해주는 것은 자바의 바이트코드 조작을 가능하게 해주는 ASM라이브러리이다. 스프링은 이 ASM의 각종 visitor를 사용해서 메타정보를 읽어낸다. 내부적으로는 스프링의 ClassMetadataReadingVisitor와 MethodMetadataReadingVisitor, AnnoationMetadataReadingVisitor 등이 ASM라이브러리의 도움을 받아서 메타정보 읽기 작업을 수행한다.

결국 빈 스캐너는 불필요한 클래스를 메모리에 로딩하는 위험성을 피하고, 리플렉션API와 같은 상대적으로 성능이 떨어지는 API의 사용을 안하고 직접 빠르게 바이트코드 파싱을 통해서 필요한 정보를 가져오기 때문에 성능에 그다지 영향이 없다.

 

그런데 정말 클래스가 로딩됐는지 아닌지를 어떻게 확인할 수 있을까? 내가 알기로는 간단한 방법은 없다.

굳이 방법을 찾아보면, ClassFileTransfomer를 지원하는 클래스로더를 이용하거나 JVM agent를 등록해서 Instrumentation을 가져와 로딩된 클래스를 확인해보는 것이다. 스프링의 InstrumentationSavingAgent를 사용하면 아주 간단히 Instrumentation 오브젝트에 접근 할 수 있다. 좀 더 해보고 싶다면 LoadTimeWeaver에서 사용할 ClassFileTransformer를 만들어 클래스가 뭐가 로딩되는지 확인하는 기능을 사용해봐도 좋을 것이다.

 

오늘의 결론은 "잘 모르면서 쓸데없는 걱정이나 하지말고 빈 스캐너를 열심히 쓰 자"는 것.

Related posts:

  1. InsideSpring (3) 스프링 밖에서 WebApplicationContext에 접근하기
  2. InsideSpring (2) AutoProxyCreator는 Pointcut의 ClassFilter만 사용하지 않는다
  3. InsideSpring (1) Annotated Factory Method (@Configuration)을 쓰는 4가지 방법 (1)
  4. InsideSpring (1) Annotated Factory Method (@Configuration)을 쓰는 4가지 방법 (2)
  5. InsideSpring (1) Annotated Factory Method (@Configuration)을 쓰는 4가지 방법 (3)

Facebook comments:

to “InsideSpring (4) 빈 스캐너는 클래스를 로딩할까?”

  1. LWT랑 ClassFileTransaformer 안 쓰고 클래스로딩 되는지 안되는지 확인하는 방법 트랙백이요ㅋㅋㅋ

    http://whiteship.me/2502

  2. 예상외의 멋진 내용이에요 +.+
    스프링 만세입니다~;;; 이말밖에는….ㅎ

  3. 네… -_-);
    (갑자기 마지막 줄에서 기 죽어서…)

  4. 그런데, 누가 “잘 모르면서 쓸데없는 걱정”을 하고 있던건지 궁금한데…

  5. 와우!~
    마치 제가 좋아 하는 세계2차 대전의 behind스토리를 읽는 기분이었습니다.
    좋은 정보 감사드립니다.

  6. My friend Tom Moyer, a guy who drives a LOT of traffic to various offers through his large blog network, has just released a traffic service with a free trial so webmasters can “try before they buy”. If you are interested in driving lots of web traffic to your website then don’t miss this offer: http://gmbal.com/210c

  7. You should try this company for getting more website visitors: http://gmbal.com/210c – I use this service on all of my websites and I am very happy. This service will get you targeted traffic with no effort on your end. Thank me later!

  8. mbts shoes InsideSpring (4) 빈 스캐너는 클래스를 로딩할까? » Toby’s Epril

  9. mbt s InsideSpring (4) 빈 스캐너는 클래스를 로딩할까? » Toby’s Epril

Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2017 Toby's Epril Suffusion theme by Sayontan Sinha