java 코드는 어떻게 자동으로 만들어질까(2/2)

Photo by Eva Gorobets on Unsplash

지난 글에서 저는 자바코드가 자동으로 만들어지는 과정에서 자바 컴파일 과정 중 하나인 annotation processor 를 다뤘습니다.

그리고 대표적인 예제로 자바진영에서 굉장히 빈번하게 사용되는 라이브러리인 lombok 을 예로 들었습니다.

그러면 이런 의문이 드실 수 있습니다.

“그래. 자바 컴파일 단계에는 annotation processor 단계가 있고, 여기에서 annotation 을 기반으로 뭔가 할 수 있다는건 알겠어.

자바 코드도 자동으로 생성할 수 있는 javaPoet 같은 라이브러리가 있다는 것도 알겠어.

근데 lombok 은 java 코드가 새로 생성되지 않는데…? 그래서 getter 코드는 어디에 있다는거야?”

getter가 있었는데요. 없었습니다.

일반적으로 annotation processor 는 새 java 파일을 생성하지만, 기존 java 파일을 수정하진 않습니다.

그리고 lombok 은 java 파일이 아닌, 클래스 파일을 수정합니다.

java 파일은 compile 단계를 거치면 바이트 코드로 되어있는 class 파일로 변환 됩니다. 그리고 이 변환된 class 파일에 getter 코드가 들어있습니다.

getUuid 라는 getter 가 들어 있는 class 파일
위 class 파일을 보기 좋게 decompile 한 class 파일

“class 파일을 수정 할 수가 있었어..?”

일반적으로 비지니스 로직을 개발 할 때, class 파일을 수정할 일이 전혀 없습니다.

하지만 라이브러리나 프레임워크 단 에서는 종종 일어나기도 합니다.

“class 파일을 어떻게 수정하지..?”

잠시 java compile 얘기를 해보겠습니다.
compile 단계는 간략하게 3단계로 구분 할 수 있는데,

1. 파싱 및 구문 분석
2. annotation processor
3.분석 및 파일 생성

정도로 간단하게 볼 수 있습니다.

open jdk 공식문서에 있는 컴파일 과정

그리고 1 단계에서 AST(Abstract Syntax Tree) 라는 마치 dom tree 형식의 tree 구조가 나오는데, 간략하게 말하면 아래 형태와 같이 소스코드가 tree 형태로 파싱 된다고 보시면 됩니다. (java 에서만 국한 된 것은 아닙니다.)

위키백과 ‘추상 구문 트리’ 에서 발췌

컴파일러는 java 코드를 위 형태와 같은 트리 구조로 파싱 하여 구문 분석을 하고, 그 후에 위 트리 구조를 기반으로 class 파일을 생성합니다.

그러면 만약 위에 트리 구조에 노드를 끼워 넣을 수 있다면 class 파일에서 새로운 코드가 추가 되지 않을까요?

그러면 그 노드는 언제 끼워 넣을까요?

바로 지난 글에서 소개 했던 annotation processing 단계에서 끼워넣게 됩니다.

정리를 하면 이렇게 됩니다.

lombok 은 이렇게 동작하고 있습니다.

이외에도 클래스 파일을 조작할 수 있는 방법은 또 있습니다.

바이트 코드를 조작 할 수 있는 ASM 이라는 대표적인 라이브러리가 있습니다. ASM은 ClassReader, ClassVisitor, ClassWriter 등의 메소드를 통해 클래스를 직접 읽고, 수정하고, 쓸 수 있는 기능을 제공해 주고 있습니다.

asm 으로 클래스에 setter 를 추가하는 예제.

“수정할 수 있는건 알겠는데.. 이걸 어디에 쓰나?”

사실 이미 사용되고 있습니다.

스프링 aop 는 cglib 를 사용하여 프록시를 할 수도 있는데, 이때 cglib 내부에서 asm 을 이용하여 바이트 코드 레벨에서 코드를 심어주고 있습니다.

또 jacoco 같은 코드 커버리지 측정 툴 같은 데서도 사용되고 있습니다.

ASM 이 사용되고 있는 프로젝트

맺음말

1부와 2부를 통해 자바에서 코드를 자동으로 생성되는 방법에 대해 살펴봤습니다. java 개발을 하면서 너무나 자연스럽게 사용하던 query dsl 이나 lombok 같은 코드 생성 라이브러리들이 어떤 원리로 작동하는지 알아보았습니다.

무엇을 사용하든 원리를 알고 사용하는 것은 중요하다고 생각합니다.
원리를 알고 사용하게 되면 어떤 문제가 생긴다 하더라도 막연한 두려움은 사라지거든요.

이제 query dsl 이나 lombok 을 사용할 때 annotation processor 관련 에러 문구가 나오더라도 겁을 먹을 필요가 없습니다.
예를 들어 전에는 “아니 이게 무슨 소리야" 하고 덜컥 겁을 먹었다면, 이젠 ‘아 annotation processor 단계 에서 에러가 났구나. 왜 났을까?’ 하고 그 부분부터 살펴보게 되는 거죠.

원래 두려움은 무지에서 부터 오는 것이니까요.

고민이 많던 주니어 때 원리를 공부하면서 이런 사실도 함께 깨닫게 해준 선배님께도 감사의 뜻을 전하며 글을 마치겠습니다. 감사합니다.

--

--