[Spring/Lombok] @Builder

Bam·2024년 4월 23일
0

Spring

목록 보기
12/48
post-thumbnail

지난 시간에 따로 다루기로 했던 @Builder 어노테이션에 대해서 좀 더 자세히 알아보겠습니다.


@Builder

빌더 패턴에 대한 내용이 들어있기 때문에 사전에 빌더 패턴이 학습된 상태여야 이해할 수 있습니다. 빌더 패턴에 대해서는 이 포스트를 참조해주세요.

@Builder는 클래스에 빌더 패턴을 간단하게 적용시켜주는 어노테이션입니다. 빌더 패턴이 좋은 패턴이긴 하지만 빌더라는 별개의 클래스가 작성되어야하는 만큼 코드량이 늘어나기 때문에 이를 보조하기 위해 등장한 어노테이션이라고 할 수 있습니다.

다음은 Member 클래스와 빌더 클래스 코드입니다.

@Getter
@ToString
@NoArgsConstructor
public class Member {
    //필수 필드
    private String name;

    //선택 필드
    private int age;

    //빌더 클래스
    public static class MemberBuilder {
        //필수 필드
        private String name;

        //선택 필드
        private int age;

        public MemberBuilder(String name) {
            this.name = name;
        }

        public MemberBuilder age(int age) {
            this.age = age;
            return this;
        }

        public Member build() {
            return new Member(name, age);
        }
    }
}
public static void main(String[] args) {
	Member member = new MemberBuilder("알렉스").age(20).build();

	System.out.println(member.toString());
}

@Builder로 빌더 패턴 적용하기

@Builder클래스에 적용하는 방식과 생성자에 적용하는 방식 두 가지 방식으로 나뉘어질 수 있습니다.

클래스 레벨에 선언

위 코드의 클래스 레벨에 @Builder를 적용해보겠습니다.

@Getter
@ToString
@NoArgsConstructor
@Builder
public class Member {
    //필수 필드
    private String name;

    //선택 필드
    private int age;
}

이게 끝입니다. 컴파일 타임에 @Builder가 알아서 빌더 클래스를 만들어서 빌더에 대한 작업을 수행하게 됩니다.

하지만, 이 방식은 컴파일과정에서 에러가 발생합니다.
이 이유는 클래스 레벨에 @Builder를 사용하게 되면 클래스에 존재하는 모든 필드에 대한 생성자가 필요한데, 우리는 @NoArgsConstructor를 사용해서 빈 생성자를 생성했기 때문에 에러가 발생하는 것 입니다.

그렇기 때문에 @AllArgsConstructor를 사용해서 모든 필드를 갖는 생성자를 만들어주도록 합니다.

이때 아무 위치에서나 생성자를 통해 객체를 생성할 수 없도록 @AllArgsConstructorAccessLevelPRIVATEPROTECTED로 설정해줍니다.

@Getter
@ToString
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class Member {
    //필수 필드
    private String name;

    //선택 필드
    private int age;
}
public class Main {
    public static void main(String[] args) {
        Member member = Member.builder()
                .name("알렉스")
                .age(20)
                .build();

        System.out.println(member.toString());
    }
}

하지만 주석이 보이시나요? 모든 필드를 받으면 안됩니다. 우리는 필수 필드와 선택 필드가 구분되어있기 때문입니다. 원하는 필드 값만 입력받고 싶다면 우리는 생성자 레벨에서 @Builder를 사용해야합니다.


생성자 레벨에 선언

원하는 값만 빌더 패턴으로 입력받고 싶다면, 특정 필드가 들어있는 생성자를 작성하고 @Builder를 붙이기만 하면 됩니다.

@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Member {
    //필수 필드
    private String name;

    //선택 필드
    private int age;

    @Builder
    public Member(String name) {
        this.name = name;
    }
}

위와 같이 작성하게 되면 builder로는 name 필드만을 입력받을 수 있게 됩니다.

age를 입력받으려고하면 다음과 같이 에러가 발생하게 됩니다.이렇게 하면 필수가 아닌 필드는 외부에 노출되지 않고, 필요한 값에만 빌더 패턴을 적용시켜서 값을 받을 수 있게 됩니다.

builderMethodName

위 방식대로 진행하면 필수인 필드에 대해서만 빌더 패턴을 적용시켜서 값을 받을 수 있게 됩니다. 그러나 필수 필드임에도 빈 문자열을 전달하게되면 의도와는 다르게 정상적으로 객체가 생성됩니다.

@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Member {
    //필수 필드
    private String name;

    //선택 필드
    private int age;

    @Builder
    public Member(String name) {
        this.name = name;
    }
}
public class Main {
    public static void main(String[] args) {
        Member member = Member.builder().build();

        System.out.println(member.toString());
    }
}

이러한 현상을 방지하고 싶다면 builderMethodName을 이용해서 필수 필드를 명확하게 지정할 수 있게 됩니다.

@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Member {
    //필수 필드
    private String name;

    //선택 필드
    private int age;
    
    @Builder(builderMethodName = "cons")
    private Member(String name, int age) {
        this.name = name;
        this.age = age;
    }


    @Builder(builderMethodName = "onlyNameBuilder")
    public static Member onlyNameBuilder(String name) {
        return Member.cons().name(name).build();
    }
}
public class Main {
    public static void main(String[] args) {
        Member member = Member.onlyNameBuilder("알렉스");

        System.out.println(member.toString());
    }
}

이 방식 외에도 필수 필드에 @NonNull을 붙여줌으로써 필수 필드임을 알릴 수 있습니다.
@NonNull은 어노테이션이 붙은 변수에 null이 들어오면 NullPointerException을 발생시킵니다.

@NonNull
private String name;

0개의 댓글