요즘 Spring으로 개발을 할 때는, 대부분 Lombok 라이브러리에서 제공해주는 Annotation을 사용한다.
Lombok은 Java의 Annotation을 이용하여 중복되는 코드를 생성해주는 라이브러리이다.
예시로, @Getter
, @Setter
와 같은 Annotation을 사용하면 클래스의 필드를 위한 Getter, Setter를 자동으로 만들어준다.
예시를 보자.
public class Test{
private Integer number;
private String str;
public Intger getNumber(){
return this.number;
}
public String getStr(){
return this.str;
}
public void setNumber(Integer number){
this.number = number;
}
public void setStr(String str){
this.str = str;
}
}
@Getter
@Setter
public class Test{
private Integer number;
private String str;
}
두 개의 차이를 보면, Getter, Setter를 만들지 않아도 Lombok이 코드를 Annotation 기반으로 생성해주기 때문에 코드양에서부터 차이를 보인다.
또한, 중복되게 작성해야하는 코드를 줄일 수 있기 때문에 가독성에도 도움을 준다.
이러한 여러 장점때문에 사용하는 Lombok에서 이번 포스트에서는 @Builder
Annotation에 대해 알아보려고 한다.
빌더 패턴은 객체에 생성을 위한 방법 중 하나로, 객체 생성시 여러 필드가 존재할 때 그것의 순서에 의해 생기는 문제나 명시적이지 못한 생성자 여러개에 의해 발생하는 문제를 해결하기 위해 나온 패턴으로 아래와 같은 방식으로 사용한다.
public class Test{
private Integer number;
private String str;
public static class Builder{
private Integer number;
private String str;
public Builder number(Integer number){
this.number = number;
return number;
}
public Builder str(String str){
this.str = str;
return this;
}
public Test build(){
return new Test(number, str);
}
}
}
public class TestMain{
@Test
void test1(){
Test test = Test.builder()
.number(1)
.str("test")
.build();
System.out.println("output : " + test.getNumber() + " " + test.getStr());
Test test2 = Test.builder()
.str("test2")
.number(2)
.build();
System.out.println("output : " + test2.getNumber() + " " + test2.getStr());
}
}
output : 1 test
output : 2 test2
위와 같이 builder 패턴을 이용하게 되면, 생성자의 필드 순서를 알 필요 없이 필드명을 통해서 새로운 객체를 생성할 수 있고 더욱 명시적으로 객체 생성이 손쉽게 가능해진다.
하지만, 여기서 생기는 문제는 매 객체마다 builder를 만드는 것은 꽤나 수고롭고 반복적인 작업이라는 것이다.
이것을 해결할 수 있는 방법 중 하나가 Lombok의 Builder Annotation이다.
그것을 아래에서 알아보자.
@Builder
@Getter
public class Test{
private Integer number;
private String str;
}
public class TestMain{
@Test
void test1(){
Test test = Test.builder()
.number(1)
.str("test")
.build();
System.out.println("output : " + test.getNumber() + " " + test.getStr());
}
}
output : 1 test
위에서 알 수 있듯이, Lombok의 @Builder
Annotation은 자동으로 Builder Pattern에 맞게 builder 클래스를 생성해주고 그것을 사용할 수 있게 한다.
아까 위에서 사용했던 코드보다 코드가 훨씬 줄고, 쉽게 사용할 수 있다는 것이 느껴진다.
@Builder
@Getter
public class Test{
private Integer number;
private String str;
}
public class TestMain{
@Test
void test1(){
Test test1 = Test.builder()
.str("test")
.build();
System.out.println("test1 output : " + test1.getNumber() + " " + test1.getStr());
Test test2 = Test.builder()
.number(1)
.build();
System.out.println("test2 output : " + test2.getNumber() + " " + test2.getStr());
}
}
test1 output : 0 test
test2 output : 1 null
위와 같이 builder 를 사용하여 build를 했을 때, 특정 필드에 값을 지정해주지 않으면 0 혹은 null과 같은 값을 보이고 있다.
그러면 우리가 이 값을 지정하지 않았을 때, default로 지정하려면 어떻게 해야할까?
@Builder
@Getter
public class Test{
private Integer number = 2;
private String str = "default";
}
public class TestMain{
@Test
void test1(){
Test test1 = Test.builder()
.str("test")
.build();
System.out.println("test1 output : " + test1.getNumber() + " " + test1.getStr());
Test test2 = Test.builder()
.number(1)
.build();
System.out.println("test2 output : " + test2.getNumber() + " " + test2.getStr());
}
}
test1 output : 0 test
test2 output : 1 null
시도의 의도는 필드에 값을 미리 초기화해두면, "이 값을 쓰겠지?" 라는 의도였지만..
Builder 패턴을 사용했을 때의 결과를 보면 그렇지 않다는 것을 알 수 있다.
왜 그럴까?
우리는 그걸 알기 위해 Lombok의 문서에서 Builder에 대해 읽어보자.
@Builder.Default
If a certain field/parameter is never set during a build session, then it always gets 0 / null / false. If you've put @Builder on a class (and not a method or constructor) you can instead specify the default directly on the field, and annotate the field with @Builder.Default:
@Builder.Default private final long created = System.currentTimeMillis();
Builder에 대해 읽어보면 위와 같은 문구가 있다.
요약하자면, "builder 사용시 필드에 대한 값을 지정하지 않으면 타입에 따라 0, null, false가 초기화 될테니, @Builder.Default
Annotation을 명시하고 그 변수에 값을 초기화해라." 이다.
이제 우리는 문제의 원인과 해결책을 찾았으니 시도해보자.
@Builder
@Getter
public class Test{
@Builder.Default
private Integer number = 2;
@Builder.Default
private String str = "default";
}
public class TestMain{
@Test
void test1(){
Test test1 = Test.builder()
.str("test")
.build();
System.out.println("test1 output : " + test1.getNumber() + " " + test1.getStr());
Test test2 = Test.builder()
.number(1)
.build();
System.out.println("test2 output : " + test2.getNumber() + " " + test2.getStr());
}
}
test1 output : 2 test
test2 output : 1 default
우리가 원했던 결과를 얻었다.
시도는 성공하였고, 원인도 알아냈으니 아래에서 정리해보자.
@Builder.Default
Annotation을 명시하고, 초기화를 해야 기본값으로 사용할 수 있다.... 오늘의 궁금증을 이렇게 순차적으로 해결해보았다.
평소에 Builder를 사용하면서, 아무 생각없이 사용했었는데 몰랐던 것을 많이 알아간다.
오늘의 궁금증 해결 끝..