10. Spring/Lib (Library)

01. Library - Lombok [미완성]

THE HEYDAZE 2020. 7. 10. 17:44
OS Windows 10 Home 64bit 버전 1903 (OS 빌드 18362.836)
Edit Tool IntelliJ IDEA 2019.1.3
FrameWork SpringBoot 2.3.1.RELEASE
Build Tool Gradle

# 의존성
1
2
3
4
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
 
cs

 

# 어노테이션
어노테이션
  @AllArgsConstructor   모든 필드 생성자 생성합니다.
  @NoArgsConstructor   기본생성자 생성합니다.
  @RequiredArgsConstructor
  이 어노테이션은 초기화 되지않은 final 필드나,
 
@NonNull 이 붙은 필드에 대해 생성자를 생성해 줍니다. 
  주로 의존성 주입(Dependency Injection) 편의성을
  위해서 사용되곤 합니다.
  @Builder   빌더 패턴을 사용할 수 있도록 코드를 생성합니다.
  @Getter   get 메소드를 생성합니다
  @Setter   set 메소드를 생성합니다
  @EqualsAndHashCode   해당 객체의 equals()와 hashCode() 메소드를 생성합니다.
  @Data   @Getter + @Setter + @RequiredArgsConstructor + 
  @ToString + @EqualsAndHashCode
  @Generated   ?
  @Cleanup   자동으로 close() 메소드를 호출합니다.
  @CustomLog   ?
  @NonNull   Null 값이 될 수 없다는 것을 명시합니다. 
  NullPointerException에 대한 대비책이 될 수 있습니다.
  (final과 조금 다름 )
  @Singular   ?
  @SneakyThrows   예외 발생 시 Throwable 타입으로 반환합니다.
  @Synchronized   메소드에서 동기화를 설정합니다.
  @ToString   toString() 메소드를 생성합니다.
  @UtilityClass   ?
  @val   ?
  @Value   불변 클래스를 생성할 때 사용합니다.
  (application.properties)
  @var   ?
  @With   ?
  @FieldNameConstants   ?
  @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j,
  @CommonsLog, @JBossLog, @Flogger, @CutomLog
  로그 클래스를 사용할 수 있게 합니다.
  log.info(), log.debug(), log.error(), log.warn()
  @SuperBuilder   ?
  @Delegate   ?
  @Accessors   ?
  @Wither   ?

 

# 사용

 

라이브러리 + 플러그인 + 컴파일 설정을 해야지 사용할 수 있습니다

 

#1. @AllArgsConstructor

ㄴ잘못된 예시

모든 필드(멤버변수) 생성자라서

2개의 필드를 가진 생성자는 생성 할 수 없다.

 

ㄴ올바른 예시

#2. @NoArgsConstructor

ㄴ 잘못된 예시

1개 이상의 매개변수가 있는 생성자가 존재 할 경우

기본 생성자는 존재하지 않는다.

 

위에서 모든 필드 생성자 @AllArgsConstructor 가 존재하기 때문에

기본(Default) 생성자는 사용 할 수 없다.

ㄴ 올바른 예시

 

@AllArgsConstructor 와 @RequiredArgsConstructor 

를 이용한 생성자 생성은 지양하는 편이다.

 

단 한번의 작성만으로 완벽한 코드를 만들 수는 없다.

서비스나 국가정책상에 이유로는 변경되는 것도 있으며,

여러차례 리팩토링(코드 수정)을 거쳐 클린코드(가독성과 효율이 좋은 코드)를 만드는 데

리팩토링을 하다, 생성자의 매개변수를 추가하거나, 지우거나, 수정하는 경우도 있다.

그로인해 서비스적 문제가 발생할 수 있는데

예시로 아래 gif 참고 바람

 

이미지 클릭

웹 서비스를 하던 도중 어느날 확인해보니

cancelPrice(환불금액)이 orderPrice(주문금액) 보다 

코드 작성위치가 위에 위치하는 게 마음에 들지않자 순서를 바꾼 후

미처 생성자까지 바꾸는 것을 까먹고 빌드를 하였더니

오류가 나지않자 개발자는 아무런 문제없이 서버에 반영하고 서비스 진행하였다고 가정하자

 

300원에 주문해서

취소했더니 1000원을 주는 문제가 발생한다.

 

지금 저 코드안에서 객체를 생성하는 코드가 있기 때문에 바로 인지 할 수 있지만,

실제 웹 서비스하면 수백개의 클래스에서 객체생성하는 곳을 찾아야한다.

 

이러한 상황이 발생할 수 있기에 @AllArgsConstructor 와 @RequiredArgsConstructor 는 지양하고

@Builder 패턴을 이용하여 객체를 생성하는 것이 좋다

 

#3. @RequiredArgsConstructor

ㄴ 잘못된 예시

 

올바른 예시

 

ㄴ 접근제한자 private

 

@AllArgsConstructor, @NoArgsConstructor, @RequiredArgsConstructor 모두 접근제한자 설정도 가능하다

@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -> private 생성자

@RequiredArgsConstructor(access = AccessLevel.PROTECTED) -> protected 생성자

 

#4. @Builder

@Builder public Class User {...} 처럼 클래스명 앞에  사용할 수 있으며,

@Builder public User() {...} 처럼 생성자 앞에 사용할 수 있다.

 

[클래스 앞에 @Builder 를 사용한 경우]

ㄴ 올바른 예시 - 클래스 앞

 

[생성자 앞에 @Builder 를 사용한 경우]

파란색 네모는 생성 할 수 없다는 것을 보여주기 위해 작성하였습니다

 

생성자 매개변수 클래스 앞 생성자 앞
기본 생성자 O X
userId O X
password O X
email O X
userId, password O X
userId, email O X
password, email O O
userId, password, email O X

클래스 앞에 사용한 경우는 매개변수를 이용하여 만들 수 있는 모든 생성자 Builder 패턴으로 만들 수 있다.

생성자 앞에 사용한 경우는 해당 생성자만Builder 패턴으로 만들 수 있다.

생성자 앞에 사용한 경우는 결과적으로 같은데 builder 를 사용하는 이유는 무엇인가?

 

나중에 해당 멤버변수가 필요없어져서 지워졌다가 가정한다

그러면 해당 객체를 생성하는 클래스를 찾아가서 생성자 매개변수를 일일이 맞춰줘야 하는 번거러움이 있다.

 

아래 gif 를 참고 바람

 

[생성자를 통해 생성한 경우]

이미지 클릭

 

[@Builder 를 통해 생성한 경우]

이미지 클릭

 

 

@Builder(access = AccessLevel.PRIVATE) 접근제한자 설정

@Builder(builderMethodName = "of") "builder" 메소드명을 재정의 가능

@Builder(buildMethodName = "get") "build" 메소드명을 재정의 가능

 

@Builder(builderClassName = "") 내부 클래스 클래스명 (Builder 클래스명)

ㄴ 올바른 예시

 

여러개의 @Builder 사용 시 유의사항

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import lombok.*;
 
public class User {
 
    private String userId;
 
    private String password;
 
    private String email;
 
    private Long point;
 
    private String hobby;
 
    @Builder(builderMethodName = "userBuilder")
    private User(String userId, String password, String email) {
        this.userId = userId;
        this.password = password;
        this.email = email;
    }
 
    @Builder(builderMethodName = "anonymousBuilder")
    private User(String userId, String password) {
        this.userId = userId;
        this.password = password;
    }
 
    @Builder(builderMethodName = "allBuilder")
    private User(String userId, String password, String email, Long point, String hobby) {
        this.userId = userId;
        this.password = password;
        this.email = email;
        this.point = point;
        this.hobby = hobby;
    }
 
    @Builder(builderMethodName = "updateBuilder")
    private User(Long point) {
        this.point = point;
    }
 
    @Override
    public String toString() {
        return "User{" +
                "userId='" + userId + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                ", point=" + point +
                ", hobby='" + hobby + '\'' +
                '}';
    }
}
 
class init {
    public static void main(String[] args) {
 
        User user = User.userBuilder()
                .userId("root")
                .password("root")
                .email("root@gmail.com")
                .build();
 
        User updateUser = User.updateBuilder()
                .userId("root")
                .password("root")
                .email("root@gmail.com")
                .point(100L)
                .build();
 
        User allUser = User.allBuilder()
                .userId("martin")
                .password("1234")
                .point(100L)
                .hobby("movie")
                .build();
 
        System.out.println(user);
        System.out.println(updateUser);
        System.out.println(allUser);
    }
}
 
cs

이미지 클릭

builderClassName 을 사용하지 않을 경우

내부 클래스가 서로 공유되기때문에 1개의 @Builder만 적용이 된다.

updateBuilder 빌더는 point 매개변수로 줄 수 있는 데 

앞에있는 userBuilder 빌더가 이미 있고 서로 공유하고 있어서 다른 매개변수를 사용해도

빌드에러가 발생하지 않는다. 하지만 userBuilder 빌드만 적용되기 때문에

updateBuilder 의 userId, password, email 만 적용되고 point는 없기때문에 null 로 된다.

 

[요약]

2개 이상생성자@Builder사용할 때에는 builderClassNamebuilderMethodName 를 같이 사용해야 한다

 

@Builder(toBuilder = true)

ㄴ 올바른 예시

기존의 객체를 이용하여 새로운 값으로 수정한 후 새롭게 객체를 만들고자 할 때 사용 (필자생각)

필드가 지금은 적으니 직접 매개변수를 입력하는게 더 빠를 수 있지만

필드가 15~20개만 되도 다 적는 것은 효율적이지 않기 때문에 이용하면 좋을 것 같다

 

[참고용] 값을 바꾸어 새 객체를 생성해야 하는 방법으로는

@With 방법이 더 간단하다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.With;
 
@With
@AllArgsConstructor
@NoArgsConstructor
public class User {
 
    @NonNull
    private String userId;
 
    private String password;
 
    private String email;
 
    @Override
    public String toString() {
        return "User{" +
                "userId='" + userId + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
 
}
 
class init {
    public static void main(String[] args) {
 
        User user1 = new User("root""root""root@gmail.com");
 
        User user2 = user1.withUserId("demo").withPassword("demo");
 
        System.out.println(user1);
        System.out.println(user2);
 
    }
}
 
cs

ㄴ 예시용 그림

 

@Builder(setterPrefix = "?")

모름

 

@Builder.Default

ㄴ @Builder.Default 미사용 예시

 

ㄴ @Builder.Default 사용 예시

 

기타 참고 사이트

 

Builder 기반으로 객체를 안전하게 생성하는 방법 | Popit

해당 코드는 Github 에 공개되어 있습니다. 실무에서 Lombok 사용법 에서 기본적인 Lombk 사용법과 Builder 사용법을 간단하게 정리 한 내용을 먼저 참고하면 좋습니다. JPA를 이용하면 엔티티 객체들을

www.popit.kr

 

 

#5. @Getter / @Setter

ㄴ 올바른 예시

 

ㄴ 올바른 예시와 잘못된 예시

원하는 필드명만 지정해서 사용할 수 있다.

( 위 그림은 email 필드명만 getter setter 메소드 사용이 가능하다)

 

@Getter lazy 옵션 (기본 false)

이미지 클릭

일반 @Getter 어노테이션을 사용한다면,

생성자가 생성될 때 필드의 값이 해당 메소드의 값으로 리턴되어 들어가게 된다

 

하지만

get 메소드를 호출할 때 해당 필드의 값이 리턴되어 들어가게 된다

이미지 클릭

lazy = true 로 옵션을 설정한 경우에는

private 접근제한자만 사용되며, final 로 선언해주어야 한다.

 

#6. @EqualsAndHashCode

ㄴ 예시용 그림

객체안의 내용은 같아도

hashCode 의 주소값이 다르기 때문에 false 가 나온다.

 

이 객체가 서로 같다는 것을 하기 위해

@EqualsAndHashCode 어노테이션을 사용한다

ㄴ 예시용 그림2

 

ㄴ 예시용 그림3

하지만 위 방법은 모든 필드의 값들이 같은 경우에만 true 를 반환하기에

보통은 특정 필드를 정하여 사용한다

  단일 다중
포함 @EqualsAndHashCode(of = "필드 변수명") @EqualsAndHashCode(of = {"필드 변수명1","필드 변수명2"})
제외 @EqualsAndHashCode(exclude = "필드 변수명") @EqualsAndHashCode(exclude = {"필드 변수명1","필드 변수명2"})

 

ㄴ 올바른 예시

 

부모 클래스까지 같은지 판단하려면  callSuper 옵션을 사용하면 된다

 

#5. @Data

@Getter + @Setter + @RequiredArgsConstructor + @ToString + @EqualsAndHashCode

위 어노테이션이 포함된 어노테이션이기에 한번에 작성하는 편리한 점이 있지만

개발자들은 지양하는 어노테이션이다.

 

1. 양방향 순환 참조 ToString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import lombok.Data;
 
import java.util.ArrayList;
import java.util.List;
 
@Data
public class Book {
 
    private String title;
 
    private String author;
 
    private BookStore bookStore;
 
}
 
@Data
class BookStore {
 
    private String name;
 
    private List<Book> books = new ArrayList<>();
 
    public void add(Book book) {
        books.add(book);
        book.setBookStore(this);
    }
 
}
 
class main {
    public static void main(String[] args) {
 
        Book book1 = new Book();
        book1.setTitle("어린왕자");
        book1.setAuthor("앙투안 드 생텍쥐페리");
 
        Book book2 = new Book();
        book2.setTitle("나무");
        book2.setAuthor("베르베르베르나르");
 
        BookStore bookStore = new BookStore();
        bookStore.setName("교보문고");
        bookStore.add(book1);
        bookStore.add(book2);
 
        System.out.println(bookStore);
    }
}
 
cs

이미지 클릭

 

원인

 

해결방법

이미지 클릭

 

2. 무분별한 EqualsAndHashCode

ㄴ #4. @EqualsAndHashCode 참고

 

3. 불필요한 Setter 메소드 

ㄴ 예시용 그림

password(비밀번호) 와 email(이메일)은 변경할 수 있다고는 하다만,

userId 처럼 한 번 생성하면 바뀔 일이 없는 필드같은 경우에는

set 메소드가 불필요하다

 

지금 처럼 필드명이 3개밖에 없는 클래스에서는 상관없지만

필드가 수십개가 있는 경우 set 메소드를 의미 없이 작성하는 것과 다름없다


옵션기능으로는 

static 메소드를 이용한 생성자 생성 옵션이 있다

 

#6. @NonNull

기본 생성자 (이미지 클릭)

기본 생성자로 된 경우에는 null 값이 된다

 

매개변수 생성자 (이미지 클릭)

매개변수로 null 값을 준다면 에러가 발생한다

 

Set 메소드 (이미지 클릭)

set 메소드 역시 null 값을 주면 에러가 발생한다

 

@NonNull 보단 final 로 선언해주는 것이 좋다고 본다 (필자생각)

 

#7. @ToString

toString 메소드를 생성해준다.

  단일 다중
포함 @ToString(of = "필드 변수명") @ToString(of = {"필드 변수명1","필드 변수명2"})
제외 @ToString(exclude = "필드 변수명") @ToString(exclude = {"필드 변수명1","필드 변수명2"})

 

 

 

참고바람

 

lombok을 잘 써보자! (1) - 머루의개발블로그

java 개발자에 있어 lombok은 아주 좋은 라이브러리이다. 어노테이션 하나로 자동으로 바이트코드를 만들어주니 더 할 것이 없는 라이브러리이다. 다른 언어들은 언어 자체에서 지원해주긴 하지만

wonwoo.ml

@Generated

@Cleanup

@CustomLog

@Singular

@SneakyThrows

@Synchronized

@val

@Value

@var

@With