인프런에서 김영한님의 스프링강의를 듣다가 에러가 나서 이틀 동안 끙끙거렸다.
바로 @RequiredArgsConstructor 어노테이션 때문인데 의문점(에러)과 해결 방법, 그리고 왜 그랬는지에 대한 설명은 아래와 같다.
1. 의문점 (에러)
1. @RequiredArgsConstructor어노테이션과 생성자를 동시에 작성했음에도 에러가 나지 않는 것이 첫 번째 의문점. (이미 이미지만으로도 문제점을 눈치채신 분들이 계실 것 같다.....)
2. 그 상태(1번)에서 생성자로 테스트를 돌리면 문제없이 잘 돌아가지만, 롬복을 사용해 테스트를 돌리면 에러가 난다는 것이 두 번째 의문점.
그렇다면, 코드는 문제가 없고 @RequiredArgsConstructor 어노테이션의 문제인가? 롬복이 제대로 설치가 안됐나? 아니면 다른 곳에서 코드를 잘못썼는데 내가 찾지 못하는 건가? 하는 생각이 마구마구 들었다. 난 아직 초짜이기에 선생님의 코드를 완벽하게 이해하지 못해서 에러를 찾으려면 생각을 많이 해야 했다.
일단 이 어노테이션의 기능을 먼저 살펴보자.
2. @RequiredArgsConstructor의 기능
롬복이 제공하는 @RequiredArgsConstructor 어노테이션을 사용하면, final키워드나 @NonNull 이 붙은 필드를 모아서 자동으로 생성자를 만들어준다. 또한 이 경우 생성자가 하나이기 때문에 @Autowired 어노테이션까지 생략가능한 코드가 완성된다. 즉, 생성자 없이 생성자 주입을 할 수 있는 것이다.
수업 때 사용한 코드를 예로 들어보자면, 아래의 코드처럼 코드가 깔끔해지는 효과를 얻을 수 있다!
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
//@Autowired //생성자가 하나일 땐 생략 가능
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
//---------------------------두 코드는 동일한 코드이다.--------------------------------//
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
3. 나의 해결 방법
@RequiredArgsConstructor 어노테이션이 하는 역할은 알겠고... 그래서 내 코드는 왜 에러가 안 나고(?), 에러가 날까(?)????
코드를 요리조리 살펴보아도 다르거나 틀린 부분이 없는데 왜?? 밤새 고민하고 생각하다가 설마... 하는 마음에 선언한 필드의 위치를 바꿔봤다.
//기존
private final DiscountPolicy discountPolicy;
private final MemberRepository memberRepository;
//변경
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
드디어!!!! @RequiredArgsConstructor에 빨간줄이빨간 줄이 생기면서 며칠간 나를 괴롭혔던 문제가 해결이 되었다!! 빨간 줄이 생긴 이유는, @RequiredArgsConstructor는 자동으로 final키워드를 가진 필드를 모아 생성자를 만들어주는데, 밑에 또 같은 이름, 같은 매개변수를 받는 생성자가 있으니 중복으로 인식되어 에러가 생긴 것이다!
에러가 이렇게 반가운 건 또 처음인 것 같다.... 드디어 @RequiredArgsConstructor가 적용이 안된 이유를 찾았으니 이제 왜 그런지에 대해 알아보도록 하자.
(참고로, 필드의 위치를 바꾼 상태에서 @RequiredArgsConstructor를 이용해 테스트 코드를 실행했을 때 잘 돌아가는 것을 확인했다!)
4. WHY?
그렇다면 왜??? 일단 해결은 했는데..... 왜지?? 필드에 선언된 순서가 중요한 것이었나??? 자바 기초를 더 공부해야 하나?? 하는 생각이 들었다.
일단 @RequiredArgsConstructor를 사용했을 때 나온 에러 메세지는 다음과 같다.
< java: incompatible types: hello.core2.member.MemoryMemberRepository cannot be converted to hello.core2.discount.DiscountPolicy >
타입이 맞지 않아 A(MemoryMemberRepository)는 B(DiscountPolicy)로 변환할 수 없다는 내용의 에러메세지이다.
아래 이미지는 @RequiredArgsConstructor를 사용하여 테스트 코드를 돌렸을 때 에러가 나는 부분이다.
여기서 알아챘어야 했다. 그러나 바보처럼 자바 기본서의 생성자 부분을 다시 읽어보고 나서야 문제점이 머릿속에 스쳐 지나갔다.
원인은 이러했다.
내가 선언한 필드의 순서는
①private final DiscountPolicy discountPolicy ②private final MemberRepository memberRepository 였고
내가 수동으로 만든 생성자의 매개 변수 순서는
①MemberRepository memberRepository ②DiscountPolicy discountPolicy 였다.
그럼 롬복이 자동으로 만들어주는 생성자의 매개변수 순서는?
①DiscountPolicy discountPolicy ②MemberRepository memberRepository
그렇다.. 원인은 생성자 오버로딩이었다. 그렇기 때문에 첫 번째 의문점인 어노테이션과 생성자를 동시에 작성했을 때 에러가 나지 않았던 것이었으며, 두 번째 의문점인 롬복을 통해 테스트 코드를 돌렸을 때 에러가 나는 것이 당연했다. 자동 생성된 생성자의 매개변수 타입과, 객체를 생성하며 넣어준 매개변수의 타입이 맞지 않았으니까...
롬복은 내가 선언한 필드의 순서에 따라 생성자를 만들고, 매개변수를 넣어줬다. 실제로 롬복을 사용했을 때 컴파일된 코드를 살펴보면, ①DiscountPolicy discountPolicy ②MemberRepository memberRepository 의 순서로 매개변수를 받는 것 을 확인할 수 있다.
결국, 선언한 필드의 순서로 인해 수동 생성과 자동 생성의 매개변수 순서에 차이가 발생한 것이었다.
5. 생각
이번 에러는 기초적인 내용을 생각하지 못한 내 실수였다.
사실 오버로딩을 공부할 때 '매개변수 타입의 개수가 같아도 순서(위치)가 다르면 오버로딩이라고? 왜?? 굳이 왜 이렇게 했을까??'라는 의문을 가진 적이 있다. 그리고 오버로딩을 그렇게 많이 사용해보지 않아서 그 의문은 서서히 머릿속에서 잊혀졌던 것 같다. 그때 의문점에 대해 더 파고들고 조금이라도 더 공부했다면 이렇게까지 오래 걸리지는 않았을 것 같다.
그리고 롬복의 자동완성 기능의 단점(?)을 미처 생각하지 못했던 부분도 있다. 당연히 내가 만든 생성자와 겹쳐야 하는 게 맞다는 생각에 갇혀있었다. (실제로 롬복을 사용하면서 생각지도 못한 롬복의 기능에 애를 먹었던 사람들이 많다고 한다.)
스프링 공부를 한다면서 자바 기초도 생각 못하고 에러를 못 잡다니... 문제를 해결하고 나서는 허무하면서 분한 마음이 들었다. 이렇게 오래 걸렸다니!!!!라는 생각에...
그래도 나 스스로 칭찬해주고 싶은 점도 있다.
이 예제는 엄청나게 좋은 기능을 구현하는 예제가 아니라, 진짜 그냥 기초 수업용 테스트 예제였기 때문에 문제가 해결된 시점에서는 어? 되네? 하고 넘어갈 수도 있었을 것 같다.
그러나 나는 정말 너무 궁금해서 구글링을 하고, 자바 기본서도 다시 읽으며 문제의 원인을 찾아보려고 노력했다.
지금은 이런 실수를 했다는 것이 부끄럽긴 하지만... 기초가 중요하다는 것을 나 자신에게 말하기 위해 기록해 놓는다.
더 열심히 공부해야지 갑자기 의욕 활활!!!!!!🔥🔥🔥
'에러 모음 > 단거리' 카테고리의 다른 글
[ERROR] java.lang.IllegalArgumentException: No enum constant 에러 (0) | 2022.03.06 |
---|---|
[ERROR] Google API를 통한 Oauth2 로그인 구현 시 403 Forbidden 에러 (0) | 2022.03.06 |
댓글