Home Lombok의 동작 원리
Post
Cancel

Lombok의 동작 원리

최근 프로젝트를 진행하며 롬복의 동작 원리에 대해 복습할 일이 있었는데 이에 대해 간략하게 정리해보겠습니다. 학습 과정에서 작성된 글이기 때문에 잘못된 내용이 있을 수 있으며, 이에 대한 지적이나 피드백은 언제든 환영입니다.

image







1. AST(Abstract Syntax Tree)


롬복을 알려면 우선 AST(Abstract Syntax Tree)에 대해 알아야 합니다. 이는 프로그래밍 언어로 작성된 소스 코드의 추상 구문 구조의 트리입니다. 각 노드는 소스 코드에서 발생하는 구조를 나타내는데, 이는 어느 정도의 구조를 나타내긴 하지만 실제 구문에서 나타나는 모든 정보를 나타내지는 않습니다. 즉 요약해 보면 AST는 프로그램 코드의 구조를 추상적으로 표현하는 프로퍼티로 컴파일러의 구문 분석 결과물이라고 할 수 있습니다.









예를 들어 아래 클래스가 AST로 어떻게 나타나게 되는지 한 번 살펴보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class StudyWithMeUser {

    private final Long userId;

    public StudyWithMeUser(Long userId) {
        this.userId = userId;
    }

    public Long getUserId() {
        return userId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof StudyWithMeUser that)) return false;
        return getUserId().equals(that.getUserId());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getUserId());
    }

    @Override
    public String toString() {
        return userId.toString();
    }
}









AST를 보면 다음과 같은데요, 우리가 작성한 클래스 파일이 갈래로 나뉘어 분류된 것을 볼 수 있습니다.

image









그림으로 나타내면 대략 아래와 같습니다. 우리가 작성한 코드가 AST에서 분류된 것입니다.

image









그런데 이게 왜 나와? 라고 생각하실 수 있는데, 롬복을 사용하면 바이트코드를 조작해서 AST에 이를 이어 붙이기 때문 입니다. 아래와 같이 롬복을 사용하도록 코드를 수정 해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import lombok.Getter;

import java.util.Objects;

// Getter 사용
@Getter
public class StudyWithMeUser {

    private final Long userId;

    public StudyWithMeUser(Long userId) {
        this.userId = userId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof StudyWithMeUser that)) return false;
        return getUserId().equals(that.getUserId());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getUserId());
    }

    @Override
    public String toString() {
        return userId.toString();
    }
}









그러면 Imports에 아까는 없던 롬복 관련 Import가 생긴걸 볼 수 있는데요, 즉 롬복의 메서드를 가져와서 AST에 붙이는 것입니다. 트리를 보면 @Getter에 대한 Imports가 생긴 것을 볼 수 있습니다.

image









그러면 우리는 Getter를 수동으로 구현하지 않아도 롬복이 제공해 주는 @getter 메서드를 이용할 수 있게 됩니다.

image









컴파일 된 바이트 코드를 보더라도 아래와 같이 Getter 메서드가 Import 된 것을 볼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// class version 61.0 (61)
// access flags 0x21
public class project/swithme/order/common/auth/StudyWithMeUser {

  // compiled from: StudyWithMeUser.java

  ......

  // access flags 0x1
  public getUserId()Ljava/lang/Long;
   L0
    LINENUMBER 10 L0
    ALOAD 0
    GETFIELD project/swithme/order/common/auth/StudyWithMeUser.userId : Ljava/lang/Long;
    ARETURN
   L1
    LOCALVARIABLE this Lproject/swithme/order/common/auth/StudyWithMeUser; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
}









2. 정리


원래는 바이트코드는 읽을 수만 있고 조작은 할 수 없습니다. 즉 AST는 Read는 가능하지만, Write는 불가능한데요, 이를 가능하게 해주는 것이 어노테이션 프로세서(Annotation Processor) 입니다. 어노테이션 프로세서는 특정한 어노테이션이 붙은 소스 코드를 컴파일할 때 참조해 새로운 소스를 만들 수 있는 기능을 제공합니다. 따라서 이를 이용해 롬복의 어노테이션 AST를 조작하는 것입니다. 글이 너무 길었는데 그 과정을 요약해보겠습니다.

   - javac은 소스파일을 파싱해 AST를 만듭니다.
   - 롬복은 어노테이션 프로세서를 통해 AST를 동적으로 수정 후 바이트 코드를 생성합니다.
   - javac은 어노테이션 프로세서에 의해 수정된 AST를 기반으로 바이트 코드를 생성합니다.
   - 이후 컴파일 된 클래스에서 이를 사용할 수 있습니다.


This post is licensed under CC BY 4.0 by the author.