티스토리 뷰

Web/Spring

[Spring] @Autowired와 의존성 주입

Jaehee Jeon 2023. 4. 15. 15:57
반응형

목차

1. @Autowired 어노테이션

2. @Autowired를 통한 의존성 주입 방법

3. 생성자 주입을 사용하는 이유

4. @Primary 혹은 @Qualifier를 통해 주입될 구현체 명시하기

 

 


 

@Autowired 어노테이션

 

 스프링 컨테이너에 어노테이션을 통해 빈을 등록하는 방법은 크게 두 가지다.

  1. @Bean 어노테이션을 통한 빈 등록 (메서드 레벨)
  2. @Component 어노테이션을 통한 빈 등록 (클래스 레벨)

 @Bean의 경우 메서드에 붙여 해당 메서드를 통해 반환되는 객체를 Bean으로 관리할 때 사용한다. 이와 달리 @Component는 클래스에 붙여 해당 클래스 타입을 기반으로 빈을 관리하도록 한다. @Repository, @Service, @Controller 등의 어노테이션들은 @Component를 이미 포함한다.

 

 @Component를 통해 등록된 빈은 다른 곳에서 @Autowired를 사용해 의존성을 주입할 수 있다.

 

@Component
public class A {
    . . .
}

@Component
public class B {

    @Autowired
    private A a; // 컨테이너에서 A 타입의 빈을 찾아서 의존성을 주입한다
    . . .
    
    }
}

 

 


 

@Autowired를 통한 의존성 주입 방법

 

 @Autowired를 통해 의존성을 주입하는 방법은 크게 세 가지다

  1. 생성자 주입
  2. 수정자(Setter) 주입, 혹은 메서드 주입
  3. 필드 주입

각각의 사용 방법은 아래와 같다.

 

 

1. 생성자 주입

 

 클래스의 생성자에 @Autowired를 붙여 해당 클래스 타입의 필드들을 찾아 등록하도록 한다.

 아래의 경우 등록된 Class2의 빈을 알아서 찾아와 주입한다. 생성자가 하나일 경우 어노테이션을 붙이지 않아도 해당 생성자를 자동 주입의 대상으로 인식한다.

 

@Component
public class Class1 {

    private final Class2 field;
    
    // @Autowired (Optional)
    public Class1(final Class2 field) {
        this.field = field
    }
}

 

 스프링에서 가장 권장되는 방법이며 이유는 아래에 설명한다.

 

 

2. 수정자(Setter) 주입, 메서드 주입

 

 메서드에 @Autowired를 붙여 해당 클래스 타입의 빈을 찾아와 의존성을 주입한다.

 

@Component
public class Class1 {

    private Class2 field;

    @Autowired
    public void setField(Class2 field) {
        this.field = field
    }
}

 

 생성 시점 이후에 메서드를 호출하여 의존성을 변경할 수 있으나 필드가 외부에서 변경될 수 있다는 점과 의존성이 필요한 시점에 주입되지 않을 수 있다는 위험이 존재한다.

 

 

3. 필드 주입

 

 가장 간단하게 주입하는 방법으로 필드에 @Autowired를 붙여 의존성을 주입하는 방법이다.

 

@Component
public class Class1 {

    @Autowired
    private Class1 field;
    
}

 

 코드는 가장 간결하나 수정자 주입과 마찬가지로 필드가 final이 아니므로 변경될 여지가 있다.

 메서드와 필드 주입 시의 메서드, 필드의 접근제어자는 private도 가능하다.

 

 


 

생성자 주입을 사용하는 이유

 

 생성자 주입은 수정자 주입, 필드 주입보다 여러 장점을 가진다.

  1. 필수적인 의존성의 주입 시점을 보장할 수 있다.
  2. 필드를 final로 선언이 가능하여 외부에서의 변경 가능성을 낮춘다.
  3. 의존성 주입이 한 곳(생성자)에서만 일어나므로 필요한 의존성을 한곳에서 모아서 확인할 수 있다.
  4. 순수 자바 코드로의 테스트가 용이하다.
  5. Bean의 순환 참조 문제를 방지할 수 있다.

아래는 4번과 5번 장점에 대한 예시이다.

 

 


 

생성자 주입과 순수 자바 코드로의 테스트

 

 Spring을 사용한 테스트는 비용이 크다. 간단한 빈 클래스의 기능을 확인하고자 할 때 생성자 주입을 사용했다면 아래처럼 순수 자바 테스트를 수행할 수 있다. 생성자를 통해 스프링이 의존성을 주입하는 대신 우리가 직접 의존적인 클래스의 인스턴스를 만들어 넣어줄 수 있다.

 

@Component
public class A {

    private final B fieldB;

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

class Test {
	
    public test() {
        A instance = new A(new B()); // B를 직접 만들어 넣는다
    }
}


 필드 주입 혹은 수정자 주입의 경우 이런 의존성 주입이 Spring을 통해서만 일어날 수 있으므로 Spring에 의존적인 테스트밖에 진행하지 못한다.

 

 


 

Bean 생성의 순환 참조 문제

 

 필드 주입을 사용했을 때 순환 참조가 발생하는 아래의 예시를 보자.

 

참고

스프링 자체적으로는 생성자 주입에서만 순환 참조를 방지하며 스프링 '부트' 2.6버전부터 필드 주입 또한 순환 참조를 발생시킨다. 스프링에서 발생시키는 예외와 달리 최신 버전에서 스프링 부트에서 발생시키는 예외는 BeanCurrentlyInCreationException이다.

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.6-Release-Notes#deprecations-from-spring-boot-24

 

@Component
public class A {

    @Autowired
    private B fieldB;

    private void methodA() {
        fieldB.methodB();
    }
}

@Component
public class B {

    @Autowired
    private A fieldA;

    private void methodB() {
        fieldA.methodA();
    }
}

 

 컴파일 타임에서는 아무런 문제가 없으나 런타임에 methodA 혹은 methodB를 실행시킨다면 서로를 참조하며 메서드를 계속 실행시키고 StackOverFlow가 발생, 런타임에 서버가 죽게 된다.

 

 이미 실행 중인 서버가 예상치 못하게 죽는 상황은 최악인데 이러한 상황을 생성자 주입을 통해 방지할 수 있다. 위의 필드 주입을 모두 생성자 주입으로 바꾸고 서버를 실행시키면 아래와 같이 에러를 발생시키며 중단된다.

 

 

 bean의 의존성이 순환 참조 문제를 발생시킨다는 내용이다.

 

 빈은 아래의 과정을 거쳐 사용이 가능한 상태가 된다.

 

  1. 빈 인스턴스화: 스프링 컨테이너는 해당 빈을 인스턴스화한다.
  2. 의존성 주입: 스프링 컨테이너는 해당 빈이 의존하는 다른 빈들을 찾아 의존성을 주입한다. 이때, @Autowired 어노테이션이 사용되면 해당 타입에 맞는 빈을 찾아 주입한다.
  3. 초기화 콜백: 모든 의존성이 주입된 후에는, 빈의 초기화 콜백 메서드가 호출됩니다. 이 단계에서는 빈이 필요한 모든 준비를 마친다.
  4. 사용: 초기화가 완료된 빈은 스프링 컨테이너에서 사용 가능한 상태가 된다.

 

 생성자 주입을 사용하면 생성자를 호출하고 필요한 의존성을 주입하는 과정이 일련에 일어난다. 따라서 의존성 주입이 일어난 뒤 인스턴스를 생성하므로 서로를 의존하는 경우에는 빈 등록 자체가 불가능한 것이다.

 반면 필드 주입의 경우 인스턴스를 생성한 뒤에 의존성을 연결하는데 이로 인해 순환 참조가 만들어진다.

 

 따라서 생성자 주입을 사용하면 아예 서버가 켜지지 않으므로 운영 중 서버가 꺼지는 사태를 방지할 수 있다.

 

 순환 참조를 하는 빈의 연관관계를 사용해야 한다면 여기에서 추가적인 방법들을 찾을 수 있다.

 

 


 

@Primary 혹은 @Qualifier를 통해 주입될 구현체 명시하기

 

 인터페이스를 통해 의존성 주입을 수행하는 경우 빈으로 등록된 구현체가 여러 개라면 선택이 불가능해 문제가 발생한다.

아래는 서비스 계층에서 저장소의 구현체를 주입받는 도중 문제가 발생하는 예시이다. 편의를 위해 필드 주입을 사용하였다.

 

@Repository
public class PrimaryRepository implements Repository {
    . . .
}

@Repository
public class SecondaryRepository implements Repository {
    . . .
}

@Service
public class Service {
    
    @Autowired
    private Repository repository // 어떤 구현체를 주입할 지 알 수 없다.
}

 

 

@Primary 사용

 

 @Primary를 주입될 클래스에 붙여 우선적으로 주입됨을 명시할 수 있다.

 

@Repository
@Primary
public class PrimaryRepository implements Repository {
}

 

 

@Qualifier 사용

 

@Qualifier를 주입받는 클래스에 붙여 구체 클래스를 명시, 어떤 구현체를 주입할지 알려줄 수 있다.

 

@Service
public class Service {
    
    @Autowired
    @Qualifier("PrimaryRepository")
    private Repository repository
}

 

 


 

참고자료

https://www.baeldung.com/circular-dependencies-in-spring

반응형