본문 바로가기

Spring

[Spring] 의존 관계 주입 방법 4가지

 


 

 

✨ 의존관계를 자동으로 주입할 객체가 Spring Bean으로 등록되어 있어야 `@Autowired`로 주입이 가능합니다.

 

생성자 주입

생성자를 통해 의존성을 주입하는 방법입니다. (가장 추천되는 방식)

 

 

특징

최초에 한번 생성된 후 값이 수정되지 못합니다.(불변, final)

 

 

예시 코드

더보기

올바른 코드

@Component
public class HelloController {

    // 불변
    private final HelloService helloService;

    // 생성자 주입
    @Autowired
    public HelloController(HelloService helloService) {
        this.helloService = helloService;
    }

    public void greet() {
        helloService.sayHello();
    }
}


@Service
public class HelloService {
    public void sayHello() {
        System.out.println("Hello, Spring!");
    }
}


@SpringBootApplication
public class SpringConstructorInjectionExample {

    public static void main(String[] args) {
        SpringApplication.run(SpringConstructorInjectionExample.class, args);
    }
}
  • 생성자가 하나인 경우 `@Autowired` 생략이 가능합니다.

 

 

올바르지 못한 예시 코드

@Component
public class HelloController {

    // 불변
    private final HelloService helloService;
    private final HelloRepository helloRepository;

    public HelloController(HelloService helloService, HelloRepository helloRepository) {
        this.helloService = helloService;
        this.helloRepository = helloRepository;
    }    
    
    public HelloController(HelloService helloService) {
        this.helloService = helloService;
    }

    public void greet() {
        helloService.sayHello();
    }
}
  • 생성자가 두 개 이상인 경우, `@Autowired`를 생략하면 둘 중 어떤 생성자를 사용해야 하는 지 Spring은 알지 못합니다.
  • 따라서 두 개 이상의 생성자가 있을 경우, 어떤 생성자를 주입에 사용할지 명확히 지정하려면 `@Autowired`를 붙여야 합니다.

 


 

 

Setter 주입

Setter 메서드를 통해 의존성을 주입하는 방법입니다

 

 

특징

  • 의존성을 선택적으로 주입할 수 있어, 필수가 아닌 의존성에 적합합니다.
  • 생성자 주입과 달리, 생성 시점에 모든 필드가 초기화되지 않아 객체가 완전히 초기화되지 않은 상태로 존재할 수 있습니다.
  • 어떤 필드가 필수이고 어떤 필드가 선택인지 명확하지 않을 수 있습니다.

 

 

예시 코드

더보기
public interface HelloService {
    void sayHello();
}


@Primary
@Service("english")
public class EnglishHelloService implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("Hello!");
    }
}

@Service("korean")
public class KoreanHelloService implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("안녕하세요!");
    }
}


@Component
public class HelloController {

    private final Map<String, HelloService> helloServiceMap;
    private HelloService helloService;

    public HelloController(Map<String, HelloService> helloServiceMap, HelloService helloService) {
        // Spring이 모든 HelloService 구현체를 자동으로 주입하면서, Bean 이름을 키로 가지는 Map을 만들어줍니다.
        this.helloServiceMap = helloServiceMap;
        this.helloService = helloService; // 기본값
    }

    public void changeLanguage(String lang) {
        if (helloServiceMap.containsKey(lang)) {
            helloService = helloServiceMap.get(lang);
        } else {
            System.out.println("지원하지 않는 언어입니다.");
        }
    }

    public void greet() {
        helloService.sayHello();
    }
}


@SpringBootApplication
public class RuntimeInjectionExample {

    public static void main(String[] args) {
        SpringApplication.run(RuntimeInjectionExample.class, args);
    }

    @Bean
    CommandLineRunner run(HelloController controller) {
        return args -> {
            controller.greet();             // Hello!
            controller.changeLanguage("korean");
            controller.greet();             // 안녕하세요!
            controller.changeLanguage("english");
            controller.greet();             // Hello!
        };
    }
}
  • 이렇게 런타임에도 의존성을 바꿀 수 있습니다.
  • 기본적으로 의존성이 주입되는 것은 `@Primary`가 붙은 `EnglishHelloService`입니다.
    1. 시작될 때, HelloController의 helloService 변수에 `EnglishHelloService`가 주입됩니다.
    2. changeLanguage("korean")가 호출되어 `KoreanHelloService`가 주입됩니다.
    3. changeLanguage("english")가 호출되어 `EnglishHelloService`가 주입됩니다.

 


 

필드 주입

필드에 직접적으로 주입하는 방법입니다. (가장 추천되지 않음)

 

 

특징

  • 별도의 생성자나 setter 메서드 없이 필드 위에 `@Autowired`만 붙이면 됩니다.
  • private 필드에도 주입 가능합니다. 
    • Reflection을 사용해 접근 제한자 무시하고 주입할 수 있습니다.
  • 의존성이 private 필드에 주입되므로, 단위 테스트에서 직접 필드 값을 바꾸기 어렵습니다. (테스트 시 번거로움)
  • final 키워드를 사용할 수 없으므로, 의존성이 변경될 가능성이 있습니다.
  • IoC 컨테이너 없이 객체 생성 시 의존성 주입이 되지 않습니다. (new)

 

 

예시 코드

더보기
@Component
public class MyService {

    @Autowired
    private SomeDependency someDependency;

    public void doSomething() {
        someDependency.action();
    }
}

@Component
public class SomeDependency {
    public void action() {
        System.out.println("Dependency action!");
    }
}
  • MyService 클래스 안에 `@Autowired`로 직접 필드 주입합니다.
  • `someDependency`는 private 필드인데도 스프링이 리플렉션으로 주입해 줍니다.

 

리플렉션(Reflection)이란?

간단히 말해서, 런타임에 프로그램이 자신(또는 다른 클래스)의 메타 정보(필드, 메섣, 생성자 등)를 조사하고 조작할 수 있는 기능입니다.

 

필요한 이유

일반적으로 자바는 컴파일 시점에 모든 타입과 접근이 정해져 있고, `private` 같은 접근 제한자가 있어서 직접 접근이 불가합니다. 하지만 리플렉션을 사용하면 private 필드에 접근해서 값을 바꿀 수 있습니다.

 

 


 

일반 메서드 주입

메서드를 통해 의존성을 주입하는 방식입니다.

 

생성자, setter 주입으로 대체가 가능하기 때문에 잘 사용하지 않습니다.

 

 

예시 코드

더보기
@Component
public class MyApp {

    private MyService myService;

    // 일반 메서드 주입
    @Autowired
    public void init(MyService myService) {
        this.myService = myService;
    }
    
    public void run() {
        myService.doSomething();
    }

}