CCEO 2023. 8. 17. 14:24
반응형

Lombok은 자바의 라이브러리로 반복되는 메소드들(getter, setter 등등)을 Annotation을 사용해서 자동으로 작성해주는 기능을 해줍니다.

Lombok을 이용해서 작성한 코드는 컴파일 과정에서 Annotation을 이용해서 코드를 생성하고 .class에 자동 컴파일 됩니다.

그러면 이제 Lombok의 주요 기능에 대해서 알아보겠습니다.

 


Lombok이 없는 기존의 코드 작성

예를 들어 게임의 새로운 우주선 유닛을 하나 생성한다고 가정해보겠습니다.

기존의 Lombok을 쓰지 않을 때의 코드는 아래와 같은 형식이 될 것입니다.

 

public class SpaceShip extends Units{
    private String unitName;	// 유닛 이름
    private String unitCode;	// 유닛 코드
    private String attakDamage;	// 공격력
    private String defense;	// 방어력
    private String speed;	//이동속도

    public String getUnitName() {
        return unitName;
    }

    public void setUnitName(String unitName) {
        this.unitName = unitName;
    }

    public String getUnitCode() {
        return unitCode;
    }

    public void setUnitCode(String unitCode) {
        this.unitCode = unitCode;
    }

    public String getAttakDamage() {
        return attakDamage;
    }

    public void setAttakDamage(String attakDamage) {
        this.attakDamage = attakDamage;
    }

    public String getDefense() {
        return defense;
    }

    public void setDefense(String defense) {
        this.defense = defense;
    }

    public String getSpeed() {
        return speed;
    }

    public void setSpeed(String speed) {
        this.speed = speed;
    }
}

그냥 보기만해도 getter, setter의 반복으로만 코드가 너무 길어졌다는걸 느낄 수 있습니다.

 

하지만 Lombok을 사용하여 어노테이션을 통해 반복되는 메소드들을 자동으로 완성시킬 수 있다면 코드의 양을 상당히 줄일 수 있습니다.

@Getter
@Setter
public class SpaceShip extends Units{
    private String unitName;	// 유닛 이름
    private String unitCode;	// 유닛 코드
    private String attakDamage;	// 공격력
    private String defense;	// 방어력
    private String speed;	//이동속도
}

@Getter, @Setter 이 2개의 어노테이션으로 코드의 양은 확연하게 줄었습니다.

그만큼 가독성도 늘어날 수 있겠죠?

이렇게 Lombok을 사용하면 

  • 어노테이션 기반의 코드 자동 생성을 통한 생산성 향상
  • 반복되는 코드를 줄임으로서 가독성 및 유지보수성 향상
  • Getter, Setter 외에 빌더 패턴이나 로그 생성 등 다양한 방면으로 활용 가능

등의 장점을 가지게 됩니다.

 

이제 각 어노테이션의 기능에 대해서 조금 더 자세히 알아보겠습니다.


자주 사용하는 Lombok 어노테이션

1) @Getter, @Setter

가장 자주 사용하는 어노테이션입니다.

@Getter와 @Setter를 클래스 이름 위에 달아주면 모든 변수들에게 적용이 되고, 변수 이름 위에 달아주면 해당 변수에만 적용이 됩니다.

 

예제를 통해 전체 변수에 Getter를 적용시키고, 유닛 이름과 유닛 코드에만 Setter를 적용시켜보겠습니다. 

@Getter
public class SpaceShip extends Units{

    @Setter
    private String unitName;	// 유닛 이름
    @Setter
    private String unitCode;	// 유닛 코드
    private String attakDamage;	// 공격력
    private String defense;	// 방어력
    private String speed;	//이동속도
}

2) @AllArgsConstructor

@AllArgsConstructor는 모든 변수를 사용하는 생성자를 만들어줍니다.

@AllArgsConstructor
public class SpaceShip extends Units{
    
    private String unitName;	// 유닛 이름
    private String unitCode;	// 유닛 코드
    private String attakDamage;	// 공격력
    private String defense;	// 방어력
    private String speed;	//이동속도

    /* @AllArgsConstructor를 통해 자동 생성되는 생성자
    public spaceShip(String unitName, String unitCode, String attakDamage, String defense, String speed) {
        this.unitName = unitName;
        this.unitCode = unitCode;
        this.attakDamage = attakDamage;
        this.defense = defense;
        this.speed = speed;
    }
     */
}

3) @NoArgsConstructor

@NoArgsConstructor는 아무 변수가 없는 기본 생성자를 만들어줍니다.

@NoArgsConstructor
public class SpaceShip extends Units{

    private String unitName;	// 유닛 이름
    private String unitCode;	// 유닛 코드
    private String attakDamage;	// 공격력
    private String defense;	// 방어력
    private String speed;	//이동속도

    /* @NoArgsConstructor를 통해 자동 생성되는 기본 생성자
    public spaceShip() {

    }
     */
}

4) @RequiredArgsConstructor

@RequiredArgsConstructor는 특정 변수만을 활용하는 생성자를 자동생성시켜줍니다.

생성자의 인자로 사용할 변수에 @NonNull 어노테이션을 붙여서 해당 변수를 생성자의 인자로 추가할 수 있습니다.

혹은 해당 변수를 final로 선언해도 의존성을 주입 받을 수 있습니다.

@RequiredArgsConstructor
public class SpaceShip extends Units{

    @NonNull
    private String unitName;	// 유닛 이름
    @NonNull
    private String unitCode;	// 유닛 코드
    private String attakDamage;	// 공격력
    private String defense;	// 방어력
    private final String speed;	//이동속도

    /* @RequiredArgsConstructor를 통해 자동 생성되는 기본 생성자
    public spaceShip(@NonNull String unitName, @NonNull String unitCode, String speed) {
        this.unitName = unitName;
        this.unitCode = unitCode;
        this.speed = speed;
    }
     */
}

5) @EqualsAndHashCode

@EqualsAndHashCode을 사용하면 클래스에 대한 equals 함수와 hashCode 함수를 자동으로 생성해줍니다.

서로 다른 두 객체에서 특정 변수의 이름이 똑같은 경우 같은 객체로 인식하게 하고 싶다면 아래와 같이 하면 됩니다.

@RequiredArgsConstructor
@EqualsAndHashCode(of={"unitName", "unitCode"}, callSuper = false)
public class SpaceShip extends Units{
    @NonNull
    private String unitName;    // 유닛 이름
    @NonNull
    private String unitCode;	// 유닛 코드
    private String attakDamage;	// 공격력
    private String defense;	// 방어력
    private String speed;	//이동속도
}

위와 같이하면 unitName과 unitCode가 같으면 같은 객체라고 인식하게 합니다.

또한 상위 클래스의 경우에는 적용시키지 않기 위해 callSuper = false를 함께 사용합니다.

 

결과는 아래와 같습니다.

public class unitController{
    @GetMapping("/getTest")
    public String getTest(){
        SpaceShip spaceShip1 = new SpaceShip("마린","1234");
        SpaceShip spaceShip2 = new SpaceShip("마린","1234");
        SpaceShip spaceShip3 = new SpaceShip("마린","4321");

        // unitName과 unitCode가 같으므로 True
        System.out.println(spaceShip1.equals(spaceShip2));

        // unitName은 같지만 unitCode가 다르므로 False
        System.out.println(spaceShip1.equals(spaceShip3));
    }
}

당연한거 아니냐고 생각하시는 분들도 있겠지만 이러한 비교를 위해서는 사실 equals()와 hashCode()를 오버라이딩하여 별도로 구현을 해주어야합니다.

별도 구현을 하지 않을 수 있도록 @EqualAndHashCode이 대신 equals() 메서드와 hashCode() 메서드를 생성해줍니다.

이거와 관련해서는 조금 더 정리하는 글을 올려보겠습니다.


6) @ToString

@ToString 어노테이션을 달아주면 클래스의 변수들을 출력해주는 ToString 메서드를 자동으로 완성해줍니다.

출력을 원하지 않는 변수는 위에 @ToString.Exclude 어노테이션을 달아주면 됩니다.

또한 상위 클래스 변수들에 대해서도 출력을 원하면 @ToString(callSuper = true)를 달아주면 됩니다.

@ToString
public class SpaceShip extends Units{
    @ToString.Exclude
    private String unitName;    // 유닛 이름
    private String unitCode;	// 유닛 코드
    private String attakDamage;	// 공격력
    private String defense;	// 방어력
    private String speed;	//이동속도
}

위에 코드는 unitName에 대해서만 출력을 하지 않도록 설정한 예시입니다.


7) @Data

@Data 어노테이션을 달아주면 @ToString, @EqualsAndHashCode, @Getter, @Setter, @RequiredArgsConstructor를 모두 달아주는 것과 같은 영향을 줍니다.

실무에서는 너무 무겁고 객체의 안정성을 해치기 때문에 활용을 지양하라고 하네요..


8)  @Builder

@Builder 어노테이션을 활용하면 해당 클래스의 객체 생성에 Builder 패턴을 적용시켜줍니다.

모든 변수에 대해서 적용하려면 클래스 위에 @Builder를 달아주면 되고,

특정 변수에만 적용하려면 생성자를 작성한 후에 그 위에 @Builder을 달아주면 됩니다.

// 전체 변수에 Builder 적용
@Builder
public class SpaceShip extends Units{
    private String unitName;    // 유닛 이름
    private String unitCode;	// 유닛 코드
    private String attakDamage;	// 공격력
    private String defense;	// 방어력
    private String speed;	//이동속도
}
// 특정 변수에만 Builder 적용 (unitName과 unitCode에만 적용)
public class SpaceShip extends Units{
    private String unitName;    // 유닛 이름
    private String unitCode;	// 유닛 코드
    private String attakDamage;	// 공격력
    private String defense;	// 방어력
    private String speed;	//이동속도
    
    @Builder
    public SpaceShip(String unitName, String unitCode){
        this.unitName = unitName;
        this.unitCode = unitCode;
    }
}

실제 사용한다면 아래와 같이 사용하면 됩니다.

public class unitController{
    @GetMapping("/test")
    private SpaceShip init(){
        SpaceShip spaceShip = SpaceShip.builder()
                .unitName("마린")
                .unitCode("1234")
                .build();
        
        return spaceShip;
    }
}

9) @Delegate

@Delegate 어노테이션은 한 객체의 메소드를 다른 객체에서도 사용할 수 있게 해줍니다.

 

지금 만들고 있는 우주선 유닛이 처치한 적들에 대한 기록을 남긴다고 가정해보겠습니다.

(DB가 아닌 객체 안에다가 직접 남기는 걸로 가정)

// 적에 대한 정보를 갖는 객체
@AllArgsConstructor
@Getter
public class Enemy extends Units{
    private String enemyName;
    private String enemyCode;
}
// @Delegate를 달아서 Enemy 리스트를 작성
@Getter
@NoArgsConstructor
public class SpaceShip extends Units{
    private String unitName;    // 유닛 이름
    private String unitCode;	// 유닛 코드
    private String attakDamage;	// 공격력
    private String defense;	// 방어력
    private String speed;	//이동속도
    @Delegate
    private List<Enemy> enemyList;	// 죽인 적 리스트

}

위와 같이 SpaceShip 객체가 List 타입을 가지고 있을 때, List 타입의 add 메서드를 위임 받아서 아래와 같이 사용할 수 있습니다.

public class unitController{
    @PostMapping("/test")
    private SpaceShip test(@RequestBody SpaceShip spaceShip){
        Enemy enemy = new Enemy("UFO","444");
        
        // 기존에는 List를 갖고와서 거기다가 add 메서드를 사용
        spaceShip.getEnemyList().add(enemy);
        
        // @Delegate로 위임받아서 사용
        spaceShip.add(enemy);
        
        return spaceShip;
    }
}

10) @Log 관련 어노테이션

@Log4j2와 같은 어노테이션을 사용하면 해당 클래스의 로그 클래스를 자동 완성해줍니다.

@RequiredArgsConstructor
@RestController
@Slf4j
public class AdminController {
    @GetMapping("/DashBoard")
    public String getDashBoard(){
            String tmp = "관리자 대시보드 입니다.";
            log.info(tmp);
            return tmp;
    }
}

관련 예시

@RequiredArgsConstructor
@Controller
public class Controller {
    private final Service service;
}

보통 Controller는 Servcie를 가지고 있는데, @RequiredArgsConstructor를 사용하고 final 키워드를 붙이면, Controller가 스프링 컨테이너에 Bean으로 등록될 때 Service를 주입시켜줍니다.


참고링크1

참고링크2

반응형