백엔드 Back-end/스프링 Spring

Lombok @Getter @NoArgsConstructor @Builder @AllArgsConstructor 실제 빌드된 클래스로 살펴보기

Tap to restart 2022. 4. 23. 12:00

Lombok을 사용하면 Getter나 Setter처럼 정말 많이 쓰는 작업을 자동화할 수 있어서 편하다.

 

테스트를 위해서 User 클래스 하나를 만들었다.

아래처럼 생겼다.

package com.test;


public class User {
    private Long id;

    private String name;

    private String password;

    public void update(String name,
                       String password) {
        this.name = name;
        this.password = password;
    }
}

빌드해보자. 빌드를 하면 build 디렉터리 아래 class 파일이 생성된다.

원래는 알아볼 수 없게 생겼다.

빌드된 파일들
user.class 파일

IntelliJ IDEA 상에서 빌드된 파일을 클릭하면 디컴파일러를 통해서 원래 코드 모습을 확인할 수 있다.

 

public User() { } 생성자가 추가된 것을 볼 수 있다.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.test;

public class User {
    private Long id;
    private String name;
    private String password;

    public User() {
    }

    public void update(String name, String password) {
        this.name = name;
        this.password = password;
    }
}

 

@Getter

같은 코드에 @Getter 추가했다.

package com.test;

import lombok.Getter;

@Getter
public class UserWithGetter {
    private Long id;

    private String name;

    private String password;

    public void update(String name,
                       String password) {
        this.name = name;
        this.password = password;
    }
}

빌드된 클래스를 보자. get 메소드들이 추가된 걸 볼 수 있다. lombok이 자동으로 추가해주고 있다는 것을 확인할 수 있다.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.test;

public class UserWithGetter {
    private Long id;
    private String name;
    private String password;

    public UserWithGetter() {
    }

    public void update(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public Long getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public String getPassword() {
        return this.password;
    }
}

 

@NoArgsConstructor

같은 코드에 @NoArgsConstructor 추가했다.

package com.test;


import lombok.NoArgsConstructor;

@NoArgsConstructor
public class UserWithNoArgsConstructor {
    private Long id;

    private String name;

    private String password;

    public void update(String name,
                       String password) {
        this.name = name;
        this.password = password;
    }
}

빌드된 파일을 열어보자.

그냥 User 클래스와 아무런 차이가 없다.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.test;

public class UserWithNoArgsConstructor {
    private Long id;
    private String name;
    private String password;

    public void update(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public UserWithNoArgsConstructor() {
    }
}

 

@Builder 

@Builder를 클래스에 추가해봤다.

package com.test;


import lombok.Builder;

@Builder
public class UserWithBuilder {
    private Long id;

    private String name;

    private String password;

    public void update(String name,
                       String password) {
        this.name = name;
        this.password = password;
    }
}

 

모든 필드가 할당된 생성자가 생긴 것을 확인할 수 있다.

아무 필드도 없는 생성자는 없는 것을 알 수 있다.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.test;

public class UserWithBuilder {
    private Long id;
    private String name;
    private String password;

    public void update(String name, String password) {
        this.name = name;
        this.password = password;
    }

    UserWithBuilder(Long id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    public static UserWithBuilder.UserWithBuilderBuilder builder() {
        return new UserWithBuilder.UserWithBuilderBuilder();
    }

    public static class UserWithBuilderBuilder {
        private Long id;
        private String name;
        private String password;

        UserWithBuilderBuilder() {
        }

        public UserWithBuilder.UserWithBuilderBuilder id(Long id) {
            this.id = id;
            return this;
        }

        public UserWithBuilder.UserWithBuilderBuilder name(String name) {
            this.name = name;
            return this;
        }

        public UserWithBuilder.UserWithBuilderBuilder password(String password) {
            this.password = password;
            return this;
        }

        public UserWithBuilder build() {
            return new UserWithBuilder(this.id, this.name, this.password);
        }

        public String toString() {
            return "UserWithBuilder.UserWithBuilderBuilder(id=" + this.id + ", name=" + this.name + ", password=" + this.password + ")";
        }
    }
}

이 경우 builder를 쓰기 위해서는 반드시 세 필드를 할당해야만 한다. 아주 번거로워진다.

builder 자체를 쓰는 의미가 없어진다.

 

@Builder 메소드에 쓰기

필요한 메소드에만 추가해봤다. getter가 없기 때문에 name과 password를 public으로 했다.

package com.test;


import lombok.Builder;

public class UserWithMethodBuilder {
    private Long id;

    public String name;

    public String password;

    @Builder
    public void update(String name,
                       String password) {
        this.name = name;
        this.password = password;
    }
}

 

빈 생성자가 추가된 것을 볼 수 있다.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.test;

public class UserWithMethodBuilder {
    private Long id;
    public String name;
    public String password;

    public UserWithMethodBuilder() {
    }

    public void update(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public UserWithMethodBuilder.VoidBuilder builder() {
        return new UserWithMethodBuilder.VoidBuilder();
    }

    public class VoidBuilder {
        private String name;
        private String password;

        VoidBuilder() {
        }

        public UserWithMethodBuilder.VoidBuilder name(String name) {
            this.name = name;
            return this;
        }

        public UserWithMethodBuilder.VoidBuilder password(String password) {
            this.password = password;
            return this;
        }

        public void build() {
            UserWithMethodBuilder.this.update(this.name, this.password);
        }

        public String toString() {
            return "UserWithMethodBuilder.VoidBuilder(name=" + this.name + ", password=" + this.password + ")";
        }
    }
}

쓸모없다. 아래처럼 테스트 해보면 john이 나온다.

        UserWithMethodBuilder userWithMethodBuilder = new UserWithMethodBuilder();
        userWithMethodBuilder.builder().name("john").build();
        System.out.println(userWithMethodBuilder.name);

 

@Getter와 메소드에 @Builder 같이 쓰기

Builder를 쓰려면 Getter를 같이 쓰는 게 맞다. 필드 public으로 하기보다는.

package com.test;


import lombok.Builder;
import lombok.Getter;

@Getter
public class UserWithGetterAndMethodBuilder {
    private Long id;

    private String name;

    private String password;

    @Builder
    public void update(String name,
                       String password) {
        this.name = name;
        this.password = password;
    }
}

 

아래처럼 클래스가 생성된다.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.test;

public class UserWithGetterAndMethodBuilder {
    private Long id;
    private String name;
    private String password;

    public UserWithGetterAndMethodBuilder() {
    }

    public void update(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public UserWithGetterAndMethodBuilder.VoidBuilder builder() {
        return new UserWithGetterAndMethodBuilder.VoidBuilder();
    }

    public Long getId() {
        return this.id;
    }

    public String getName() {
        return this.name;
    }

    public String getPassword() {
        return this.password;
    }

    public class VoidBuilder {
        private String name;
        private String password;

        VoidBuilder() {
        }

        public UserWithGetterAndMethodBuilder.VoidBuilder name(String name) {
            this.name = name;
            return this;
        }

        public UserWithGetterAndMethodBuilder.VoidBuilder password(String password) {
            this.password = password;
            return this;
        }

        public void build() {
            UserWithGetterAndMethodBuilder.this.update(this.name, this.password);
        }

        public String toString() {
            return "UserWithGetterAndMethodBuilder.VoidBuilder(name=" + this.name + ", password=" + this.password + ")";
        }
    }
}

 

아래처럼 테스트를 해보면 john을 얻을 수 있다.

        UserWithGetterAndMethodBuilder userWithGetterAndMethodBuilder = new UserWithGetterAndMethodBuilder();
        userWithGetterAndMethodBuilder.builder().name("john").build();
        System.out.println(userWithGetterAndMethodBuilder.getName());

 

@AllArgsConstructor

@AllArgsConstructor을 클래스에 추가하자.

package com.test;


import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
public class UserWithAllArgsConstructor {
    private Long id;

    private String name;

    private String password;

    public void update(String name,
                       String password) {
        this.name = name;
        this.password = password;
    }
}

 

단어 뜻 그대로 모든 arguments 필드가 포함된 생성자가 추가된다.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.test;

public class UserWithAllArgsConstructor {
    private Long id;
    private String name;
    private String password;

    public void update(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public UserWithAllArgsConstructor(Long id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }
}

이렇게 할 경우 문제는 public이라 해당 클래스에 어떤 필드가 있는지 모두 노출된다는 점이다.

또 데이터베이스 Entity라면 보통 id는 auto increment 설정으로 자동 증가하게 되는데 id까지 설정할 수 있게 된다.

그래서 @AllArgsConstructor 편하지만 되도록 쓰지 않는게 좋다.

 

결론

@Getter와 특정 메소드에 @Builder를 쓰는 방식이 적절하다.

@Getter의 경우에 특정 필드만 쓸 수도 있다. 그렇게 해서 @Builder를 통해서 할당할 수는 있지만 값에 접근은 안 되게 할 수도 있다.

 

lombok-test.zip
0.14MB