Clickin Devlog

AOT의 시대를 열다 — Micronaut 소개와 역사적 맥락

· dev
#Java#Micronaut#Quarkus#Helidon#Framework#AOT#DI#Spring
시리즈: micronaut-guide(9편)
  1. AOT의 시대를 열다 — Micronaut 소개와 역사적 맥락(현재)
  2. 첫 Micronaut 프로젝트 만들기
  3. 컴파일 타임의 마법 — Micronaut 내부 동작 심층 분석
  4. Spring과 Micronaut 비교 — 무엇을 선택할까
  5. HTTP 서버 모델과 Virtual Thread — Netty, EventLoop, 그리고 가상 스레드가 바꾼 선택 기준
  6. Micronaut Data JDBC와 Virtual Thread — 컴파일 타임 쿼리 생성과 데이터 접근 계층
  7. 선언적 HTTP Client와 Virtual Thread — @Client 인터페이스 완전 가이드
  8. Thymeleaf로 SSR 구현하기 — Micronaut Views 실전 가이드
  9. 세션 기반 인증 — 인메모리 & Redis | Micronaut Security 완전 가이드

잠깐! Micronaut 에 관심이 없으시다고요? 그래도 괜찮습니다.
시리즈의 다음 포스팅들은 본격적으로 Micronaut를 다루지만, 이 포스팅은 DI 의 개념이 태동한 시기부터의 역사를 서술하는데 집중합니다.
Micronaut에 관심이 없으시더라도, 만약 Spring의 태동기가 궁금하시다면 한번쯤 읽어주세요.

DISCLAIMER

이 글은 약 20년 전 Java 개발 업계를 다룹니다. 저는 당시 아직 학생이었기 때문에 인터넷에서 찾을 수 있는 자료를 기반으로 상황을 재구성했으나,
아쉽게도 2000년대 초반에서 2000년대 후반의 자료들은 이미 유실된 경우가 많아서 부정확하거나 사실관계가 잘못될 수 있습니다.
당시의 상황을 잘 아시는 분이 계시다면 언제든지 댓글 혹은 이메일로 연락해주시면 반영하도록 하겠습니다.

한국 Java 생태계에서 Micronaut은 아직 낯선 이름입니다. Spring Boot가 사실상 표준으로 자리 잡은 상황에서 Micronaut을 선택할 이유가 있을까요? 이 연작 시리즈는 그 질문에 답하기 위해 기획했습니다.

1편에서는 코드보다 역사를 다룹니다. Micronaut이 왜 만들어졌는지, 그 배경을 이해해야 “compile-time DI가 왜 좋은가”라는 질문에 납득이 가는 답을 얻을 수 있기 때문입니다.


1) 2002년의 Java 개발 환경

EJB의 지옥과 XML 지옥

2002년의 Java 엔터프라이즈 개발은 EJB(Enterprise JavaBeans) 중심이었습니다. EJB는 강력했지만 그 대가가 혹독했습니다.

간단한 비즈니스 로직 하나를 만들려면 최소 3개의 인터페이스(Home, Remote, Local)와 1개의 구현 클래스, 그리고 XML 배포 서술자(deployment descriptor)가 필요했습니다. JNDI 룩업으로 의존성을 가져오는 코드는 서비스 이름 문자열을 하드코딩해야 했고, 오타가 있으면 런타임에야 알 수 있었습니다.

// EJB 시대의 의존성 조회
Context ctx = new InitialContext();
OrderHome home = (OrderHome) PortableRemoteObject.narrow(
    ctx.lookup("java:comp/env/ejb/OrderBean"),
    OrderHome.class
);
Order order = home.create();

이 코드가 실패하는 경우는 배포 후 첫 호출 시점이었습니다. “xml 지옥”이라는 표현은 과장이 아니었습니다. 수백 줄의 ejb-jar.xml을 관리하는 것이 개발의 상당 부분을 차지했습니다.

Spring의 선택: DI 컨테이너와 Reflection

2002년, Rod Johnson은 “Expert One-on-One J2EE Design and Development”라는 책을 출판하면서 함께 공개한 예제 프레임워크가 있었습니다. 이것이 2004년 3월 Spring 1.0으로 발전합니다.1

Spring의 핵심 아이디어는 간단했습니다. 의존성 주입(DI)을 컨테이너가 대신해준다. 개발자는 new를 직접 호출하거나 JNDI 룩업을 작성하는 대신, 필요한 의존성을 선언만 하면 됩니다.

// Spring 초기: XML로 의존성 선언
// applicationContext.xml
// <bean id="orderService" class="com.example.OrderService">
//   <property name="orderRepository" ref="orderRepository"/>
// </bean>

public class OrderService {
    private OrderRepository orderRepository;

    // setter injection — Spring이 호출해줌
    public void setOrderRepository(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
}

EJB에 비해 대폭 단순했습니다. 하지만 Spring도 XML을 사용했고, XML의 양은 EJB보다 적었지만 여전히 상당했습니다.

그럼 Spring은 어떻게 OrderRepositoryOrderService에 주입했을까요? Reflection이었습니다.

DI 패턴의 정립과 에코시스템 확산

Rod Johnson의 2002년 책 “Expert One-on-One J2EE Design and Development”2는 약 3만 줄의 프레임워크 코드를 수록하고 있었습니다. 이 코드가 2003년 초(SourceForge 호스팅 시작) Spring의 출발점이 됩니다. 책이 공개되면서 “EJB 없이도 엔터프라이즈 Java를 잘 만들 수 있다”는 논의가 본격화됐고, 이것이 경량 컨테이너 붐의 직접적인 도화선이 됐습니다.

다만 IoC라는 개념 자체는 그보다 훨씬 오래됐습니다. 1994년 Robert C. Martin의 Dependency Inversion Principle, 1998년 Stefano Mazzocchi가 Apache Avalon 프레임워크3에서 대중화한 “컨테이너가 컴포넌트에게 의존성을 넘겨준다”는 아이디어가 선행했습니다. Setter Injection 역시 Joe Walnes와 Mike Cannon-Brookes가 XWork/WebWork2를 개발하며 먼저 정리한 개념이었고, Rod Johnson도 같은 시기 자신의 책에서 독립적으로 다뤘습니다.

PicoContainer(2003년 봄~)3는 Rod Johnson의 책에서 파생한 것이 아니라 독자적인 경로로 탄생했습니다. Avalon 출신 개발자 Paul Hammant는 Joe Walnes의 책 원고를 검토하던 Rachel Davies가 남긴 margin note 한 줄, “생성자로 의존성을 주입하면 더 우아하지 않을까” 에서 아이디어를 얻어 Spring과 평행하게 constructor injection 컨테이너를 시작했습니다. 두 팀은 이후 2003년 12월 Martin Fowler(이메일 참가)와 함께 용어를 정리했고, Rod Johnson이 쓰던 “injection” 표현을 기반으로 **“Dependency Injection”**이라는 이름이 확립됐습니다. Fowler는 2004년 1월 이를 아티클로 발표했습니다.4

이후의 흐름에서는 Spring의 영향이 뚜렷합니다. 2006년부터 Google AdWords에서 내부적으로 사용하던 DI 프레임워크를 2007년 3월 오픈소스로 공개한 Google Guice5는 공식 위키에서 “Spring이 Java DI의 길을 텄다(blazed the trail)“고 명시했습니다.6 Java annotation 기반 DI를 앞서 정착시킨 Guice 팀과 SpringSource는 이후 2009년 **JSR-330(javax.inject)**을 공동 제안하여 @Inject, @Qualifier, @Singleton 등의 어노테이션을 Java 표준으로 정립했습니다.7

.NET 진영도 같은 흐름을 따랐습니다. Castle Windsor(2004~)8를 비롯해 Spring.NET, StructureMap 등 2004~2008년 사이에 등장한 .NET 컨테이너들은 Java DI 생태계에서 개념을 가져와 System.Reflection.Emit 기반 런타임 IL 생성(Java의 CGLIB에 해당)으로 구현했습니다.

동적 언어는 다른 길을 택했습니다. Python, Ruby는 언어 자체가 런타임에 객체 구조를 자유롭게 다룰 수 있어서 컨테이너 DI가 굳이 필요하지 않았습니다. Python의 Zope Component Architecture처럼 컴포넌트 레지스트리 방식을 택하거나, 의존성을 직접 넘기는 수동 DI가 자연스러운 문화가 자리 잡았습니다.

정리하면: Rod Johnson의 2002년 책은 Spring의 직접적인 출발점이자 경량 컨테이너 붐의 도화선이었습니다. PicoContainer는 평행 진화였고, Guice·Castle Windsor 등 이후 프레임워크들은 명시적으로 Spring에 영향을 받았습니다. “DI = Reflection”은 2003~2008년 Java/.NET 정적 타입 환경의 공통 해법이었고, Spring은 그 중심에 있었습니다.


2) 한국: EJB·Struts의 시대에서 Spring 국가 표준화까지

영미권에서 이 경량 컨테이너 붐이 일던 2003~2005년, 한국 엔터프라이즈 Java 개발은 다른 궤적을 걷고 있었습니다.

당시 한국 SI/금융/공공 프로젝트의 일반적인 기술 스택은 EJB 2.x + Struts 1.x + Oracle이었습니다.9 MVC는 Struts, 비즈니스 레이어는 EJB Session Bean, 데이터베이스는 Oracle이 조합의 기본이었습니다. 2005년 ZDNet Korea 기사에는 “2003년을 기점으로 Struts, Velocity 등 오픈소스 프레임워크 통합 사례가 폭발적으로 증가”했다고 서술됩니다.10 “EJB 없이 대형 시스템을 만들 수 있느냐”는 물음 자체가 낯선 시절이었습니다.

공공 정보화 사업은 민간보다 더 심각하게 분화되어 있었습니다. 2006년 10월 공공기관 실태를 정리한 문서에는 “개발언어는 JSP, ASP, PHP 등 혼재, DB도 Oracle, MS SQL, Access 등으로 서비스별 다양하게 구성”이 직접 기재됩니다.11 기관의 운영환경(Windows/IIS면 ASP, Linux/Apache면 PHP)에 따라 언어가 결정되는 구조였고, 단일한 “공공 표준 스택”이란 없었습니다.

전자정부프레임워크: Spring을 국가 표준으로

이 혼재 구조를 바꾼 것이 **전자정부표준프레임워크(eGovFrame)**입니다. 2009년 6월 24일 행정안전부가 v1.0 소스코드를 공개했고,12 그 아키텍처는 Spring MVC + Spring Web Flow + Spring+iBATIS + Spring Transaction 조합이었습니다.13 단순한 기술 권고가 아니었습니다. 「행정기관 및 공공기관 정보시스템 구축·운영 지침(행정규칙)」에 **“전자정부표준개발프레임워크의 적용을 우선적으로 고려하여야 한다”**는 조항이 명시되며,14 신규 사업의 발주·감리 문서에서 Spring 기반 아키텍처가 기본 체크리스트가 됐습니다. 2011년 11월 기준으로 이미 166개의 공공 정보화사업에 적용됐다는 수치가 정부 브리핑에 등장합니다.15

공공 SI 비중이 높은 한국 시장 구조상, 이 표준화는 민간으로도 파급됐습니다. eGovFrame 기반 공공사업에서 훈련된 개발자 풀이 민간 프로젝트로 이동하면서, “Spring = 개발 방법론의 기본값”이라는 관성이 산업 전반으로 확산됐습니다.

Spring이 한국 커뮤니티에서 본격적으로 논의되기 시작한 것은 Spring 2.0(2006) 이후이지만, “한국 Java = Spring”이라는 등식으로 굳어진 배경에는 이 공공 표준화 과정이 있었습니다. EJB는 레거시로 밀려났고, Struts는 신규 프로젝트에서 선택할 유인이 사라졌습니다.

EJB의 고통을 거치지 않고 eGovFrame을 통해 Spring을 “표준”으로 받아들인 개발자에게는, Spring의 설계 선택들이 어떤 문제를 풀기 위한 것인지 직관적으로 이해하기 어렵습니다. Micronaut이 한국 생태계에서 낯선 이유 중 하나가 여기 있습니다. Spring이 해결한 문제를 충분히 겪지 않은 채 Spring을 도구로 받아들인 경우, Spring의 한계도 체감하기 어렵습니다.


3) 왜 Reflection 기반으로 DI를 구현했는가

당시엔 Annotation Processor도 없었다

Spring 1.0이 나온 2004년에는 선택지가 제한적이었습니다. 오늘날 당연하게 쓰는 도구들이 없었습니다.

  • Annotation: Java 5(2004년 9월)에 도입. Spring 1.0 당시 없었음
  • APT (Annotation Processing Tool): Java 5와 함께 등장
  • javax.annotation.processing: Java 6(2006년)에 표준화
  • Maven: 2004년 등장. Spring 1.0 당시 빌드 도구는 Ant
  • Gradle: 2007년 등장

2003년의 Java 세계에서 컴파일 타임에 코드를 분석하거나 코드를 생성하는 표준 방법은 없었습니다. Reflection은 런타임에 클래스 정보를 탐색할 수 있는 유일하고도 표준적인 메커니즘이었습니다.

// Spring이 내부적으로 하는 일 (단순화)
Class<?> clazz = Class.forName("com.example.OrderService");
Field field = clazz.getDeclaredField("orderRepository");
field.setAccessible(true); // private 접근 허용
field.set(orderServiceInstance, orderRepositoryInstance);

XML에 작성한 의존성 정보를 런타임에 읽어서 reflection으로 주입하는 방식은 EJB의 복잡성을 극적으로 줄였습니다.

Java 에코시스템 성숙 타임라인

당시 반경을 더 넓혀 보면, annotation processing뿐 아니라 빌드 에코시스템 전반이 미성숙했습니다.

도구출시 시점
Spring 1.02004년 3월
Java 5 (Annotation 도입)2004년 9월
Maven 1.02004년 7월
Annotation Processing 표준화2006년 (Java 6)
Gradle2007년
Spring 2.5 (Annotation DI)2007년 11월

Spring이 @Autowired와 같은 어노테이션 기반 DI를 도입한 것은 2007년의 Spring 2.5입니다. 4년이 지난 후였고, 그때도 내부 구현은 여전히 reflection이었습니다. 어노테이션이 XML을 대체했을 뿐, 런타임에 reflection으로 어노테이션을 읽고 의존성을 해석하는 방식은 동일했습니다.

Reflection 기반 DI는 당시 기술 환경에서 최선의 선택이었습니다. 그리고 그 선택은 오늘날까지 Spring의 근간으로 이어지고 있습니다.


4) 시대의 변화: 클라우드, 컨테이너, 서버리스

Spring이 탄생한 2003년과 Micronaut이 등장한 2018년 사이에 세상은 크게 바뀌었습니다.

콜드 스타트가 중요해진 이유

2000년대 중반까지 서버는 “항상 켜져 있는 물리 장비”였습니다. 시작 시간이 30초든 1분이든 큰 문제가 없었습니다. 서버는 배포 후 수개월을 재시작 없이 운영되었습니다.

2013년 Docker의 등장과 함께 컨테이너 환경이 확산되었고, 2014년 AWS Lambda가 발표되며 서버리스가 등장했습니다.16 그리고 클라우드 VM을 탄력적으로 운용하는 방식도 보편화되었습니다. 세 환경 모두에서 시작 시간이 핵심 지표가 됩니다.

  • 클라우드 VM 오토스케일링: EC2 Auto Scaling Group과 CloudWatch를 연동해 CPU 사용률이 임계치를 넘으면 새 인스턴스를 자동으로 추가하는 구성이 일반적입니다. VM 한 대에 Spring 인스턴스 하나를 띄우는 방식이더라도, 스케일 아웃 시 새 인스턴스가 트래픽을 받을 준비가 될 때까지 걸리는 시간이 병목이 됩니다. 시작 시간이 5초면 그 5초 동안 기존 인스턴스가 과부하를 혼자 감당해야 합니다.
  • 컨테이너 / 쿠버네티스: HPA(Horizontal Pod Autoscaler)가 파드를 추가할 때 새 파드가 Ready 상태가 될 때까지 트래픽을 받지 못합니다. 롤링 업데이트에서도 구버전 파드를 종료하기 전에 신버전이 준비되어야 합니다.
  • 서버리스 함수: AWS Lambda는 요청이 없으면 인스턴스가 종료되고 다음 요청 시 새로 시작합니다. JVM 콜드 스타트가 수 초에 달하면 그 지연이 API 응답 시간으로 그대로 나타납니다.

Spring Boot 애플리케이션의 JVM 시작 시간은 기본적인 웹 서비스 구성 기준 2~5초 수준입니다.17 물리 서버 시대에는 무시할 수 있었던 이 시간이, 탄력적으로 크기가 변하는 클라우드 환경에서는 가용성과 비용에 직접 영향을 주기 시작했습니다.

메모리 요금이 실시간으로 청구되는 세상

클라우드 환경에서 메모리는 돈입니다.

AWS Lambda는 128MB부터 10,240MB까지 메모리를 설정하며, 메모리와 실행 시간의 곱으로 요금이 청구됩니다. Spring Boot 애플리케이션의 메모리 사용량은 기본적인 REST 서비스 기준 300MB 내외입니다.18 단순한 마이크로서비스라도 Spring의 클래스패스 스캔, reflection 기반 초기화, 내장 서버 등이 메모리를 차지합니다.

컨테이너 환경에서도 마찬가지입니다. 쿠버네티스 클러스터에서 수십 개의 마이크로서비스가 각각 300MB를 사용하면 전체 비용이 빠르게 올라갑니다.

클라우드 네이티브 환경은 빠른 시작과 낮은 메모리 사용량을 요구하기 시작했습니다. Spring은 이 요구에 후향적으로 대응해야 했습니다.


5) Micronaut의 등장 (2018)

Graeme Rocher — Grails에서 Micronaut으로

Micronaut의 창시자 Graeme Rocher는 낯선 이름이 아닙니다. 그는 Grails 프레임워크의 핵심 개발자였습니다. Grails는 Groovy 언어로 Ruby on Rails 스타일의 개발 경험을 Java 생태계에 가져온 프레임워크로, Spring 위에서 동작합니다.

Grails를 수년간 개발하면서 Rocher는 Spring의 한계를 깊이 이해하게 됩니다. Spring 기반 Grails 애플리케이션은 시작 시간이 길고, 클라우드 환경에 적합하지 않았습니다. 근본 원인은 Spring의 runtime reflection과 classpath scanning이었습니다.

그는 OCI(Object Computing Inc.)에 합류하여 이 문제를 근본부터 해결하는 새로운 프레임워크를 설계하기 시작합니다.

주요 이정표:

  • 2018년 3월: 스페인 마드리드의 Greach 컨퍼런스에서 Micronaut 최초 공개 발표
  • 2018년 10월: Micronaut 1.0 정식 릴리즈19
  • 2020년: Micronaut Foundation 설립 (벤더 중립적 거버넌스)
  • 2023년: Micronaut 4.0 릴리즈 (JDK 17 기준선, 가상 스레드 지원)

AOT란 무엇인가

AOT(Ahead-Of-Time) 컴파일은 런타임 이전에 작업을 완료하는 접근입니다. 대비되는 개념은 JIT(Just-In-Time)로, 런타임에 최적화를 수행합니다.

Java 생태계에서 AOT의 대표적인 사례가 GraalVM Native Image입니다. JVM이 런타임에 JIT 컴파일로 생성하는 코드를 미리 컴파일해서 네이티브 바이너리로 만들어두는 것입니다.

Micronaut은 이 AOT 철학을 DI와 AOP에 적용했습니다. Spring이 런타임에 reflection으로 하던 작업들, 즉 어노테이션 분석, 의존성 해석, 프록시 생성을 모두 컴파일 타임의 annotation processing 단계에서 처리합니다.

Spring의 DI 처리 흐름:
  javac → .class 파일 → JVM 시작 → ClassPath 스캔 →
  Reflection으로 어노테이션 분석 → BeanFactory 구성 → 애플리케이션 준비

Micronaut의 DI 처리 흐름:
  javac + Annotation Processor → BeanDefinition 클래스 생성 → .class 파일 →
  JVM 시작 → 생성된 BeanDefinition으로 즉시 초기화 → 애플리케이션 준비

생성된 BeanDefinition 클래스는 reflection 없이 생성자를 직접 호출합니다. JVM이 시작하는 시점에 “무엇을 어떻게 만들어야 하는가”가 이미 코드로 완성되어 있는 것입니다.


6) 같은 시대의 다른 해법들: Quarkus와 Helidon

Micronaut이 해결하려 한 문제, “Spring의 런타임 reflection이 클라우드 환경에서 병목이 된다”는 문제를 독립적으로 인식한 팀들이 더 있었습니다. 거의 같은 시기에 두 프레임워크가 더 등장합니다.

Quarkus: 표준을 지키며 빌드 타임으로

2019년 Red Hat이 Quarkus 1.0을 출시했습니다.20 JBoss, Hibernate, RESTEasy를 만든 팀들이 합류한 프로젝트로, Jakarta EE 생태계에 깊은 뿌리를 두고 있습니다.

아이디어는 Micronaut과 유사하지만 구현 방식이 다릅니다. Micronaut은 Java annotation processor(APT)로 빌드 시 소스 코드를 분석해서 BeanDefinition 클래스를 생성합니다. Quarkus는 Jandex21라는 별도 인덱서로 클래스패스를 빌드 타임에 인덱싱하고, Gizmo라는 바이트코드 생성기로 직접 바이트코드를 만들어냅니다. 그리고 Arc22라는 CDI 구현체가 이 생성된 코드를 런타임에 빠르게 초기화합니다.

Micronaut의 처리:
  소스 코드 → javac + APT → BeanDefinition 클래스(.java 생성 후 컴파일) →
  JVM 시작 → 생성된 클래스로 즉시 초기화

Quarkus의 처리:
  소스 코드 → Jandex 인덱싱 → Augmentation 단계(Gizmo로 바이트코드 직접 생성) →
  JVM 시작 → Arc CDI로 즉시 초기화

이 방식 차이는 철학의 차이를 반영합니다. Micronaut은 가볍고 독자적인 컨테이너를 새로 설계했습니다. Quarkus는 Jakarta CDI 표준 스펙을 완전히 구현하되, 모든 처리를 빌드 타임으로 옮겼습니다. 기존 CDI 기반 라이브러리나 MicroProfile 코드와의 호환성 면에서 Quarkus가 유리한 이유입니다.

Quarkus의 클래스 로딩 구조도 독특합니다. 개발 모드와 프로덕션 모드를 5계층 다중 ClassLoader 전략으로 처리합니다.23 덕분에 개발 모드에서 빠른 핫 리로드를 지원하면서도, 프로덕션 빌드(fast-jar)에서는 빌드 타임 인덱싱 결과를 활용해 시작 시간을 단축합니다.

Helidon: DI를 컴파일 타임으로 옮기지 않은 선택

같은 해(2018년 9월) Oracle도 Helidon을 공개했습니다.24 Helidon SE(DI 없는 함수형 모드)와 Helidon MP(MicroProfile CDI 기반 모드) 두 가지를 제공했습니다.

Helidon은 Micronaut, Quarkus와 달리 DI 처리를 컴파일 타임으로 옮기지 않았습니다. Helidon MP의 CDI는 런타임 기반으로 동작합니다. DI 방식만 놓고 보면 “같은 시대에 등장했지만 다른 선택을 했다”는 대조적 사례입니다.

세 프레임워크 비교

MicronautQuarkusHelidon
1.0 GA2018년 10월2019년 11월2019년 초
만든 곳OCI (Graeme Rocher)Red HatOracle
DI 처리 시점컴파일 타임 (APT)컴파일 타임 (Jandex+Gizmo)런타임 CDI (MP) / 없음 (SE)
DI 표준JSR-330 기반 커스텀Jakarta CDI 완전 준수MicroProfile CDI

세 프레임워크가 거의 동시에 등장한 것은 우연이 아닙니다. 클라우드·컨테이너·서버리스 환경에 적합한 Java 프레임워크라는 같은 요구에, 각기 다른 배경과 철학을 가진 팀들이 독립적으로 답을 내놓은 결과입니다.

이 시리즈는 Micronaut에 집중합니다. 세 중에 하나를 깊이 파는 것이 실제로 더 유용하기 때문입니다. Quarkus나 Helidon을 다루는 포스팅은 별도 기회를 찾겠습니다.


7) Micronaut의 핵심 철학

Reflection-free

Micronaut은 런타임에 reflection을 사용하지 않습니다. 정확히는 framework core에서 사용하지 않는다는 의미입니다. 사용자 코드가 reflection을 쓰는 것은 막지 않습니다.

왜 reflection이 문제인가?

  1. 성능: reflection 호출은 일반 메서드 호출보다 느립니다. 특히 JVM warm-up 이전인 시작 시점에 수천 건씩 반복 실행될 때 병목이 됩니다.25
  1. 메모리: reflection에 필요한 메타데이터가 메모리를 차지합니다. Spring은 시작 시 수천 개의 클래스를 스캔하고 메타데이터를 캐싱합니다.
  2. GraalVM Native Image 비호환: Native Image는 reflection 사용 정보를 사전에 선언해야 합니다. 동적 reflection은 Native Image에서 동작하지 않습니다. Spring Native는 이를 위해서 reflect-config.json, proxy-config.json 등 다양한 Metadata 파일을 생성하는 복잡한 절차를 거칩니다.26
  3. 타입 안전성 부재: reflection 오류는 컴파일 타임이 아닌 런타임에 발생합니다.

Compile-time DI & AOP

Micronaut의 annotation processor는 빌드 시 다음 작업을 수행합니다.

  • @Singleton, @Inject, @Controller 등의 어노테이션을 분석
  • 각 Bean에 대한 BeanDefinition 클래스를 생성
  • AOP 어드바이스가 있으면 프록시 클래스를 생성
  • 의존성 그래프를 미리 계산

이 덕분에 의존성 오류가 컴파일 타임에 발견됩니다. Spring에서 @Autowired된 빈이 없을 때 NoSuchBeanDefinitionException 오류가 런타임에 터지는 것과 대조적입니다.

주의할 점은, 여기서 말하는 “없는 빈”은 클래스 자체가 없는 경우가 아닙니다. 클래스가 없으면 Java 컴파일러가 먼저 막기 때문에 Spring/Micronaut 모두 컴파일 오류가 납니다. 실제 차이가 나는 시나리오는 클래스는 존재하지만 Bean으로 등록되지 않은 경우입니다.

// OrderRepository 인터페이스는 코드베이스에 존재하지만,
// @Repository / @Component 등의 어노테이션이 없어 Bean으로 등록되지 않은 상태를 가정

// Spring: @Service, @Autowired 모두 컴파일 성공.
//         애플리케이션 시작 시 컨텍스트 로드 단계에서 런타임 오류 발생.
@Service
public class OrderService {
    @Autowired
    private OrderRepository repository;
    // 시작 시: NoSuchBeanDefinitionException: No qualifying bean of type 'OrderRepository'
}

// Micronaut: annotation processor가 빌드 시 의존성 그래프를 검증.
//            OrderRepository에 대한 BeanDefinition이 없으면 컴파일 오류 발생.
@Singleton
public class OrderService {
    @Inject
    public OrderService(OrderRepository repository) {
        // 빌드 시: error: No bean of type [OrderRepository] found
        this.repository = repository;
    }
}

GraalVM Native Image 지원

Micronaut는 GraalVM native 지원이 좋기로 유명합니다. 하지만 Micronaut 1.0이 출시된 2018년 10월 당시, GraalVM은 아직 안정 버전이 없었습니다. GraalVM 19.0 (첫 stable release)은 2019년 5월에야 등장합니다.27 따라서 시작부터 GraalVM을 염두에 두고 설계하지는 않았다고 추정할 수 있습니다. 대신, Micronaut 의 지향점이 GraalVM과 잘 맞았다고 볼 수 있습니다.

Micronaut의 reflection-free, compile-time DI 설계는 애초에 빠른 시작 시간과 낮은 메모리 사용량이라는 목표에서 비롯된 것이었습니다. 그런데 GraalVM Native Image가 요구하는 조건, 즉 “런타임 reflection 없이 정적으로 분석 가능한 코드”가 Micronaut의 설계와 자연스럽게 맞아떨어졌습니다. 결과적으로 GraalVM이 성숙해지면서 Micronaut은 Native Image 지원을 큰 추가 작업 없이 확보할 수 있었습니다.

Native Image는 실행에 필요한 모든 코드를 AOT 컴파일하여 JVM 없이 동작하는 바이너리를 만듭니다. 이를 위해 reflection 사용처를 모두 사전에 알아야 합니다. Micronaut은 런타임 reflection을 사용하지 않기 때문에 별도의 reflection 설정 파일 없이 Native Image를 생성할 수 있습니다.

같은 애플리케이션을 Native Image로 빌드하면:28

  • 시작 시간: JVM 모드 ~500ms → Native Image ~10ms 미만
  • 메모리: JVM 모드 ~254MB → Native Image ~70MB

정리

Micronaut은 Spring을 비판하거나 대체하려는 것이 아닙니다. Spring은 그것이 탄생한 시대의 최선이었고, 지금도 수많은 프로젝트에서 최선입니다.

Micronaut은 클라우드, 컨테이너, 서버리스라는 새로운 실행 환경이 요구하는 특성, 빠른 시작, 낮은 메모리, GraalVM 호환성을 컴파일 타임 AOT 처리로 달성하려 했습니다.

1편의 핵심을 한 문장으로 정리하면: Reflection 기반 DI는 2003년의 기술 환경에서 합리적인 선택이었고, Micronaut은 그 선택이 맞지 않는 실행 환경이 생겼을 때 등장했습니다.

“AOT 처리가 실제로 어떤 코드를 생성하는가”는 3편에서 자세히 다룹니다. 2편에서는 먼저 Micronaut으로 첫 프로젝트를 만들어봅니다.


다음 편: 첫 Micronaut 프로젝트 만들기

Footnotes

  1. https://en.wikipedia.org/wiki/Spring_Framework

  2. Rod Johnson, Expert One-on-One J2EE Design and Development, Wrox Press, 2002년 10월. 약 3만 줄의 프레임워크 코드를 수록했으며, 2003년 초 SourceForge에 오픈소스로 공개된 뒤 2004년 3월 Spring 1.0 GA로 정식 릴리즈됩니다.

  3. Apache Avalon의 IoC 역사와 PicoContainer의 탄생 배경에 대한 1차 자료: PicoContainer — Inversion of Control History 2

  4. 2004년 1월 23일 발표. PicoContainer 팀, Spring 팀과의 대화를 바탕으로 작성된 글로, “DI”라는 이름과 세 가지 주입 방식(생성자/세터/인터페이스)을 정립했습니다. 현재도 DI 관련 1차 레퍼런스로 가장 많이 인용됩니다. martinfowler.com/articles/injection.html

  5. 2006년 구글 내부 AdWords 개발에 사용하기 위해 제작 후 오픈소스 공개. Java annotation 기반 DI를 적극 활용한 첫 프레임워크 중 하나. google/guice

  6. https://github.com/google/guice/wiki/SpringComparison

  7. Google Guice 팀과 SpringSource가 공동 제안한 JSR-330. @Inject, @Qualifier, @Named, @Singleton, Provider<T> 등을 표준화하여 DI 구현체 간 코드 이식성을 확보했습니다. googlecode.blogspot.com — javax.inject

  8. Castle Project © 2004~. Castle Windsor / Castle DynamicProxy

  9. 2004년 KLDP(한국 리눅스 사용자 그룹) 커뮤니티 글. J2EE 개발자 스택으로 Struts, EJB가 함께 나열됩니다. https://kldp.org/node/41808

  10. ZDNet Korea 연재(2005). “2003년을 기점으로 Struts, Velocity 등의 프레임워크 통합 사례가 폭발적으로 증가”를 직접 서술합니다. https://zdnet.co.kr/view/?no=00000039131310

  11. 경기도농업기술원 관련 보고서(2007 공개, 내부 조사 시점 2006.10). https://nongup.gg.go.kr/wp-content/uploads/sites/2/2013/08/20070803122587348.pdf

  12. NIA 보도자료(2019). “2009년 6월 최초 소스코드 공개(v1.0)“로 명시합니다. https://www.nia.or.kr/site/nia_kor/ex/bbs/View.do?bcIdx=21582&cbIdx=90549

  13. 2009년 개발자 블로그. eGovFrame 구성요소를 당시 시점에서 열거합니다(“Spring MVC + iBATIS + Hibernate + Transaction…”). https://vicki.tistory.com/750

  14. 「행정기관 및 공공기관 정보시스템 구축·운영 지침」(행정규칙), 국가법령정보센터. https://www.law.go.kr/LSW/admRulInfoP.do?admRulSeq=2100000197311&chrClsCd=010201

  15. 정책브리핑(2011.11). “2009년 6월 최초 공개 이후 166개의 공공 정보화사업 적용” 언급. https://www.korea.kr/briefing/pressReleaseView.do?newsId=155795314

  16. AWS Lambda는 2014년 11월 AWS re:Invent에서 처음 발표(preview)됐고, GA는 2015년 4월입니다. https://aws.amazon.com/blogs/compute/aws-lambda-is-generally-available/

  17. 의존성 구성과 애플리케이션 규모에 따라 편차가 큽니다. 참고: dsyer/spring-boot-startup-bench

  18. 기본 REST 서비스 기준 추정치. 참고: dsyer/spring-boot-memory-blog

  19. https://micronaut.io/2018/10/23/micronaut-1-0-ga-released/

  20. Quarkus는 2019년 3월에 처음 공개(0.x)됐고, 1.0 GA는 2019년 11월에 출시됐습니다. https://quarkus.io

  21. Jandex — SmallRye Java Annotation Indexer. 클래스패스를 빌드 타임에 인덱싱하여 런타임 스캔을 제거합니다. https://github.com/smallrye/jandex

  22. Quarkus CDI 가이드 (Arc 구현체). https://quarkus.io/guides/cdi-reference

  23. Quarkus 클래스 로딩 레퍼런스 (Base, Augmentation, Deployment, Base Runtime, Runtime ClassLoader의 5계층). https://quarkus.io/guides/class-loading-reference

  24. Helidon v4 공식 소개. https://helidon.io/docs/v4/about/introduction

  25. reflection의 실제 오버헤드는 JVM 버전과 호출 패턴에 따라 다르며, JIT이 일부 경로를 최적화합니다. 시작 시점의 대량 반복 호출이 특히 문제가 됩니다.

  26. https://docs.spring.io/spring-boot/docs/3.2.3/reference/html/native-image.html#native-image.introducing-graalvm-native-images.understanding-aot-processing

  27. GraalVM Release Calendar 기준. 1.0-RC1(2018년 4월)~RC16(2019년 2월)은 프리릴리즈, 19.0.0이 첫 GA 릴리즈입니다.

  28. 단순 REST 서비스 기준 추정치로, 앱 복잡도에 따라 편차가 있습니다. 참고: Build Native Java Apps with Micronaut, Quarkus, and Spring Boot

댓글 영역에 가까워지면 자동으로 불러옵니다.

Preparing comments...