tools & libs/ETC

[Lombok] 설정 및 기본 사용법

  • -

project lombok

Java 애플리케이션을 만들다 보면 계층 간 데이터 교환에 사용되는 DTO(Data Transfer Object)를 만들게 된다. 

DTO를 만들 때는 일반적으로 다음의 규칙을 따른다.

  • 멤버 변수(field)는 private 접근 제한자를 사용한다.
  • 해당 멤버 변수에 접근하기 위해 public setter/getter를 포함한다.
  • 기본 생성자를 정의한다.
  • 선택 사항으로, 여러 파라미터 생성자를 오버로딩 하거나, toString(), equals(), hashCode() 등을 재정의 할 수 있다.

이렇듯 DTO를 만드는 작업은 대체로 간단해서 대부분 IDE들이 제공하는 기능을 통해 코드를 쉽게 생성할 수 있다. 하지만 프로젝트 진행 과정에서 리펙토링이 필요하거나 속성이 변경될 경우, DTO 코드의 수정이 필요하게 되고 이는 예상외로 많은 작업을 요구할 수 있다.

이런 번거로움을 줄이기 위해 lombok 라이브러리는 간단한 어노테이션을 통해 DTO 코드를 컴파일 타임에 필요한 코드를 생성해 줌으로 써 개발자의 수고를 덜어줄 수 있다.

지정학적으로 롬복은 인도네시아의 섬인 자바 옆에 위치하고 있어서 아주 밀접하게 관계가 있다. ㅎㅎ

 

다운로드 및 설치

intellij나 vscode에서는 별도로 설치할 필요가 없지만 Eclipse 계열에서는 설치해주어야 한다.

lombok은 https://projectlombok.org에서 다운로드 받을 수 있다.

 

Project Lombok

 

projectlombok.org

 

다운로드한 경로로 이동해서 java 명령으로 실행시킨다.

가끔 STS를 실행 중인 상태에서 실행 시 오류가 나기도 한다. STS를 종료시킨 후 실행하자.

cd "다운로드 경로"

java -jar lombok.jar

 

그럼 아래와 같은 GUI가 동작하는데 먼저 (1)을 이용해서 STS의 위치를 지정하고 (2)에 툴의 경로가 표시되게 한다. 마지막으로 (3) install / update를 이용해 설치를 마친다.

 

설치와 별도로 프로젝트에는 lombok 라이브러리가 build path에 추가되어야 한다.

 

lombok의 애너테이션

lombok은 간단히 애너테이션 선언만으로 DTO의 필요한 코드를 생성할 수 있다.

annotation 설명
@Getter/@Setter 멤버 변수들에 대해 getter / setter 메서드를 생성한다.
@ToString toString() 메서드를 재정의 한다.
@EqualsAndHashCode equals()와 hashCode()를 재정의 한다.
@NonNull 객체형에 대해 Null 값이 될수 없음을 표시한다. Lombok에 의해 생성되는 setter 또는 생성자에는 null check 로직이 삽입되며 spring 등 다른 코드에는 가독성을 높이는 효과만 있다.
@AllArgsConstructor 모든 멤버 변수를 파라미터로 하는 생성자를 생성한다.
@NoArgsConstructor 기본 생성자를 명시적으로 생성한다.
@RequiredArgsConstructor blank final 과 @NonNull로 지정된 멤버  변수들을 이용해서 생성자를 만든다. 
@Data @ToString, @EqualsAndHashCode, @Getter, @Setter 그리고 다른 생성자가 없을 경우는 @RequiredArgsConstructor까지 모두 적용한다.
@Builder builder 패턴을 이용해서 객체를 생성할 수 있도록 한다.
@Slf4j Slf4j의 Logger 타입의 변수 log를 생성한다.

 

동작 확인

실제 간단한 DTO를 만들어보고 생성되는 코드를 살펴보자.

@Getter
@RequiredArgsConstructor
public class Main {

    private final String name;
    private int age;

    public static void main(String[] args) {
        Main m = new Main("hong");
        System.out.println(m.getName());
    }
}

클래스에는 단지 두 개의 멤버 변수만 선언되어있지만 main 메서드를 살펴보면 @RequiredArgsConstructor에 의해 생성된 파라미터 생성자를 사용하기도 하고 @Getter에 의해 생성된 getName 메서드를 호출할 수도 있게 되었다. @Data는 두 개의 annotation을 모두 포함하므로 하나로 대체할 수도 있다.

@Data가 편할것 같지만 실제로 사용하다 보면 여러가지 복잡한 이유로 사용하지 않는 것이 정신건강에 유리할 경우가 많다.

아무튼 이제 번거로운 코드를 작성하느라 고생하지 말자.

 

Lombok 사용 시 주의 사항

 

@Builder

@Builder는 @AllArgsConstructor와 사용하면 매번 생성자를 만들 필요 없이 다양한 조합으로 객체를 생성할 수 있는 아주 막강한 녀석이다. 하지만 @Builder를 이용한 객체 생성 방식을 잘 알고 사용해야 한다. 

만약 BuilderExample을 아래와 같이 생성하면 어떻게 될까?

@Builder
@ToString
public class BuilderExample {
    private String name = "hong";

    public static void main(String[] args) {
        BuilderExample be = BuilderExample.builder().build();
        System.out.println(be);
    }
}

name에 default로 hong을 할당해 두었으므로 name에 'hong' 이 출력될 것 같지만 출력 결과는 BuilderExample(name=null)이다. 우리의 이름은 어디로 사라진 것일까?

다음은 lombok 공홈에 있는 예의 일부이다. created와 name 모두 기본 값이 있는 상태에서 created는 @Builder.Default가 설정되어있다. 

@Builder
public class BuilderExample {
  @Builder.Default private long created = System.currentTimeMillis();
  private String name = "hong";
}

// 실제 생성되는 자바 코드 일부
public class BuilderExample {
  private long created;                                    // 할당했던 값들은 없어짐
  private String name;
  
  BuilderExample(long created,String name) {
    this.created = created;
    this.name = name; 
  }
  
  private static long $default$created() {                 // @Default에 값을 공급하는 메서드
    return System.currentTimeMillis();
  }
  
  public static BuilderExampleBuilder builder() {
    return new BuilderExampleBuilder();
  }
  
  public static class BuilderExampleBuilder {
    private long created;                                   
    private boolean created$set;
    private String name;
       
    public BuilderExampleBuilder name(String name) {          // 전달받은 값 할당
      this.name = name;
      return this;
    }
    
    public BuilderExampleBuilder created(long created) {
      this.created = created;                                 // 전달받은 값 할당
      this.created$set = true;                                // 새로운 값 할당 여부
      return this;
    }

    public BuilderExample build() {
      // 최종적으로 객체 생성
      return new BuilderExample(created$set ? created : BuilderExample.$default$created(), name);
    }
  }
}

즉 @Builder는 기본적으로 클래스에 선언된 모든 변수들의 할당값을 사용하지 않고 전달받은 값만 사용한다. 만약 클래스의 field에 초기에 할당해놓은 값을 사용하기 위해서는 @Builder.Default를 추가해주어야 한다.

 

@ToString 순환 참조

@ToString은 객체의 모든 field들을 이용해서 toString()을 구성하는데 field가 객체인 경우 그 객체의 toString()이 계속 호출될 것이다. 만약 두 개의 객체가 서로를 field로 가지고 있으면 어떻게 될까?

@ToString @Setter
static class First{
    private Second s;
}

@ToString @Setter
static class Second{
    private First f;
}


public static void main(String[] args) {
    First f = new First();
    Second s = new Second();
    f.setS(s);
    s.setF(f);
    System.out.println(f);
}

둘 사이에는 순환 참조가 발생해서 StackOverflowError가 발생한다. 이런 경우는 @ToString을 그냥 사용할 수 없고 toString()을 직접 재정의하거나 field들에 대해 @ToString.Exclude나 @ToString.Include를 이용해서 출력 대상을 제어할 수 있다. 기본적으로는 include이므로 제외할 대상에 대해 exclude 시켜주자.

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.