본문 바로가기

JAVA

제어의 역전과 의존성 주입

제어의 역전 (IoC)

정의

제어의 역전 (IoC = Inversion of Control)

 

보통 일반적인 자바를 사용하면 new C() 형태로 객체를 생성해서 사용한다. 객체지향 프로그래밍언어에서 일반적으로 클래스를 사용하는 방식이다.

 

하지만 Spring에서는 위처럼 클래스의 객체를 직접 생성하는 것이 아닌 필요하다면 (type) (변수) 로 변수만 지정하면 Spring Container가 객체를 “어딘가”에서 받아와 변수에 지정해준다.

 

일반적인 클래스 객체 생성

public class A {
    b = new B();
}

 

Spring Container가 관리하는 객체 생성

public class A {
    private B b;
}

의존성 주입 (DI)

정의

의존성 주입 (DI = Dependency Injection)

 

@Autowired annotation은 Spring Container가 관리하는 Bean 객체들을 주입하는 역할을 한다.

public class A {
    // A에서 B를 주입받음
    @Autowired
    B b;
}

위의 예시에서는 A 클래스가 Spring Container가 제공하는 B객체를 받아 사용하는 것이다.

Bean을 등록하는 방법

클래스를 Bean으로 등록하는 방법

@Component  // 클래스 MyBean 빈으로 등록
public class MyBean {
}

@Component annotation을 가진 class가 Bean으로 등록되어 Spring Container에서 이 class를 관리(생성, 주입, …)한다.

@Autowired 의 주의점

여러가지 의존성 주입(IoC) 방법

1. 생성자 주입 (Constructor Injection)

생성자(Constructor)를 통해 의존 관계를 주입한다. 생성자의 호출에 따라 한번 호출이 보장되므로 주입받은 객체가 변하지 않거나, 반드시 객체의 주입이 필요한 경우에 강제하기 위해 사용한다. 어디서 어떻게 주입되는지 확인이 가능하다

@Service
public class A {
    private final B b;

    @Autowired  // 생략가능
    public A(B b) {
        this.b = b;
    }
}

 

2. 수정자 주입 (Setter Inejction)

Setter 메서드를 이용하여 의존성을 주입한다. 한번 호출을 보장하지 못한다. 주입받는 객체가 변경이 필요한 경우 사용한다.

@Service
public class A {
    private B b;

    @Autowired
    public setB(B b) {
        this.b = b;
    }
}

 

3. 필드 주입 (Field Injection)

Spring Container의 주입을 의존하는 방법이다. 코드가 간결해지지만 외부에서 접근이 불가능해진다. 테스트 코드 구성이 어려워진다

@Service
public class A {
    @Autowired
    private B b;
}

왜 생성자 주입 방식이 권장되나?

순환참조 문제

필드 주입 방식에서는 A Class 인스턴스 생성(private B b) 부분에서는 b가 null이 된다. b를 사용할 시점에서야 b에 B 객체가 주입된다.

 

즉, 아래 예시에서 A Class 인스턴스 생성 → B Class 인스턴스 생성 → B 객체 의존성 주입 → A 객체 의존성 주입 의 순서대로 진행된다. 인스턴스가 모두 생성되고 객체 의존성이 주입되므로 에러가 바로 발생하지 않는다. 이에 따라 숨겨진 에러가 존재할 수 있다.

@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private A a;
}

하지만, 생성자 주입 방식에서는 A 클래스를 생성하는 시점에 B 클래스 객체 의존성이 필요하기 때문에 바로 순환참조 오류를 불러온다.

즉, 아래 예시에서 A클래스 생성중 → A 생성자 안의 B 클래스의 객체가 필요함 → B 클래스 생성자 안의 A 클래스 객체가 필요함 → 순환오류 순서에 의해 바로 오류를 출력할 수 있다.

 @Component
 public class A {
     private final B b;

     @Autowired
     public A(B b) {
         this.b = b;
     }
 }

 @Component
 public class B {
     private final A a;

     @Autowired
     public B(A a) {
         this.a = a;
     }
 }

요약하면,

  1. 의존성이 시각적으로 확실히 보여 구조가 쉽게 보인다
  2. 객체 생성자 안에 모든 의존성이 주입되므로 객체 불변성이 보장된다
  3. 1번에 의해 의존성이 확실히 보이므로 Unit Test가 쉬워진다

final 키워드

final 키워드로 인해 명시적으로도 코드적으로도 불변임을 확실시할 수 있다

반응형

'JAVA' 카테고리의 다른 글

Spring Security 개념 정리  (0) 2025.07.21
JAVA의 GC  (2) 2025.07.21