[Spring] 어노테이션으로 반복 코드를 줄여주는 Lombok(롬복)
Lombok이란?
Lombok은 여러가지 @어노테이션을 제공하고
이를 기반으로 반복 소스코드(boiler plate)를 컴파일 과정에서 생성 해주는 방식으로 동작하는 라이브러리이다.
즉, 코딩 과정에서 롭복과 관련된 어노테이션만 보이고 getter,setter 등의 생략되지만
실제로 컴파일된 결과물 .class 파일에는 코드가 생성되어 있다는 뜻이다.
Lombok 어노테이션의 종류
- @Getter, @Setter : Java Bean 규약에 있는 setter, getter를 생성
- @ToString : Object에 기본 구현된 ToString 대신 객체의 값 보여주는 ToString을 생성
- @NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor : 생성자를 생성
- @Data : @Getter/@Setter/@ToString/@EqualsAndHashCode/@RequiredArgsContructor을 포함
- @Slf4j : 객체에 맞는 로그를 췹게 출력하는걸 도와줌
- @UtilityClass : 유틸리티 성 클래스의 생성자를 private으로 만들어서 인스턴스가 생성되지 못하게 함
@Getter와 @Setter
Java Bean 규약에는 필드(프로퍼티)를 모두 private으로 두고 getter와 setter을 통해서만 프로퍼티에 접근하라는 규약이 있다.
규약을 지키기 위해 getter와 setter을 모든 프로퍼티에 대해 직접 만들어주는 방법도 있지만,
1. 코드 자체가 길어질 뿐 아니라
2. 프로퍼티의 이름을 바꿀 때 getter와 setter의 이름도 바꿔야 하고
3. 새로운 프로퍼티가 추가되었을 때 그 프로퍼티에 대한 getter와 setter을 새로 추가해야 한다는 번거로움이 있다.
이를 도와주는 것이 lombok의 @Getter / @Setter 어노테이션이다.
Getter, Setter 확인 by test code
lombok이 컴파일 과정에서 코드를 만들어 준다는 것을 확인하기 위해서, test code를 작성해보자.
(인텔리제이에서 Ctrl+Shift+T를 누르면 {클래스이름}Test.class 라는 이름의 Test코드가 생성됨)
test code에서 getter와 setter이 생성되었는지 확인할 클래스의 인스턴스를 만들고,
getter와 setter을 호출하면 오류가 뜨지 않고 정상 작동하는 것을 볼 수 있다.
Getter, Setter 확인 by 디롬복
getter와 setter이 생성되었는지를 delombok 기능을 이용해 확인할 수도 있다.
@Getter || @Setter 어노테이션을 드래그한 다음 '우클릭 > 리팩토링 > 디롬복'을 하면 getter와 setter 메소드를 확인할 수 있다.
// AccountDto.class
import lombok.*;
import java.time.LocalDateTime;
@Getter
@Setter
public class AccountDto {
private String accountNumber;
private String nickname;
private LocalDateTime registeredAt;
}
// AccountDtoTest.class
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
class AccountDtoTest {
@Test
void accountDto() {
AccountDto accountDto = new AccountDto();
// getter와 setter 테스트
accountDto.setAccountNumber("123");
System.out.println(accountDto.getAccountNumber());
}
}
@ToString
class에 기본으로 구현된 ToString은 객체 고유의 해시코드를 리턴하는데, 이는 거의 사용하지 않으므로
객체에 담긴 값을 확인하기 위해서는 toString 메서드를 오버라이딩 해줄 필요가 있다.
롬복의 @ToString 이용하면, 객체가 갖는 값을 출력하는 toString을 만들 수 있다.
// AccountDto.class
import lombok.*;
import java.time.LocalDateTime;
@Getter
@Setter
@ToString
public class AccountDto {
private String accountNumber;
private String nickname;
private LocalDateTime registeredAt;
}
// AccountDtoTest.class
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
class AccountDtoTest {
@Test
void accountDto() {
AccountDto accountDto = new AccountDto();
// getter와 setter 테스트
accountDto.setAccountNumber("123");
System.out.println(accountDto.getAccountNumber());
// toString 테스트
System.out.println(accountDto.toString());
// AccountDto(accountNumber=123, nickname=null, registeredAt=null) 출력됨
}
}
@NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor
이름에서 알 수 있다시피, 위 세가지 어노테이션은 생성자를 만들어주는 롬복이다.
하지만 단순히 해당하는 생성자를 만들어주는 것이 아니라, 세가지의 용도가 다르다는 것도 알고 있어야 한다.
@NoArgsConstructor
인자가 없는 기본 생성자를 만들어준다.
기본 생성자를 만드는 어노테이션이므로 자바에서 기본 생성자를 만드는 원리를 고려하며 사용해야 한다.
자바에서 기본 클래스를 생성하는 원칙
1) 어떤 생성자도 없으면 기본 생성자 자동 생성
2) 생성자가 하나라도 있으면 기본 생성자는 만들지 않음
이를 고려하면서 '기본 생성자를 정의했음에도 @NoArgsConstructor를 선언'하거나,
'다른 생성자 어노테이션을 썼음에도 기본 생성자가 있을 것이라 생각'하는 등의 오류를 범하지 않도록 해야한다.
@NoArgsConstructor의 활용 :
1) 완전한 객체를 만들도록 강제
2) Jpa에서 Lazy Loading을 통해서 객체를 프록시 형태로 조회하기 위해
1) 완전한 객체를 만들도록 강제
@NoArgsConstructor에 (access=AccessLevel.PROTECTED 또는 PRIVATE) 속성을 부여하면,
기본 생성자에 접근할 수 없게 된다.
따라서 기본 생성자로는 객체를 만들 수 없으므로
{기본 생성자로 생성 -> Setter로 값 등록 -> 몇몇 프로퍼티 누락} 의 경로로 만들어지는
불완전한 객체를 원천 차단할 수 있게 된다.
즉, @NoArgsConstructor(access=AccessLevel.PROTECTED) 로 완전한 상태의 객체를 생성하게 강제할 수 있다.
2) Jpa의 Entity 프록시 형태로 조회하기 위해
▽ 잘 정리된 블로그 글
[JPA] 프록시(Proxy) (velog.io)
[JPA] 즉시로딩(EAGER)과 지연로딩(LAZY) (왜 LAZY 로딩을 써야할까?) (1) (velog.io)
@AllArgsConstructor
이름 그대로 클래스의 모든 프로퍼티를 인자로 갖는 생성자를 만들어준다.
@RequiredArgsConstructor
생성자를 이용해서 의존성 주입을 받을 때 사용한다.
필요한 스프링빈을 인자로 갖는 생성자를 자동으로 만들어준다.
@Data
@Data 어노테이션은 @Getter/@Setter/@ToString/@EqualsAndHashCode/@RequiredArgsContructor 을 합쳐놓은 어노테이션으로,
getter, setter, toString, equals, 그리고 생성자, 즉, POJO(Plain Old Java Objects)와 bean과 관련된 모든 코드를 생성한다.
@Data가 얼마나 코드를 줄여주는지 보여주는 글 : https://zi-c.tistory.com/entry/JAVA-Lombok-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-Data
@Data 어노테이션은 만능일까?🤔
의외로 실무에서는 Data 어노테이션을 중요하지 않은 부분에만 사용한다고 한다.
Data 어노테이션을 계좌 번호와 같은 클래스에 사용하게 되면, to String으로 정보가 노출될 위험이 있기 때문이다.
따라서 그때그때 필요한 부분만 달아주는 것이 실제로 사용하는 패턴이라고 한다.
→ @Data 는 주의해서 사용해야 한다.
// AccountDto.class
import lombok.*;
import java.time.LocalDateTime;
@Data
public class AccountDto {
private String accountNumber;
private String nickname;
private LocalDateTime registeredAt;
}
// AccountDtoTest.class
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
class AccountDtoTest {
@Test
void accountDto() {
// @Data 선언만으로 아래 메서드들이 생성됨
AccountDto accountDto = new AccountDto();
accountDto.setAccountNumber("123");
System.out.println(accountDto.getAccountNumber());
System.out.println(accountDto.toString());
}
}
@Slf4j
원래 로그는 각 클래스의 정보를 담아서 생성해야 하는데, 이 어노테이션으로 간단히 로그를 출력할 수 있다.
로그를 출력하는 문법은 다음과 같다. => log.error("error is occured");
@UtilityClass
유틸리티성 클래스란,
클래스 자체는 껍데기일 뿐이고, 실제로 내부의 static 메서드를 사용하기 위한 클래스이다.
유틸리티성 클래스의 경우 클래스를 생성할 필요가 없으므로
생성자를 private으로 지정하여 인스턴스를 만들지 못하게 막아줘야 한다.
이 과정을 줄여주는게 @UtilityClass이다.
참고 블로그