Back-End (Web)/Spring

[Spring] 의존관계 주입

JABHACK 2024. 12. 26. 16:29

의존관계 주입

📌 객체 간의 의존성을 스프링 컨테이너가 자동으로 관리하고 주입해주는 설계 패턴입니다. 객체가 다른 객체를 필요로 할 때, 직접 생성하지 않고 외부에서 주입받아 사용하도록 설계하는 방식입니다.

  • @Autowired 는 의존성을 자동으로 주입할 때 사용하는 Annotation 이다.
    • 기본적으로 주입할 대상이 없으면 오류가 발생한다.(required = true)

 

의존관계 주입의 기본 개념

  1. 의존성(Dependency):
    • A 객체가 B 객체를 사용해야 한다면, A는 B에 의존하고 있다고 말합니다.
    • 이때 B 객체를 A 내부에서 직접 생성하지 않고, 외부에서 주입받는 것을 의존관계 주입이라고 합니다.
  2. 스프링 DI:
    • 스프링 컨테이너가 객체(빈)를 생성하고 관리하면서 필요한 의존성을 자동으로 주입합니다.
    • 개발자는 직접 의존성을 생성하거나 연결할 필요가 없으며, 컨테이너가 이를 처리합니다.

 

왜 의존성 주입인가?

@Autowired와 스프링 컨테이너가 있기 때문에, 개발자가 의존 객체를 직접 생성하지 않아도 되고, 컨테이너가 이를 대신 처리합니다. 이 점이 의존성 주입의 핵심입니다.

장점

  1. 결합도 감소:
    • MyApp은 MyService의 구체적인 구현체를 몰라도 됩니다. (DIP 원칙 준수)
    • 다른 구현체로 변경할 때 코드 수정이 필요 없습니다.
  2. 유연성 증가:
    • 스프링 컨테이너에서 주입받는 객체를 쉽게 교체하거나 확장할 수 있습니다.
  3. 테스트 용이성:
    • 테스트 환경에서 Mock 객체를 주입할 수 있습니다.

 

1. 생성자 주입

  • 생성자를 통해 의존성을 주입하는 방법.
  • 최초에 한번 생성된 후 값이 수정되지 못한다.[불변, 필수]
public interface MyService {
    void doSomething();
}

// Spring Bean으로 등록
@Service
public class MyServiceImpl implements MyService {
    @Override
    public void doSomething() {
        System.out.println("MyServiceImpl 메서드 호출");
    }
}

// 생성자 주입 방식
@Component
public class MyApp {
		// 필드에 final 키워드 필수! 무조건 값이 있도록 만들어준다.(필수)
    private final MyService myService;

		// 생성자를 통해 의존성 주입, 생략 가능
    @Autowired
    public MyApp(MyService myService) {
        this.myService = myService;
    }

    public void run() {
        myService.doSomething();
    }
    
}

@ComponentScan(basePackages = "com.example.springdependency.test")
public class Main {
    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);

        // 등록된 MyApp 빈 가져오기
        MyApp myApp = context.getBean(MyApp.class);

        // 빈 메서드 호출
        myApp.run();
    }
}

-------------------------------------

// 생성자가 두개인 경우 생략이 불가능하다.
@Component
public class MyApp {
		// 필드에 final 키워드 필수! 무조건 값이 있도록 만들어준다.(필수)
    private final MyService myService;
    
    public MyApp(MyService myService, String myRepository) {
        this.myService = myService;
    }

		// 생성자를 통해 의존성 주입
    // @Autowired를 생략하기 위해서는 생성자가 하나여야 한다.
    public MyApp(MyService myService) {
        this.myService = myService;
    }

    public void run() {
        service.doSomething();
    }
    
}
  • 생성자가 하나인 경우 @Autowired 생략이 가능하다.
  • 둘중 어떤 생성자를 사용해야 하는지 Spring은 알지 못한다.

 

 

2. Setter 주입

  • Setter 메서드를 통해 의존성을 주입하는 방법.
@Component
public class MyApp {

    private MyService myService;

    // Setter 주입
    @Autowired
    public void setMyService(MyService myService) {
        this.myService = myService;
    }
    
    public void run() {
        myService.doSomething();
    }

}

선택하거나, 변경 가능한 의존관계에 사용한다.(생성자 주입은 필수 값)

// MyService가 Spring Bean으로 등록되지 않은 경우에도 주입이 가능하다.
@Autowired(required = false)
public void setMyService(MyService myService) {
    this.myService = myService;
}

// 실행 도중 인스턴스를 바꾸고자 하는 경우
// setMyService(); 메서드를 외부에서 호출하면 된다.(이런 경우는 거의 없음)

 

 

3. 필드 주입

  • 필드에 직접적으로 주입하는 방법 (가장 추천되지 않음).
@Component
public class MyApp {

    @Autowired
    private MyService myService;  // 필드에 직접 주입

    public void run() {
        myService.doSomething();
    }
    
}
  • 코드는 간결하지만 Spring이 없으면 사용할 수 없다.
    • 사용하지 않아야 한다.
// Spring을 사용하지 않는 경우 실행이 불가능하다.
public class MainV2 {
    public static void main(String[] args) {
        MyApp myApp = new MyApp();
        myApp.run();
    }
}
  • 외부에서 myService 값을 설정하거나 변경할 방법이 없다.
    • 결국 setter를 만들어야 한다.
  • 순수 Java 코드로 사용할 수 없다. = 테스트 코드 작성이 힘들다.
  • Application의 실행과 관계 없는 @SpringBootTest 테스트 코드나 Spring에서만 사용하는 @Configuration 같은 곳에서 주입할 때 주로 사용한다.

 

4. 일반 메서드 주입

  • 생성자, setter 주입으로 대체가 가능하기 때문에 사용하지 않는다.
@Component
public class MyApp {

    private MyService myService;

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

}

 

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

 

 

생성자 주입

📌 과거 setter, 필드 주입도 사용했지만 현재는 DI를 가지고 있는 대부분의 Framework가 생성자 주입 방식을 권장한다.

 

생성자 주입을 선택하는 이유

  • 불변(immutable)
    • 어떤 요리(Web Application)를 만들지 정해졌다면 이미 재료(Bean)와 의존 관계가 결정된다.
    • 객체를 생성할 때 최초 한번만 호출된다.(불변)
    • setter 주입을 사용하면 접근제어자가 public 으로 설정되어 누구나 수정할 수 있게된다.
  • 실수 방지
    • 순수 Java 코드로 사용할 때(주로 테스트 코드) 생성자의 필드를 필수로 입력하도록 만들어준다.(NPE 방지)
    • 컴파일 시점에 오류를 발생 시킨다. 즉, 실행 전에 오류를 알 수 있다.
public class MyApp {
    private MyService myService;

    public MyApp() {
        this.myService = new MyService(); // 직접 생성
    }

    public void run() {
        myService.doSomething();
    }
}

---------------------------------------

@Component // 해당 어노테이션이 MyService 객체를 bean으로 만들어준다. 즉 스프링이 객체 MyService를 저장한다.
public class MyApp {
    private final MyService myService;

    @Autowired
    public MyApp(MyService myService) {
        this.myService = myService; // 외부에서 주입 (직접 선언이 아니라 스프링에 저장된 객체 사용)
    }

    public void run() {
        myService.doSomething();
    }
}

 

  • 위 코드 참고
  • @Autowired: 스프링 컨테이너가 MyService 타입의 빈을 찾아서 자동으로 주입합니다.
  • MyApp은 MyService를 직접 생성하지 않습니다. 대신, 스프링 컨테이너가 제공한 인스턴스를 사용합니다.

 

 

 

Spring Framework에 의존하지 않아도 객체 지향 특성을 가장 잘 사용하는 방법이다.

필드에 final 은 생성자 주입 방식만 사용할 수 있다. 나머지 주입 방식들은 모두 생성 이후에 호출되어 사용할 수 없다.

 

 

@RequiredArgsConstructor

📌 실제 Web Application을 개발하면 대부분이 불변 객체이고 생성자 주입 방식을 선택하게 된다. 이런 반복되는 코드를 편안하게 작성하기 위해 Lombok에서 제공하는 Annotation 이다.

 

@RequiredArgsConstructor

  • final 필드를 모아서 생성자를 자동으로 만들어 주는 역할
  • Annotation Processor 가 동작하며 컴파일 시점에 자동으로 생성자 코드를 만들어준다.
  • 사용 방법
@Component
@RequiredArgsConstructor
public class MyApp {
		// 필드에 final 키워드 필수! 무조건 값이 있도록 만들어준다.(필수)
    private final MyService myService;
    
    // Annotation Processor가 만들어 주는 코드
    // public MyApp(MyService myService) {
    //     this.myService = myService;
    // }

    public void run() {
        myService.doSomething();
    }
    
}
  • 만약 생성자가 필요한 경우가 생긴다면, 생성자 주입 방식을 직접 선언하면 된다.
생성자를 하나 만들고 @Autowired 를 사용한 코드와 똑같이 동작한다.

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] Bean 등록  (0) 2024.12.25
[Spring] Spring의 핵심 개념  (0) 2024.12.24
[Spring] Layered Architecture  (2) 2024.12.20
[Spring] Server에서 Client로 Data를 전달하는 방법  (0) 2024.12.19
[Spring] HTTP Message Body & TEXT  (0) 2024.12.18