내가 @RequiredArgsConstructor를 통해 DI를 한 이유

Kevin·2024년 5월 1일
1
post-thumbnail

🤗 서론

나는 주로 Spring에서 Dependency를 Injection 받을 때 @RequiredArgsConstructor를 통한 방법을 주로 사용했다.

이 때 정확히 어떤 원리를 통해서 DI가 되는 것인지에 대해서는 잘 알아보지 않고 사용을 했었는데, 이번에 @RequiredArgsConstructor 와 Spring의 DI 방식에 대해서 함께 알아 가보고자 한다.


😗 본론

1️⃣ @RequiredArgsConstructor에 대해서

자세한 내용들을 살펴보기 전에 @RequiredArgsConstructor 어노테이션에 대해서 먼저 알아보면 좋을 것 같다.

@RequiredArgsConstructor 어노테이션은 final이 붙거나 @Noutnull이 붙은 필드의 생성자를 자동으로 생성해주는 Lombok 어노테이션이다.

아래 코드에 대해서 살펴보면 도움이 될 것이다.

아래는 내가 강아지 산책 플랫폼을 개발하면서 사용한 일부 코드이다.

@Service
@RequiredArgsConstructor
public class WalkService {

    private final WalkRepository walkRepository;

    private final MemberJpaRepository memberJpaRepository;

    private final MatchingRepository matchingRepository;

    private final PaymentRepository paymentRepository;

   // 아래 코드는 생략
}

위 코드를 컴파일하면 아래와 같은 코드가 된다.

@Transactional(
    readOnly = true
)
@Service
public class MemberService {
    private static final Logger log = LoggerFactory.getLogger(MemberService.class);
    private final MemberJpaRepository memberJpaRepository;
    private final RefreshTokenJpaRepository refreshTokenJpaRepository;
    private final PaymentRepository paymentRepository;
    private final WalkRepository walkRepository;
    private final DogJpaRepository dogJpaRepository;
    private final ApplicationRepository applicationRepository;
    private final NotificationJpaRepository notificationJpaRepository;
    private final ReviewRepository reviewRepository;

    public MemberService(final MemberJpaRepository memberJpaRepository, final RefreshTokenJpaRepository refreshTokenJpaRepository, final PaymentRepository paymentRepository, final WalkRepository walkRepository, final DogJpaRepository dogJpaRepository, final ApplicationRepository applicationRepository, final NotificationJpaRepository notificationJpaRepository, final ReviewRepository reviewRepository) {
        this.memberJpaRepository = memberJpaRepository;
        this.refreshTokenJpaRepository = refreshTokenJpaRepository;
        this.paymentRepository = paymentRepository;
        this.walkRepository = walkRepository;
        this.dogJpaRepository = dogJpaRepository;
        this.applicationRepository = applicationRepository;
        this.notificationJpaRepository = notificationJpaRepository;
        this.reviewRepository = reviewRepository;
    }
    
    // 아래 코드는 생략
}

이 코드들을 통해서 MemberService class의 final로 선언된 변수들에 대하여 @RequiredArgsConstructor 어노테이션이 생성자를 만들어주는 모습을 확인할 수 있다.

그렇다면 여기서 눈썰미가 좋은 사람이면 눈치 챌 수 있는 부분이 있다.

해당 생성자로 Spring이 어떻게 DI를 해주는걸까?


Spring은 @Autowired가 되어있는 생성자에 대하여 DI를 해준다.

그러나 위 생성자는 @Autowired가 되어있지 않다.

이 비밀은 바로 Spring에 있다.

2️⃣ Spring + @RequiredArgsConstructor

Spring 프레임워크는 3.4ver 부터 클래스에 1개뿐인 생성자에 대하여 @Autowired가 없을 경우, 해당 생성자에 대하여 자동으로 @Autowired로 인식하여 처리한다.

즉 생성자가 1개라면 생성자에 @Autowired를 생략해도 된다는 뜻이다.

@RequiredArgsConstructor 어노테이션은 이러한 Spring의 특징을 더 효율적, 효과적으로 이용할 수 있는 어노테이션이라 할 수 있다.

만약 @RequiredArgsConstructor 어노테이션이 없었다면 생성자를 직접 작성하고, 필드 변경시 매번 생성자도 변경을 해줘야 하는 리소스가 소모 되었을 것이다.

3️⃣ 왜 생성자 주입을 지향할까?

여기까지 Spring과 @RequiredArgsConstructor 어노테이션의 장점에 대해서 찬양을 하는 시간을 가졌다.

좋다.

그러나 더 깊게 생각을 해서, 왜 우리는 생성자 주입을 통한 DI를 사용해야 할까?

다들 알다시피 DI를 하는 방법은 @Autowired를 사용한 필드 주입이나 Setter를 이용한 주입 방법도 있다.

그런데 왜 생성자 주입을 다들 권장할까?

  1. final 키워드 작성 및 Lombok 어노테이션과 시너지

가장 첫번째 장점으로는 우리가 위에서 이야기 한 부분이 있다.

생성자 주입을 사용하게 된다면 필드에 final 키워드를 사용할 수 있기에 객체의 불변성을 확보할 수 있고, 컴파일 시점에 누락된 의존성을 확인할 수 있다.

→ 다른 주입 방법들은 객체의 생성 이후에 호출되기에 final 키워드를 사용할 수 없다.

또한 Lombok의 어노테이션을 사용하여 코드를 더욱 쉽고, 간결하게 만들 수 있는 장점도 있다.

  1. 객체의 불변성이 확보된다.

만약 Setter를 통한 수정자 주입을 사용하게 된다면, 객체 생성 이후에 해당 의존성이 변경될 수 있는 가능성이 열려있게 된다.

만약 의존성이 변경되지 않더라도, 이러한 변경 가능성은 유지보수성을 떨어트린다.

  1. 테스트 코드가 프레임워크에 종속되지 않는다.

만약 @Autowired를 통한 필드 주입을 사용한다고 한다고 해보자.

@Service
public class WalkService {

		@Autowired
    private WalkRepository walkRepository;

		@Autowired
    private MemberJpaRepository memberJpaRepository;

		@Autowired
    private MatchingRepository matchingRepository;

		@Autowired
    private PaymentRepository paymentRepository;

   // 아래 코드는 생략
}

그렇다면 위와 같은 코드가 될 것이다.

public class WalkServiceTest {

		@Test
		public void test() {
			WalkService walkService = new WalkService();
			
			// 아래 코드는 생략
		
		}
}

위의 코드를 테스트할 때는 Spring 위에서 동작하지 않는다.

이 말은 즉 @Autowired를 통해 DI를 받을 수 없다는 말이 된다.

그렇기에 의존 객체들을 사용하고자 할 때 NPE가 발생할 것이다.

그러나 만약 생성자 주입을 사용한다면 컴파일 시점에 객체를 주입 받아 테스트 코드를 작성할 수 있고, 주입하는 객체가 누락된 경우에 컴파일 시점에서 오류를 발견할 수도 있게 된다.

📰 레퍼런스

[Spring] 스프링(Spring)과 롬복(Lombok)의 결합, @RequiredArgsConstructor를 사용한 생성자 주입

[Spring] 다양한 의존성 주입 방법과 생성자 주입을 사용해야 하는 이유 - (2/2)

[Spring] @RequiredArgsConstructor 어노테이션을 사용한 "생성자 주입"

profile
Hello, World! \n

0개의 댓글