어리바리 신입 개발자의 얼렁뚱땅 개발 기록 ✨
23.08.26 / [Java / SpringBoot / intelliJ] Lombok 라이브러리 본문
[ Lombok - 롬복]
개발을 할 때 기계적으로 작성하는 코드들이 많다. 이런 코드들을 자동화해서 코드를 간략하게 만드는 Java 라이브러리다.
DTO, Model, Entity의 Getter, Setter, toString, 의존성 주입 등을 대신하기 위해서 사용한다.
롬복은 코드를 작성할 때는 어노테이션을 사용해서 코드를 줄이고, 컴파일하는 과정에서 생략된 코드를 생성해 주는 방식으로 동작하는 라이브러리이기 때문에 코드의 가독성과 생선성은 높지만, 실제로 눈에 보이는 코드는 롬복과 관련된 어노테이션 뿐이기 때문에 코드에 대한 직관성이 떨어질 수 있다.
[ 설치 방법 ]
1. File - Settings - Plugins - Lombok 검색해서 Install
(IntelliJ 2020.03 이후 버전에는 자동으로 Lombok이 설치되어 있다.)
2. 롬복 Dependency 설정 (소스 복사하러가기 링크 클릭)
최신 버전 클릭 후 maven 혹은 gradle 소스 복사 - pom.xml에서 <dependencies> </dependencies> 사이에 넣어준다.
[ 어노테이션 ]
[ 롬복을 사용하지 않은 DTO ]
public class Point { private String userId; private int currentHoldingPoint; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public int getCurrentHoldingPoint() { return currentHoldingPoint; } public void setCurrentHoldingPoint(int currentHoldingPoint) { this.currentHoldingPoint = currentHoldingPoint; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Point [userId="); builder.append(userId); builder.append(", currentHoldingPoint="); builder.append(currentHoldingPoint); builder.append("]"); return builder.toString(); } }
@Getter : 필드의 getter (필드의 이름이 userId라면 getUserid() 메서드 대체)
@Setter : 필드의 의 setter (필드의 이름이 userId라면 setUserid() 메서드 대체)
@ToString : 필드의 toString / @ToString.Exclude : 출력을 원하지 않는 필드가 있을 때
단, Point라는 클래스에 @ToString을 붙였을 때
만약 Point 클래스 내의 필드에 클래스 User 타입이 존재하고,
클래스 User 내의 필드에 클래스 Point 타입의 필드가 존재할 경우 무한루프가 발생한다.
= > 해당 필드에 @ToString.Exclude를 붙여줘서 해당 필드를 제외시켜줘야 한다.
(1 대 N 양방향 맵핑 케이스)
@NonNull : 변수에 반드시 값이 존재해야하게 한다.
Setter에 null 값일 들어올 경우 NullpointerException 발생시킨다.
단, 불필요하게 branch coverage를 증가시키기 때문에 사용하지 않는 것이 좋다.
import lombok.Getter; import lombok.Setter; import lombok.ToString; @Getter // Getter, Setter를 추가해주고 싶은 필드가 따로 있으면 필드 위에 붙여줘도 된다. @Setter @ToString public class Point { private String userId; @ToString.Exclude private int currentHoldingPoint; }
@Data : @Getter / @Setter / @TtoString / @EqualsAndHashCode / @RequiredArgsConstructor 한 번에 사용
@Value : @Data와 비슷하지만 필드와 클래스를 private 및 final로 선언하고, setter 메서드를 생성하지 않는다.
import lombok.Data @Data public class Point { private String userId; private int currentHoldingPoint; }
@NoArgsConstructor : 매개변수가 없는 기본 생성자 생성 (매개변수 없기 때문에 필드 순서 상관 없음)
기본 생성자 생성 방지(PROTECTED)
import lombok.NoArgsConstructor; @NoArgsConstructor public class UserPointService { // 클래스에 존재하는 필드 private final UserPointMapper userPointMapper; // NoArgsConstructor를 통해 생성된 생성자 아무것도 붙이지 않으면 기본적으로 public으로 생성 // @NoArgsConstructor(access = AccessLevel.PROTECTED)를 통해 접근 제한자 설정 가능 // 실제로 눈에 보이지 않는 코드(어노테이션 사용함으로써 생략됐다.) // public UserPointService() {} }
@AllArgsConstructor : 모든 필드를 포함한 생성자 생성 (단, 필드의 순서를 따라 매개변수의 순서가 정해진다.)
@RequiredArgsConstructor : @AllArgsConstructor와 비슷하지만
필드에서 final이나 @NonNull이 붙어있는 필드만 포함해서 생성자를 생성한다.
(단, 필드의 순서를 따라 매개변수의 순서가 정해진다.)
import lombok.AllArgsConstructor; @AllArgsConstructor public class UserPointService { // 클래스에 존재하는 필드 private final UserPointMapper userPointMapper; // AllArgsConstructor 통해 생성된 생성자 아무것도 붙이지 않으면 기본적으로 public으로 생성 // @AllArgsConstructor(access = AccessLevel.PROTECTED)를 통해 접근 제한자 설정 가능 // 실제로 눈에 보이지 않는 코드(어노테이션 사용함으로써 생략됐다.) // 의존성 주입 // public UserPointService(UserPointMapper userPointMapper) { // this.userPointMapper = userPointMapper; // } }
AllArgsConstructor와 RequiredArgsConstructor의 문제점 :
필드에 잘 못된 값이 들어왔을 때 에러를 띄우지 않을 수 있다.
에러가 없이 잘 동작하는 것 같지만 실제로는 잘 못 된 동작을 하고 있는 경우가 있다는 뜻// 클래스에 존재하는 필드 private final UserPointMapper userPointMapper; private final UserDepositMapper userDepositMapper; // 필드의 순서에 따라 매개변수의 순서가 정해진다. // public UserService(UserPointMapper userPointMapper, UserDepositMapper userDepositMapper) { // this.userPointMapper = userPointMapper; // this.userDepositMapper = userDepositMapper; // } UserPointService pointDeposit = new UserPointService("id001", "id002"); => id001 사용자의 포인트 정보와 id002 사용자의 보증금 정보가 담긴다. ------------------------------------------------------------------------------------------------ // 클래스에 존재하는 필드 private final UserDepositMapper userDepositMapper; private final UserPointMapper userPointMapper; // 필드의 순서에 따라 매개변수의 순서가 정해진다. - 필드의 순서가 바뀌어 매개변수 순서도 바뀜 // public UserService(UserDepositMapper userDepositMapper, UserPointMapper userPointMapper) { // this.userDepositMapper = userDepositMapper; // this.userPointMapper = userPointMapper; // } UserPointService pointDeposit = new UserPointService("id001", "id002"); => 원래 원하는 결과는 id001 사용자의 포인트 정보와 id002 사용자의 보증금 정보 였지만, 매개변수의 순서가 바뀌어버려서 id002 사용자의 포인트 정보와 id001 사용자의 보증금 정보가 담긴다.
@Builder : 빌더 패턴을 사용할 수 있도록 빌더API를 자동 생성
이미 생성자가 존재하는 경우 따로 생성자를 생성하지 않지만
생성자가 없을 경우 모든 필드를 매개변수로 받는 기본 생성자를 생성한다.
import lombok.Builder; public class User { private String name; private int age; @Builder User(String name, int age) { this.name = name; this.age = age; } } ----------------------------------------------------------------------------------------- public class User { private String name; private int age; @Builder User(String name, int age) { this.name = name; this.age = age; } // 실제로 눈에 보이지 않는지만 자동 생성되는 부분들 /* public static UserBuilder builder() { return new UserBuilder(); } public static class UserBuilder { private String name; private int age; UserBuilder() {} public UserBuilder name(String name) { this.name = name; return this; } public UserBuilder age(int age) { this.age = age; return this; } public User build() { return new User(name, age); } } */ } ----------------------------------------------------------------------------------------- 빌더를 사용할 수 있게 된다. User user = User.builder() .name("John") .age(30) .build();
[ @NoArgsConstructor / @AllArgsConstructor / @Build의 관계 ]
@NoArgsConstructor 는 보통 protected로 설정한다.
protected는 상속받은 자식 클래스에서만 생성자를 사용할 수 있게 만든다.
즉, 다른 외부 클래스에서는 이 기본 생성자를 변경(setter)할 수 없게 된다.
외부 클래스 에서 변경하기 위해서 따로 생성자를 생성해줘야 한다.
따로 생성자를 생성 후 @Build를 함께 사용해줄 수 있다.
혹은, @AllArgsConstructor와 @Build를 함께 사용해줄 수 있다.import lombok.NoArgsConstructor; import lombok.Builder; @NoArgsConstructor(access = AccessLevel.PROTECTED) public class User { private String name; private int age; // 실제로 눈에 보이지 않는 코드(기본 생성자 protected로 생성) // protected User() {} @Builder public User(String name, int age) { this.name = name; this.age = age; } } ------------------------------------------------------------------------------------------ import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; import lombok.Builder; @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Builder public class User { private String name; private int age; // 실제로 눈에 보이지 않는 코드(기본 생성자 protected로 생성) // protected User() {} /* public User(String name, int age) { this.name = name; this.age = age; } */ }
@Builder는 생성자가 이미 존재하면 생성자를 생성하지 않고,
생성자가 존재하지 않으면 존재하는 모든 필드를 매개변수로 사용해 생성자를 생성한다.
만약 NoArgsConstructor를 protected로 생성한 상태에서 public 생성자를 따로 생성해주지 않고
@Builder를 사용할 경우 기본 생성자가 이미 존재하기 때문에 생성자를 따로 생성해주지 않는다.
하지만, 기본 생성자가 protected이기 때문에 문제가 발생한다.import lombok.NoArgsConstructor; import lombok.Builder; @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) public class User { private String name; private int age; // 실제로 눈에 보이지 않는 코드(기본 생성자 protected로 생성) // protected User() {} /* public static UserBuilder builder() { return new UserBuilder(); } public static class UserBuilder { private String name; private int age; UserBuilder() {} public UserBuilder name(String name) { this.name = name; return this; } public UserBuilder age(int age) { this.age = age; return this; } // 실제로 존재하는 생성자는 protected User이기 때문에 불일치 - 문제 발생 public User build() { return new User(name, age); } } */ }
@Slf4j : 로그 남기기 / System.out 보다 성능이 좋다.
// 롬복을 사용하지 안았을 경우 import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class UserService { // 둘 중 하나 선택해서 사용한다. private final Logger logger = LoggerFactory.getLogger(UserService.class); private final Logger logger = LoggerFactory.getLogger(getClass()); public void addUser(String name) { logger.info("username: {}", name); } }
// 롬복을 사용했을 경우 import lombok.extern.slf4j.Slf4j; @Slf4j public class UserService { public void addUser(String name) { log.info("username: {}", name); } }
하지만, 보통 @Getter, @Setter, @ToString, @Builder, @Slf4j 정도만 사용하는 것을 권장한다.
( @Data로 한 번에 작성 가능하지만 필요한 어노테이션만 사용하는 것을 권장 )