Today I Learned

@Default 빌더 기본값 적용

greatwhite 2024. 5. 28. 01:01

java.lang.NullPointerException: Cannot invoke "com.keb.kebsmartfarm.entity.Plants.hasGrowingPlant()" because "this.plants" is null

일급 컬렉션 적용하면서 plants에서 현재 키우는 식물이 있는지 확인하는 hasPlant메서드를 실행하는데 문제가 생겼다.

아무래도 @Builder@NoArgsConstructor 중에 문제가 있는 것 같아, 빈 생성자에 new Plants()로 직접 삽입해줬다. 그런데도 문제가 있는 거 보니 @Builder 문제가 맞는 것 같다.

이 글을 참고해 생성된 Builder 코드를 직접 살펴보았다.

public static class ArduinoKitBuilder {
        private Long kitNo;
        private ReleasedKit releasedKit;
        private String deviceName;
        private String serialNum;
        private LocalDateTime date;
        private User user;
        private Long userSeqNum;
        private Plants plants;
        private List<SensorData> sensorDataList;

        ArduinoKitBuilder() {
        }

        public ArduinoKitBuilder kitNo(final Long kitNo) {
            this.kitNo = kitNo;
            return this;
        }

        public ArduinoKitBuilder releasedKit(final ReleasedKit releasedKit) {
            this.releasedKit = releasedKit;
            return this;
        }

        public ArduinoKitBuilder deviceName(final String deviceName) {
            this.deviceName = deviceName;
            return this;
        }

        public ArduinoKitBuilder serialNum(final String serialNum) {
            this.serialNum = serialNum;
            return this;
        }

        public ArduinoKitBuilder date(final LocalDateTime date) {
            this.date = date;
            return this;
        }

        @JsonIgnore
        public ArduinoKitBuilder user(final User user) {
            this.user = user;
            return this;
        }

        public ArduinoKitBuilder userSeqNum(final Long userSeqNum) {
            this.userSeqNum = userSeqNum;
            return this;
        }

        @JsonIgnore
        public ArduinoKitBuilder plants(final Plants plants) {
            this.plants = plants;
            return this;
        }

        public ArduinoKitBuilder sensorDataList(final List<SensorData> sensorDataList) {
            this.sensorDataList = sensorDataList;
            return this;
        }

        public ArduinoKit build() {
            return new ArduinoKit(this.kitNo, this.releasedKit, this.deviceName, this.serialNum, this.date, this.user, this.userSeqNum, this.plants, this.sensorDataList);
        }

        public String toString() {
            return "ArduinoKit.ArduinoKitBuilder(kitNo=" + this.kitNo + ", releasedKit=" + this.releasedKit + ", deviceName=" + this.deviceName + ", serialNum=" + this.serialNum + ", date=" + this.date + ", user=" + this.user + ", userSeqNum=" + this.userSeqNum + ", plants=" + this.plants + ", sensorDataList=" + this.sensorDataList + ")";
        }
    }

직접 살펴보니 @Builder 어노테이션은 클래스의 기본 생성자를 사용하지 않고 비어있는 빌더 클래스에서 매개변수로 받은 필드들을 채워나가는 형식으로 되어있는 것을 확인할 수 있었다. 그래서 기본 생성자로 채워넣든 필드를 초기화하든 관계 없이 매개변수로 넣어주지 않으면 채워지지 않는다.

아두이노 키트로 변환할 때 필요한 plants 값을 넣어주지 않았기 때문에 발생한 문제였다. 그래서 기본값을 넣어줘야 하는데 이 글을 참고해서 @Default 어노테이션으로 기본값을 넣어줬다. 그리고 직접 @Default 를 넣었을 때 생성되는 코드를 확인해봤다.

public class ArduinoKit {
        ...
        private Plants plants;
        ...
        private static Plants $default$plants() { return new Plants(); }
        public static class ArudinoKitBuilder {
                ...
                private boolean plants$set;
                private Plants plant$value;
                ...

                @JsonIgnore
                public ArduinoKitBuilder plants(final Plants plants) {
                        this.plants$value = plants;
                        this.plants$set = true;
                        return this;
                }

                public ArduinoKit build() {
                        Plants plants$value = this.plants$value;
                        if (!this.plants$set) {
                                plants$value = ArduinoKit.$default$plants();
                        }
                        return new ArduinoKit(this.kitNo, this.releasedKit, this.deviceName, this.serialNum, this.date, this.user, this.userSeqNum, plants$value, this.sensorDataList);
                }
        }

}

직접 확인해보니 ArduinoKitBuilder 내부에 변수가 할당이 되었는지 확인하는 $##$setboolean 으로 할당되어 있다. plants 메서드로 직접 plant$value를 할당하면 설정이 되었다는 의미인 true 로 둔다.

이후 build 메서드에서 set이 false 일 경우에만 $default$plants 메서드로 새로 생성해서 할당한다.

결론적으로 해결은 했다. @Builder 역시 남들이 다 쓰니까 나 역시도 관습적으로 사용했는데 빌더가 어떻게 작용하는지와 어떤 추가 어노테이션이 있는지 몰라서 조금 헤멨었다. 그리고 빌더를 사용하면 당연히 기본 생성자를 사용해서 쓸 것이라고 생각했는데 아예 내부 정적 클래스에서 새로 값을 추가해가면서 변환하는 것이였다. 역시 뭐든 잘 알고 사용하지 않으면 탈이난다..

 

참고

[intelliJ] lombok 생성 코드 확인하기

Lombok @Builder 사용 시 기본값 지정 방법