Skip to content

Latest commit

 

History

History
94 lines (73 loc) · 7.97 KB

File metadata and controls

94 lines (73 loc) · 7.97 KB

07 — AOT 빈 등록과 코드 생성

무엇을 / 왜

지금까지의 컨테이너는 런타임 리플렉션에 의존한다. 빈 클래스를 읽고, 생성자를 찾고, @Autowired 필드를 스캔하고, 정의를 병합하는 일이 모두 애플리케이션 시작 시점에 일어난다. 이는 GraalVM 네이티브 이미지에서 두 가지 문제가 된다 — (1) 리플렉션 대상을 미리 등록(hint)해야 하고, (2) 시작 시 분석 비용을 치른다.

AOT(Ahead-Of-Time) 처리는 이 분석을 빌드 타임으로 옮긴다. 컨테이너가 런타임에 할 일(빈 정의 등록, 인스턴스 생성, 주입)을 미리 분석해 명시적인 Java 소스 코드로 생성한다. 네이티브 이미지는 이 생성 코드를 실행하므로 리플렉션이 최소화되고 시작이 빨라진다. factory.aot 패키지가 그 엔진이다.

이 모듈은 코드를 생성하는 쪽이고, 생성된 코드를 컴파일·실행하는 건 빌드 플러그인과 spring-context/spring-core의 AOT 인프라가 맡는다.

핵심 타입

  BeanFactoryInitializationAotProcessor   "팩토리 전체를 분석해 기여(contribution)를 만든다"
     └ BeanRegistrationsAotProcessor       모든 빈을 훑어 등록 코드 기여 생성
          │ 빈마다
          ▼
  BeanRegistrationAotProcessor            "빈 하나를 분석" (예: AutowiredAnnotationBPP)
     └ BeanRegistrationAotContribution     그 빈을 위한 코드 조각 기여
          │
          ▼
  BeanDefinitionMethodGenerator           "이 빈의 BeanDefinition을 돌려주는 메서드"를 생성
     ├ BeanDefinitionPropertiesCodeGenerator   프로퍼티/스코프/init 등 정의 속성 → 코드
     ├ InstanceSupplierCodeGenerator           "어떻게 인스턴스를 만들지" → 코드
     │     └ BeanInstanceSupplier              생성자/팩토리 메서드 + 인자 해소 캡슐화
     └ BeanRegistrationCodeFragments           생성 코드의 조각들(교체 가능)

  AotServices<T>   META-INF/spring/aot.factories 또는 빈에서 AOT 프로세서 로드

대비되는 두 SPI:

  • BeanFactoryInitializationAotProcessorConfigurableListableBeanFactory 전체를 받아 분석(팩토리 수준 기여).
  • BeanRegistrationAotProcessorRegisteredBean 하나를 받아 분석(빈 수준 기여). AutowiredAnnotationBeanPostProcessor가 이를 구현해, 런타임 필드 주입을 빌드 타임 코드로 대체한다.

동작 흐름 — 빈 하나가 등록 코드가 되기까지

빌드 타임 (AOT 처리 단계)
   │
   ▼ BeanRegistrationsAotProcessor.processAheadOfTime(beanFactory)
   ├ 모든 빈 이름 순회, 각 빈을 RegisteredBean으로 감쌈
   ├ BeanDefinitionMethodGeneratorFactory가 빈마다 BeanDefinitionMethodGenerator 생성
   │    이때 등록된 BeanRegistrationAotProcessor들에게 기여를 모음
   │    (예: AutowiredAnnotationBPP → "이 필드는 이렇게 주입" 코드 기여)
   ▼ BeanRegistrationsAotContribution.applyTo(generationContext, code)
   └ 각 빈에 대해 BeanDefinitionMethodGenerator.generateBeanDefinitionMethod
        ├ InstanceSupplierCodeGenerator:
        │     생성자/팩토리 메서드를 BeanInstanceSupplier 코드로
        │     → 리플렉션 대신 직접 호출 람다 생성
        │     → 인자 해소가 필요하면 AutowiredArguments 코드 동반
        ├ BeanDefinitionPropertiesCodeGenerator:
        │     스코프/lazy/primary/init-method/프로퍼티값 → setter 호출 코드
        └ 결과: "RootBeanDefinition을 만들어 반환하는 static 메서드" 소스

   생성 산출물 예 (개념적):
   ┌──────────────────────────────────────────────┐
   │ public static BeanDefinition getMyServiceBean(){│
   │   RootBeanDefinition bd =                       │
   │      new RootBeanDefinition(MyService.class);   │
   │   bd.setScope("singleton");                     │
   │   bd.setInstanceSupplier(                        │
   │      MyService::new 또는 BeanInstanceSupplier);  │
   │   // @Autowired 필드 주입도 명시적 코드로         │
   │   return bd;                                     │
   │ }                                               │
   └──────────────────────────────────────────────┘

런타임 (네이티브 이미지)
   생성된 메서드들을 호출 → 리플렉션 없이 정의 등록·인스턴스화

리플렉션을 코드로 바꾸는 핵심 — BeanInstanceSupplier

BeanInstanceSupplierInstanceSupplier의 특수 구현으로, 빈을 만드는 생성자/팩토리 메서드를 캡슐화한다. AOT에서는 generator 콜백(직접 호출 코드)을 제공해 리플렉션 호출을 직접 호출로 대체하고, 인자가 필요하면 AutowiredArguments로 의존성을 해소하는 코드를 함께 낸다. generator가 없으면 리플렉션으로 폴백하되 그에 맞는 리플렉션 hint를 기여한다.

02장에서 createBeanInstanceinstanceSupplier를 가장 먼저 확인했던 것을 기억하자 — AOT가 심는 게 바로 이 instanceSupplier다. 즉 AOT는 런타임 생성 경로를 새로 만드는 게 아니라, 기존 경로의 "공급자" 슬롯에 미리 만든 코드를 꽂아 리플렉션 분기를 건너뛰게 한다.

핵심 메서드

  • BeanRegistrationsAotProcessor.processAheadOfTime — 팩토리의 모든 빈을 등록 코드로 펼치는 진입점. 빈 수준 프로세서들의 기여를 수합한다.
  • BeanRegistrationAotProcessor.processAheadOfTime(RegisteredBean) — 빈 하나를 분석해 기여를 반환(없으면 null). 이 인터페이스를 구현한 빈은 그 자신과 의존성이 빌드 타임에 초기화되므로, 인프라 빈(BPP)에만 쓰도록 권고된다.
  • BeanDefinitionMethodGenerator.generateBeanDefinitionMethod — 한 빈의 정의를 만들어 반환하는 메서드 소스를 생성. 인스턴스 공급 + 정의 속성 두 생성기를 조립한다.
  • AotServices.loadMETA-INF/spring/aot.factories(FACTORIES_RESOURCE_LOCATION)와 빈 양쪽에서 AOT 프로세서를 모은다.

설계 포인트 / 확장점

  • 런타임 경로 재사용 — AOT는 별도 컨테이너를 만들지 않는다. instanceSupplier, RootBeanDefinition 등 기존 런타임 구조의 "슬롯"을 빌드 타임 코드로 채울 뿐이라, 리플렉션 모드와 AOT 모드가 같은 실행 경로를 공유한다.
  • 코드 조각 교체(BeanRegistrationCodeFragments) — 기본 생성 코드가 부적합한 빈(특수 팩토리 등)은 BeanRegistrationAotContribution이 코드 조각을 감싸(...Decorator) 커스텀 생성 로직을 끼울 수 있다.
  • BeanRegistrationExcludeFilter — AOT 등록에서 제외할 빈을 거르는 훅. 런타임에만 의미 있는 인프라 빈을 생성 코드에서 빼는 용도.
  • hint 기여 — 직접 호출로 못 바꾸는 부분은 리플렉션/리소스/프록시 hint를 GenerationContext에 기여해, 네이티브 이미지 빌더가 해당 멤버를 보존하게 한다(CodeWarnings가 안전성 경고를 모은다).

정리

factory.aot는 "런타임에 리플렉션으로 하던 빈 정의·인스턴스화·주입을, 빌드 타임에 명시적 Java 코드로 미리 생성"하는 엔진이다. 팩토리 수준(BeanFactoryInitializationAotProcessor)과 빈 수준(BeanRegistrationAotProcessor)의 두 SPI로 기여를 모으고, BeanDefinitionMethodGenerator가 각 빈을 "정의를 반환하는 메서드"로 펼친다. 생성 코드는 기존 런타임 구조의 instanceSupplier 슬롯에 꽂혀, 02장의 생성 흐름을 리플렉션 없이 재현한다 — 이것이 Spring 네이티브 이미지의 빠른 시작과 작은 메모리 풋프린트의 토대다.