스프링으로 개발할 때, 스프링의 DI, 스프링 데이터 JPA, 롬복들을 사용해서 편하게 개발한다. 이러한 기술들은 어떻게 동작하는 걸까? 바로 자바가 제공하는 코드 조작 기술로 동작한다.
자바가 제공하는 코드 조작 기술을 이해하기 위해서는 자바의 JVM 구조에 대해서 알아야 한다.
클래스 로더 시스템
.class 에서 바이트코드를 읽고 메모리에 저장
로딩: 클래스 읽어오는 과정
링크: 레퍼런스를 연결하는 과정
초기화: static 값들 초기화 및 변수에 할당
메모리
메소드 영역에는 클래스 수준의 정보 (클래스 이름, 부모 클래스 이름, 메소드, 변수) 저장. 공유 자원이다.
힙 영역에는 객체를 저장. 공유 자원이다.
스택 영역에는 쓰레드 마다 런타임 스택을 만들고, 그 안에 메소드 호출을 스택 프레임이라 부르는 블럭으로 쌓는다. 쓰레드 종료하면 런타임 스택도 사라진다.
PC(Program Counter) 레지스터: 쓰레드 마다 쓰레드 내 현재 실행할 instruction의 위치를 가리키는 포인터가 생성된다.
네이티브 메소드 스택
실행 엔진
인터프리터: 바이트 코드를 한줄 씩 실행.
JIT 컴파일러: 인터프리터 효율을 높이기 위해, 인터프리터가 반복되는 코드를 발견하면 JIT 컴파일러로 반복되는 코드를 모두 네이티브 코드로 바꿔둔다. 그 다음부터 인터프리터는 네이티브 코드로 컴파일된 코드를 바로 사용한다.
GC(Garbage Collector): 더이상 참조되지 않는 객체를 모아서 정리한다. JNI(Java Native Interface)
자바 애플리케이션에서 C, C++, 어셈블리로 작성된 함수를 사용할 수 있는 방법 제공
Native 키워드를 사용한 메소드 호출
네이티브 메소드 라이브러리
- C, C++로 작성 된 라이브러리
1.소스 코드를 바이트 코드로 컴파일시에 바이트 코드 조작
애노테이션 프로세서
애노테이션은 바이트코드로 컴파일할 때 남아있지만, 주석 같이 취급되고 디폴트로 메모리에 적재되지 않는다
@Retention(RetentionPolicy.SOURCE)
.@Retention(RetentionPolicy.CLASS)
로 명시해주어야 메모리(메소드 영역)에 적재된다. 또는@Retention(RetentionPolicy.RUNTIME)
으로 명시해주어야 런타임까지 남아 있다.If문으로 특정 애노테이션이 있으면 특정 기능을 동작하는 방식으로 작동한다.
장점: 런타임 비용이 없다.
단점: 기존 소스 코드를 변경할 때 약간의 hack이 필요하다.
오버라이드(
@override
)- 컴파일시에 해당 메소드로 소스 코드를 바이트 코드로 오버라이드 한다.
롬복
롬복은 컴파일 시점에 애노테이션 프로세서를 사용하여 소스코드의 AST(Abstract Syntax Tree)를 조작한다.
롬복의
@Getter
@Setter
는 컴파일될 때 사라지고, 바이트코드로 Getter, Setter 메소드가 생성된다.장점: 코드 생산량을 줄여준다. 핵심 로직만 남길 수 있어서 코드 가독성을 높여준다.
단점: 공개된 API가 아니다. 컴파일 단계에서 기존 소스 코드를 조작한다. 해킹의 일종이라는 관점도 있다. 버전 호환성에 문제가 생길 수 있다.
2.컴파일된 바이트 코드를 로딩해서 메모리에 적재한 바이트 코드 조작
3.메모리에 올라가 있는 바이트 코드를 실행엔진에서 런타임때 바이트 코드 조작
리플렉션 API
메모리(메소드 영역에 있는 클래스 정보, 힙 영역에 있는 객체 인스턴스)에 있는 바이트 코드를 읽어서 클래스, 인터페이스, 필드, 메소드, 애노테이션 등등에 접근하고 조작할 수 있다.
장점: 런타임 때 클래스의 인스턴스를 생성하거나, 접근 지시자를 무시해 필요한 작업을 수행할 수 있다.
단점: 성능 이슈를 야기할 수 있다. 컴파일 타임에 확인되지 않고 런타임시에 발생하는 문제를 만들 수 있다.
다이나믹 프록시
자바에서 제공하는 리플렉션 API를 사용한 프록시 기법이다. 리플렉션 API를 사용해 런타임에 특정 인터페이스들을 구현하는 클래스 또는 인스턴스를 만드는 기술이다.
스프링 데이터 JPA의 인터페이스 타입의 인스턴스는 스프링 AOP를 기반으로 프록시를 생성한다!
스프링 AOP는 다이나믹 프록시를 사용한다.
CGlib
자바에서 제공하는 다이나믹 프록시는 인터페이스 구현체만 프록시를 생성할 수 있고, 클래스는 프록시를 생성할 수 없다. 클래스의 프록시를 만들기 위해 사용하는 라이브러리다.
스프링, 하이버네이트가 사용한다
버전 호환성이 좋지 않아서 내부에 내장된 형태로 제공한다.
스프링은 런타임때 CGlib 라이브러리를 사용해서 기존 클래스를 상속한 프록시 클래스를 생성해서 의존성을 주입한다!
참고 자료