[Spring] lombok 어노테이션 정리와 주의사항
Lombok
Lombok이란 자바 컴파일 시점에서 특정 어노테이션으로 해당 코드를 추가해주는 라이브러리이다.
코드의 간결함과 가독성 및 유지 보수에 많은 도움이 된다. 하지만 그만큼 잘못 사용할 수도 있어 간단한 어노테이션 설명과 좋은 사용 방법에 대해 정리했다.
Lombok에는 @Getter, @Setter외에 다양한 어노테이션들이 있다.
@NoArgsConstructor
매개변수가 없는 기본 생성자 구현
@NoArgsConstructor(AccessLevel.PROTECTED) 이렇게 사용할 경우 모든 필드에 대한 값이 들어가야함을 보장하고 싶을 때, 기본 생성자 호출을 막음으로서 이후에 발생할 수 있는 문제를 사전 차단할 수 있다.
@NoArgsConstructor
public Class Sample {
private String attr1;
public Sample(String attr1) {
this.attr1 = attr1;
}
}
실제 자바 코드에는
public Sample() {
}
이 추가되어 들어간다.
주의점
필드들이 final로 생성되어 있는 경우에는 필드를 초기화할 수 없기 때문에 생성자를 만들 수 없어 Error가 발생한다.
이 때, @NoArgsConstructor(force=true) 옵션을 줘서 final 필드를 0, false, null 등으로 강제 초기화시켜 생성자를 만들 수 있다.
@AllArgsConstructor
클래스에 존재하는 모든 필드에 대한 생성자를 자동으로 생성
@AllArgsConstructor
public Class Sample {
private String attr1;
private String attr2;
@NonNull
private String attr3
}
실제 자바 코드에는
public Sample(String attr1, String attr2, @NonNull String attr3) {
if(attr3 == null) {
throw new NullPointerException("attr3 is marked non-null but is null");
} else {
this.attr1 = attr1;
this.attr2 = attr2;
this.attr3 = attr3;
}
}
이 추가된다.
@RequiredArgsConstructor
final, @NonNull인 필드들에 대한 생성자 구현
@RequiredArgsConstructor
public Class Sample {
private String attr1;
private final String attr2;
@NonNull
private String attr3
}
실제 자바 코드에는
public Sample(String attr2, @NonNull String attr3) {
if(attr3 == null) {
throw new NullPointerException("attr3 is marked non-null but is null");
} else {
this.attr2 = attr2;
this.attr3 = attr3;
}
}
이 추가된다.
생성자 어노테이션 주의점: static 필드는 스킵된다. AccessLevel을 명시해주지 않으면 public으로 설정된다.
@EqualsAndHashCode
equal, hashCode를 자동 생성해준다.
equals : 두 객체의 문자열이 같은지 동등성 비교
hashCode: 두 객체가 같은 객체인지 동일성 비교
@Builder
해당 클래스에 자동으로 빌더를 추가해준다.
다수의 필드를 가지는 복잡한 클래스일 경우, 생성자 대신 빌더를 사용하는 경우가 많다.
@Builder
public class User {
private Long id;
private String username;
private String password;
@Singular
private List<Integer> scores;
}
User user = User.builder()
.id(1L)
.username("dale")
.password("1234")
.score(70)
.score(80)
.build();
// User(id=1, username=dale, password=1234, scores=[70, 80])
@ToString
객체의 내용을 문자열로 나타내는 toString 메소드 코드를 생성해준다.
public class Sample {
private String id;
private String name;
private Integer age;
@Override
public String toString() {
return "Sample(id=" + this.id + ", name=" + this.name + ")");
}
}
주의! ToString 사용 시 멤버 변수 중 객체 타입이 있고, 순환 참조가 있다면 무한 루프가 발생한다.
classA의 멤버 변수로 classB를 가지고 있고, classB의 멤버 변수로 classA를 갖고 있다면 = 양방향 연관 관계라면,
ToString의 무한 반복이 발생한다.
이럴 때는, exclude 키워드를 사용하여 명시적으로 해당 필드를 제외해줘야 한다.
@ToString(exclude="classA")
@Data
@ToString, @EqualsAndHashCode, @Getter, @Setter, @RequiredArgsConstructor
다음 어노테이션을 한번에 설정해준다.
@Value
@Data와 비슷하지만 불변(immutable 객체)으로 만들어준다.
생성 시 생성자에 따라 값이 설정되고, 그 이후는 값 변경이 불가능. 즉, 한번 생성하면 변경할 수 없는 불변 객체를 만들기 위한 클래스 선언할 때는 @Data대신 @Value 사용
@Value를 사용하면 @Getter, @ToString, @EqualsAndHashCode, @AllArgsConstructor이 부여된다.
클래스를 final로 설정하고, 모든 필드를 private final로 설정. Setter를 생성하지 않는다.
@NonNull
자동으로 null 체크하고 null인 경우 NullPointException을 발생시킨다.
💡 Lombok 주의사항
@Data는 지양하자
@Data는 @ToString, @EqualsAndHashCode, @Getter, @Setter, @RequiredArgsConstructor을 한번에 사용하는 강력한 어노테이션이다. 강력한 어노테이션인만큼 부작용도 많다.
무분별한 Setter 남용
@Data를 사용하면 자동으로 Setter를 지원하게 된다. ➡
간단히 말하면, Setter는 그 의도가 분명하지 않고 객체를 언제든지 변경할 수 있는 상태가 되어서 객체의 안전성이 보장받기 힘들다.
ToString으로 양방향 연관관계 시 무한 순환 참조 문제
예를 들어, Member 객체와 Coupon 객체가 양방향 연관관계일 경우, ToString을 호출하면 무한 순환 참조가 발생한다.
JPA를 사용하면 객체를 JSON으로 직렬화 하는 과정에서 발생하는 문제와 동일한 이유.
해결 방법으로는,
@ToString(exclude = "coupons")
public class Member {...}
좋은 Lombok 사용법
Lombok 사용법 예시
@Entity
@Table(name = "member")
@ToString(exclude = "coupons")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(of = {"id", "email"})
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "email", nullable = false)
private String email;
@Column(name = "name", nullable = false)
private String name;
@CreationTimestamp
@Column(name = "create_at", nullable = false, updatable = false)
private LocalDateTime createAt;
@UpdateTimestamp
@Column(name = "update_at", nullable = false)
private LocalDateTime updateAt;
@OneToMany
@JoinColumn(name = "coupon_id")
private List<Coupon> coupons = new ArrayList<>();
@Builder
public Member(String email, String name) {
this.email = email;
this.name = name;
}
}
@ NoArgsConstructor 접근 권한을 최소화하자
JPA에서는 프록시를 생성을 위해서 기본 생성자를 반드시 하나를 생성해야 한다. 이때 접근 권한이 protected 이면 된다. 외부에서 생성을 열어둘 필요가 없다.
@NoArgsConstructor(access = AccessLevel.PUBLIC)
을 하게 되면 id 값은 null이 되게 된다. 이처럼 기본 생성자를 아무 이유 없이 열어두는 것은 객체 생성 시 안전성을 낮출 수 있다.
@NoArgsConstructor(access = AccessLevel.PROTECTED)를 사용하면 객체 생성 시 안전성을 어느정도 보장받을 수 있다. 다만, 외부에서 해당 생성자를 접근할 수 없으므로 아래 생성자를 통해서 객체를 생성해야한다.
@Builder
public Product(String name) {
this.id = UUID.randomUUID().toString();
this.name = name;
}
객체에 대한 생성자를 하나 두고 그것을 @Builder를 통해서 사용하는 것이 좋다.
Builder 사용시 클래스 매개변수를 최소화하자
@Builder
public class Member {...}
클래스 위에 @Builder를 사용시 @AllArgsConstructor 어노테이션을 붙인 효과를 발생시켜 모든 멤버 필드에 대해 매개변수를 받는 기본 생성자를 만들게 된다.
이럴 경우, 객체 생성지 받지 않아야 할 데이터들이 Builder를 사용하게 되면 다 넘겨받게 된다.
public class Member {
@Builder
public Member(String email, String name) {
this.email = email;
this.name = name;
}
}
그렇기 때문에, 이렇게 받아야 하는 생성자를 필요 조건에 따라 지정하고 그 위에 @Builder를 붙이는게 좋다.
위 코드로 하면 매개변수 name, email만 넘겨 받을 수 있게 된다.
✨ 요약
- @NoArgsConstructor(access = AccessLevel.PROTECTED) JPA에서는 프록시 객체가 필요하므로 기본 생성자 하나가 반드시 있어야 한다. 이때 접근제한자는 protected로 설정한다.
- @Data는 사용하지 말자.
- @Setter 지양하자. 객체는 변경 포인트를 남발하지 말자.
- @ToString은 무한 참조가 생길 수 있다. @ToString(of = {" "}) 권장
- 클래스 상단의 @Builder (X). 생성자 위에 @Builder (O)
출처
https://haenny.tistory.com/387
https://github.com/cheese10yun/blog-sample/tree/master/lombok