Java Lombok 마스터하기: 반복 코드 확 줄이고 개발 생산성 200% 올리는 법

Java Lombok 가이드, Sense

Java로 개발을 하다 보면 반복적으로 작성해야 하는 코드들이 있습니다. 예를 들어, 데이터 객체(DTO, VO, Entity 등)를 만들 때마다 Getter, Setter, 생성자, toString(), equals(), hashCode() 메서드 등을 구현해야 하죠. 이러한 기계적인 코드 작성은 개발 시간을 소모시키고, 클래스 파일의 길이를 늘려 가독성을 떨어뜨리기도 합니다.

이런 불편함을 해결해주는 아주 유용한 라이브러리가 바로 Lombok입니다. 이번 포스팅에서는 Lombok이 무엇인지, 왜 필요한지, 그리고 어떻게 활용할 수 있는지 자세히 알아보겠습니다.

1. Lombok이란?

Lombok은 Java 라이브러리로, 어노테이션(Annotation)을 사용하여 컴파일 시점에 반복적인 자바 코드를 자동으로 생성해주는 도구입니다. 개발자는 Lombok 어노테이션을 클래스나 필드에 추가하기만 하면, Getter, Setter, 생성자, toString() 등의 메서드를 직접 작성할 필요 없이 Lombok이 대신 만들어줍니다.

예시: Lombok 사용 전후 비교

Lombok을 사용하지 않을 때:

public class Member {
    private String id;
    private String name;
    private int age;

    public Member(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Member{" +
               "id='" + id + '\'' +
               ", name='" + name + '\'' +
               ", age=" + age +
               '}';
    }
    // equals(), hashCode() ...
}

Lombok을 사용할 때:

import lombok.Getter;
import lombok.Setter;
import lombok.AllArgsConstructor;
import lombok.ToString;
// import lombok.Data; // @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor 포함

@Getter
@Setter
@AllArgsConstructor
@ToString
// @Data // 위 어노테이션들을 한번에 적용하고 싶을 때
public class Member {
    private String id;
    private String name;
    private int age;
}

단 몇 줄의 어노테이션으로 코드가 훨씬 간결해진 것을 볼 수 있습니다.

2. Lombok 설치 및 설정

Lombok을 사용하기 위해서는 프로젝트에 의존성을 추가하고, IDE에 Lombok 플러그인을 설치해야 합니다.

2.1. 의존성 추가

  • Maven:

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version> <!-- 최신 버전 확인 -->
            <scope>provided</scope>
        </dependency>
    </dependencies>
    
  • Gradle:

    dependencies {
        compileOnly 'org.projectlombok:lombok:1.18.30' // 최신 버전 확인
        annotationProcessor 'org.projectlombok:lombok:1.18.30' // 최신 버전 확인
    }
    

    provided (Maven) 또는 compileOnly (Gradle) 스코프를 사용하는 이유는 Lombok 어노테이션이 컴파일 시점에만 필요하고, 런타임에는 생성된 코드가 사용되기 때문입니다.

2.2. IDE 플러그인 설치

  • IntelliJ IDEA: 최신 버전의 IntelliJ IDEA에는 Lombok 플러그인이 기본적으로 내장되어 있습니다. 만약 활성화되어 있지 않다면, File > Settings (Preferences) > Plugins에서 "Lombok"을 검색하여 설치하고, File > Settings (Preferences) > Build, Execution, Deployment > Compiler > Annotation Processors에서 "Enable annotation processing"을 체크합니다.
  • Eclipse: Help > Eclipse Marketplace에서 "Lombok"을 검색하여 설치합니다. 설치 후 Eclipse를 재시작해야 합니다.

3. Lombok의 주요 어노테이션과 활용법

Lombok은 다양한 어노테이션을 제공하여 코드 작성을 자동화합니다. 자주 사용되는 주요 어노테이션들을 살펴보겠습니다.

3.1. @Getter / @Setter

  • 필드에 대한 Getter와 Setter 메서드를 자동으로 생성합니다.
  • 클래스 레벨에 적용하면 모든 non-static 필드에 대해, 필드 레벨에 적용하면 해당 필드에 대해서만 생성됩니다.
import lombok.Getter;
import lombok.Setter;

@Getter
public class Product {
    private final String productCode; // Getter만 생성 (final 필드는 Setter 생성 불가)

    @Setter
    private String productName;       // Getter, Setter 모두 생성

    private int price;                // Getter만 생성 (클래스 레벨 @Getter)
                                    // Setter는 없음

    public Product(String productCode, String productName, int price) {
        this.productCode = productCode;
        this.productName = productName;
        this.price = price;
    }
}

3.2. 생성자 관련 어노테이션

  • @NoArgsConstructor: 파라미터가 없는 기본 생성자를 생성합니다.
  • @AllArgsConstructor: 모든 필드를 파라미터로 받는 생성자를 생성합니다.
  • @RequiredArgsConstructor: final 또는 @NonNull 어노테이션이 붙은 필드만을 파라미터로 받는 생성자를 생성합니다. 주로 의존성 주입(DI) 시에 유용합니다.
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.NonNull;

@Getter
@NoArgsConstructor // 기본 생성자: public User() {}
@AllArgsConstructor // 모든 필드 생성자: public User(String id, String email, int points) {}
@RequiredArgsConstructor // NonNull 필드 생성자: public User(@NonNull String id, @NonNull String email) {}
public class User {
    @NonNull // 이 필드는 RequiredArgsConstructor에 포함되며, null 체크 로직이 추가됨
    private String id;
    @NonNull
    private String email;
    private int points; // RequiredArgsConstructor에 포함되지 않음
}

3.3. @ToString

  • toString() 메서드를 자동으로 생성합니다.
  • exclude 옵션으로 특정 필드를 제외하거나, callSuper = true 옵션으로 부모 클래스의 toString() 결과를 포함할 수 있습니다.
import lombok.ToString;
import lombok.Getter;
import lombok.AllArgsConstructor;

@Getter
@AllArgsConstructor
@ToString(exclude = "password", callSuper = true)
public class Account extends BaseEntity { // BaseEntity에 id 필드가 있다고 가정
    private String username;
    private String password; // toString 결과에서 제외됨
    private String email;
}

호출 시: Account(super=BaseEntity(id=1), username=testuser, email=test@example.com) 와 같이 출력됩니다.

3.4. @EqualsAndHashCode

  • equals()hashCode() 메서드를 자동으로 생성합니다.
  • 기본적으로 모든 non-static, non-transient 필드를 사용하지만, of 옵션으로 특정 필드만 지정하거나 exclude로 특정 필드를 제외할 수 있습니다.
  • callSuper = true 옵션으로 부모 클래스의 필드도 비교에 포함할 수 있습니다.
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.AllArgsConstructor;

@Getter
@AllArgsConstructor
@EqualsAndHashCode(of = {"id"}, callSuper = false) // id 필드만으로 동등성 비교
public class Item {
    private Long id;
    private String itemName;
    private int quantity;
}

3.5. @Data

  • @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 모두 합쳐놓은 어노테이션입니다.
  • 매우 편리하지만, 원치 않는 Setter가 생성되거나 final이 아닌 필드에 대해 RequiredArgsConstructor가 동작하지 않는 등 의도치 않은 동작이 발생할 수 있어 주의가 필요합니다.
  • 불변(Immutable) 객체를 원한다면 @Value를 고려하거나, 필요한 어노테이션만 조합해서 사용하는 것이 좋습니다.
import lombok.Data;

@Data // 모든 것을 자동으로!
public class Order {
    private Long orderId;
    private String customerName;
    private java.util.Date orderDate;
}

3.6. @Builder

  • 빌더 패턴(Builder Pattern) 코드를 자동으로 생성해줍니다. 객체 생성 시 가독성을 높이고, 필요한 값만 설정하여 객체를 유연하게 생성할 수 있습니다.
  • 클래스 또는 생성자에 적용할 수 있습니다.
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
@Builder // 클래스 레벨에 적용하면 모든 필드를 대상으로 빌더 생성
public class Configuration {
    private String host;
    private int port;
    private boolean useTls;
    private String username;
    private String password;

    // 특정 필드만으로 빌더를 만들고 싶다면 생성자에 @Builder를 붙임
    // @Builder
    // public Configuration(String host, int port) {
    //     this.host = host;
    //     this.port = port;
    // }
}

// 사용 예시
// Configuration config = Configuration.builder()
//         .host("localhost")
//         .port(8080)
//         .useTls(false)
//         .build();
// System.out.println(config);

3.7. @Log 관련 어노테이션

  • @Slf4j, @Log4j2, @CommonsLog 등 다양한 로깅 프레임워크에 대한 Logger 객체를 자동으로 생성하고 초기화해줍니다.
  • private static final Logger 필드를 직접 선언할 필요가 없습니다.
import lombok.extern.slf4j.Slf4j;

@Slf4j // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MyService.class); 자동 생성
public class MyService {
    public void doSomething() {
        log.info("Doing something important...");
        try {
            // ... 작업 ...
            log.debug("Successfully did something.");
        } catch (Exception e) {
            log.error("Error during something", e);
        }
    }
}

4. Lombok 사용 시 장점 및 주의사항

장점:

  1. 코드 간결성 향상: 반복적인 코드가 사라져 클래스가 매우 깔끔해지고 가독성이 좋아집니다.
  2. 생산성 증대: Getter, Setter 등을 직접 작성하는 시간을 절약하여 핵심 로직 개발에 집중할 수 있습니다.
  3. 유지보수 용이성: 필드 추가/삭제 시 관련된 메서드를 수정할 필요 없이 Lombok이 자동으로 처리해줍니다.

주의사항:

  1. IDE 플러그인 필수: Lombok 코드를 IDE가 제대로 인식하고 컴파일 오류를 표시하지 않으려면 Lombok 플러그인 설치 및 설정이 필수입니다.
  2. 어노테이션의 동작 이해: 각 어노테이션이 어떤 코드를 생성하는지 대략적으로 이해하고 사용해야 의도치 않은 문제를 피할 수 있습니다. (예: @Data의 무분별한 사용)
  3. 팀원 간 합의: 프로젝트 팀원 모두가 Lombok에 익숙해야 코드 리뷰나 협업이 원활합니다.
  4. Delombok: Lombok이 생성하는 실제 코드를 보고 싶다면 delombok 기능을 사용할 수 있습니다. 이는 디버깅이나 코드 이해에 도움이 될 수 있습니다.

5. 실무 활용 예시

5.1. DTO (Data Transfer Object) 작성

import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.ToString;

@Getter
@Setter // 필요에 따라 Setter는 제외하거나 AccessLevel.PRIVATE 등으로 제한 가능
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserDto {
    private Long id;
    private String username;
    private String email;
    private String address;
}

5.2. Entity 클래스 (JPA 사용 시)

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.AccessLevel;
import lombok.ToString;
import lombok.EqualsAndHashCode;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // JPA 스펙상 기본 생성자 필요, protected로 제한
@ToString(exclude = {"sensitiveData"}) // 민감 정보는 toString에서 제외
@EqualsAndHashCode(of = "id") // id 필드로만 동등성 비교
public class MemberEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;
    private String sensitiveData; // 예시 필드

    @Builder // 모든 인자를 받는 생성자 대신 빌더 사용
    public MemberEntity(String name, String email, String sensitiveData) {
        this.name = name;
        this.email = email;
        this.sensitiveData = sensitiveData;
    }
}

참고: Entity 클래스에서 @Setter는 무분별하게 열어두기보다 필요한 경우에만 명시적으로 메서드를 정의하는 것이 객체의 상태 변경을 제어하는 데 좋습니다. 또한, 양방향 연관관계가 있는 필드는 @ToString에서 제외하여 순환 참조로 인한 StackOverflowError를 방지해야 합니다.

5.3. Service 클래스 (의존성 주입)

import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor // final 필드에 대한 생성자 자동 생성 (생성자 주입)
public class OrderService {
    private final OrderRepository orderRepository; // final로 선언
    private final ProductService productService;   // final로 선언

    public void placeOrder(Long productId, int quantity) {
        // productRepository, productService 사용 로직
        productService.decreaseStock(productId, quantity);
        // ...
        // orderRepository.save(...);
    }
}

6. 마치며

Lombok은 Java 개발에서 반복적인 코드 작성을 획기적으로 줄여주는 강력한 도구입니다. 코드의 가독성을 높이고 개발 생산성을 향상시키는 데 큰 도움을 줍니다. 처음에는 어노테이션 뒤에 숨겨진 코드 때문에 어색할 수 있지만, 익숙해지면 Lombok 없는 Java 개발은 상상하기 어려울 정도입니다.

Lombok의 다양한 어노테이션을 적재적소에 활용하여 더 효율적이고 즐거운 개발을 경험해 보시기 바랍니다!