본문 바로가기
Spring

[Spring; 스프링] 스프링에서 `final`과 `@RequiredArgsConstructor`로 의존성 주입(DI) 쉽게 이해하기

by daddydontsleep 2024. 9. 23.
728x90
728x90

사진: Unsplash 의 Mathias Reding

스프링 프레임워크를 사용하다 보면 **의존성 주입(Dependency Injection, DI)**이라는 말을 자주 듣게 됩니다. 의존성 주입은 객체 간의 의존 관계를 스프링이 대신 관리해주는 것을 의미하는데요, 이 과정을 효율적으로 하기 위해 final과 @RequiredArgsConstructor를 많이 사용합니다. 이 글에서는 이 두 가지 키워드가 무엇인지, 왜 사용하는지, 그리고 롬복(Lombok) 없이도 의존성을 주입하는 방법까지 알아보겠습니다.

1. final 키워드는 무엇인가요?

final은 자바에서 변수를 한 번만 초기화하고, 이후에는 변경할 수 없게 만드는 키워드입니다. 이 키워드를 사용하면 다음과 같은 장점이 있습니다:

  1. 변경 방지: 해당 변수가 의도치 않게 변경되는 것을 방지합니다.
  2. 안정성: 코드의 안정성을 높여줍니다. 예를 들어, 프로그램이 실행되는 동안 서비스가 특정 리포지토리를 잘못된 다른 리포지토리로 변경되는 실수를 막을 수 있습니다.

2. @RequiredArgsConstructor란?

@RequiredArgsConstructor는 롬복(Lombok) 라이브러리에서 제공하는 애너테이션입니다. 이 애너테이션은 필수 생성자를 자동으로 만들어 줍니다. 여기서 필수 생성자란, final로 선언된 모든 멤버 변수를 초기화해주는 생성자를 말합니다.

예를 들어, 다음과 같은 코드가 있다고 가정해봅시다:

@Service
public class UserService {
    private final UserRepository userRepository;
    
    // @RequiredArgsConstructor가 이 생성자를 자동으로 만들어줍니다.
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

위 코드에서 @RequiredArgsConstructor를 사용하면 생성자를 따로 작성하지 않아도 다음과 같이 자동으로 생성자를 만들어줍니다:

@RequiredArgsConstructor
@Service
public class UserService {
    private final UserRepository userRepository;
}

3. 왜 final과 @RequiredArgsConstructor를 함께 사용할까요?

3-1. 의존성의 변경 방지

final 키워드를 사용하면 userRepository가 한 번 초기화된 후에는 변경될 수 없기 때문에, 서비스 클래스가 안정적으로 리포지토리를 사용할 수 있습니다.

3-2. 코드 간결화

@RequiredArgsConstructor를 사용하면 생성자를 자동으로 만들어주기 때문에 코드가 간결해지고, 생성자를 직접 작성하는 실수를 방지할 수 있습니다.

3-3. 의존성 주입(DI)의 편리성

스프링은 서비스 클래스의 생성자를 통해 리포지토리 같은 의존성을 주입합니다. @RequiredArgsConstructor는 필요한 생성자를 자동으로 생성하므로, 의존성 주입이 매우 간편해집니다.

4. 롬복 없이 의존성 주입하기

롬복 없이도 의존성 주입을 할 수 있습니다. 여기서는 생성자 주입필드 주입 방법을 소개하겠습니다.

4-1. 생성자 주입

롬복을 사용하지 않고, 생성자를 직접 작성하여 의존성을 주입하는 방법입니다.

import org.springframework.stereotype.Service;

@Service
public class UserService {
    private final UserRepository userRepository;
    
    // 생성자를 직접 작성하여 의존성 주입
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User findUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
    }
}

장점:

  • 의존성이 명확하게 드러나서 코드의 가독성이 높아집니다.
  • final 키워드를 사용하여 의존성 변경을 방지할 수 있습니다.

단점:

  • 의존성이 많은 경우 생성자가 길어질 수 있습니다.

4-2. 필드 주입

스프링의 @Autowired 애너테이션을 사용하여 필드에 직접 의존성을 주입하는 방법입니다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    
    // 필드 주입
    @Autowired
    private UserRepository userRepository;

    public User findUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
    }
}

장점:

  • 코드가 간결합니다.

단점:

  • final 키워드를 사용할 수 없어, 주입된 의존성이 변경될 수 있는 가능성이 있습니다.
  • 테스트가 어려워질 수 있습니다.

5. 어떤 방법을 사용할까요?

생성자 주입 권장

  • 생성자 주입은 의존성이 명확하게 드러나고, 테스트가 용이하며, final 키워드와 함께 사용할 수 있어 가장 권장되는 방법입니다.
  • @RequiredArgsConstructor는 이러한 생성자 주입을 자동으로 생성해주는 도구일 뿐, 생성자 주입 자체의 이점을 대체하지 않습니다.

필드 주입은 피하기

  • 필드 주입은 코드가 간결해 보일 수 있지만, 주입된 객체가 변경될 수 있는 위험이 있고, 테스트 시 주입을 직접적으로 할 수 없어 권장되지 않습니다.

6. 결론

final과 @RequiredArgsConstructor를 사용하면 코드가 더욱 안정적이고 간결해집니다. 의존성 주입 과정에서 실수를 방지하고, 서비스 클래스가 필요한 의존성을 안전하게 보장받을 수 있습니다. 롬복을 사용할 수 없는 경우에는 생성자 주입을 통해 동일한 효과를 얻을 수 있습니다.

이제 여러분도 final, @RequiredArgsConstructor, 그리고 생성자 주입을 활용해 코드의 품질을 한 단계 업그레이드해 보세요!

끝.

728x90
300x250