Using @Autowired

2023. 12. 10. 20:37Spring Framework/Spring IoC

Note: JSR 330의 @Inject  어노테이션은 이 섹션에 포함된 예제에서 Spring의 @Autowired  어노테이션을 대신하여 사용할 수 있습니다. 자세한 내용은 여기 를 참조하세요.

 

다음 예제에서 보여주는 것처럼 @Autowired 어노테이션을 생성자에 적용할 수 있습니다:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

Spring Framework 4.3부터는 타겟 빈이 단 하나의 생성자만 정의하고 있는 경우, 그러한 생성자에 @Autowired 어노테이션을 사용할 필요가 없습니다. 그러나 여러 생성자가 있고 디폴트 생성자가 없는 경우, 어느 생성자를 사용할지 컨테이너에 지시하기 위해 적어도 하나의 생성자에는 @Autowired 어노테이션이 있어야 합니다. 생성자 선택에 대한 자세한 내용은 관련 논의를 참조하세요.

다음 예제처럼, @Autowired 어노테이션을 세터 메서드에 적용할 수 있습니다:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

다음 예제에서 보여주는 것처럼, 임의의 이름을 가진 메서드와 여러 파라미터에도 어노테이션을 적용할 수 있습니다:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

다음 예제에서 보여주는 것처럼, @Autowired를 필드에도 적용할 수 있으며, 생성자와 혼합하여 사용할 수도 있습니다.

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}

Spring 프레임워크에서 @Autowired 어노테이션을 사용하여 의존성을 주입할 때 발생할 수 있는 문제점들이 있습니다"

  1. 일관된 타입 선언의 중요성:
    @Autowired 어노테이션을 사용하여 의존성을 주입할 때, 대상 컴포넌트(예: MovieCatalog, CustomerPreferenceDao)가 주입 지점에서 사용하는 타입과 일치하도록 선언되어야 합니다. 예를 들어, 만약 @Autowired 어노테이션이 특정 인터페이스 타입을 주입하기 위해 사용되었다면, 해당 인터페이스를 구현하는 클래스는 그 인터페이스 타입으로 선언되어야 합니다. 만약 타입이 일치하지 않으면, 런타임에 "일치하는 타입을 찾을 수 없음" 오류가 발생할 수 있습니다.
  2. XML로 정의된 빈 및 클래스패스 스캐닝:
    XML 설정 파일에서 정의된 빈이나 클래스패스 스캐닝을 통해 발견된 컴포넌트는 Spring 컨테이너가 미리 구체적인 타입을 알고 있습니다. 즉, Spring이 해당 컴포넌트가 어떤 구체적인 클래스를 사용하는지 알기 때문에, 주입 시 문제가 발생할 가능성이 적습니다.
  3. @Bean 팩토리 메서드의 반환 타입:
    그러나 @Bean 어노테이션을 사용하여 팩토리 메서드를 정의할 때는 주의가 필요합니다. 팩토리 메서드의 리턴 타입이 충분히 구체적이지 않다면, Spring이 주입 시 어떤 빈을 사용할지 제대로 판단하지 못할 수 있습니다. 예를 들어, 여러 인터페이스를 구현하는 컴포넌트나 특정 구현 타입으로 참조될 가능성이 있는 컴포넌트의 경우, 팩토리 메서드에서 가장 구체적인 타입(최소한 주입 지점에서 요구하는 수준)으로 리턴 타입을 선언해야 합니다. 이렇게 하면 주입 시 발생할 수 있는 오류를 방지할 수 있습니다.

아래는 위 설명에 해당하는 코드 예제입니다. 이 코드는 @Autowired 어노테이션을 사용한 의존성 주입, 그리고 @Bean 팩토리 메서드에서 리턴 타입을 구체적으로 선언하는 방법을 보여줍니다.

1. 의존성 주입에서 타입 일관성을 유지하는 예제

// 인터페이스 정의
public interface MovieCatalog {
    void displayCatalog();
}

// 인터페이스를 구현하는 클래스
public class ActionMovieCatalog implements MovieCatalog {
    @Override
    public void displayCatalog() {
        System.out.println("Displaying action movies catalog.");
    }
}

// 서비스 클래스
public class MovieService {

    // @Autowired로 의존성 주입
    @Autowired
    private MovieCatalog movieCatalog;

    public void displayCatalog() {
        movieCatalog.displayCatalog();
    }
}

2. @Bean 팩토리 메서드를 사용하여 구체적인 타입 리턴

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    // MovieCatalog 인터페이스를 반환하는 팩토리 메서드
    @Bean
    public MovieCatalog movieCatalog() {
        return new ActionMovieCatalog();  // 구체적인 구현체를 반환
    }

    // MovieService 빈을 설정하는 팩토리 메서드
    @Bean
    public MovieService movieService() {
        return new MovieService();
    }
}

3. @Autowired@Bean을 사용한 애플리케이션 실행 예제

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Application {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // MovieService 빈을 가져와서 메서드 호출
        MovieService movieService = context.getBean(MovieService.class);
        movieService.displayCatalog();
    }
}

설명

  • MovieCatalog 인터페이스와 이를 구현한 ActionMovieCatalog 클래스가 있습니다.
  • MovieService 클래스에서는 @Autowired 어노테이션을 사용하여 MovieCatalog 타입의 빈을 주입받습니다.
  • AppConfig 클래스는 @Configuration을 사용하여 Spring 설정 클래스로 정의되었으며, @Bean 어노테이션을 통해 MovieCatalogMovieService 빈을 생성합니다. 이때, movieCatalog() 메서드는 구체적인 구현체인 ActionMovieCatalog를 반환하도록 설정되었습니다.

이 예제는 Spring에서 의존성 주입이 어떻게 이루어지는지, 그리고 @Bean 팩토리 메서드를 사용할 때 타입 일관성을 유지하는 방법을 보여줍니다.

Spring 4.3부터 @Autowired는 주입 중인 빈에 대한 자기 참조(self reference)도 고려합니다(즉, 현재 주입 중인 빈에 대한 참조). 하지만 자기 주입은 최후의 수단으로 사용해야 한다는 점을 유의하세요. 실제로, 자기 참조는 마지막 수단으로만 사용해야 합니다(예: 빈의 트랜잭셔널 프록시를 통해 같은 인스턴스의 다른 메서드를 호출할 때). 이러한 상황에서는 영향을 받는 메서드를 별도의 위임 빈(delegate bean)으로 분리하는 것을 고려하세요.

자기 참조 사용한 예제

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

@Service
public class MyService {

    @Autowired
    private MyService self; // 자기 참조 주입

    // 메인 서비스 메서드
    public void performService() {
        System.out.println("Main service logic execution started.");

        // 비즈니스 로직 실행
        processLogic();

        // 트랜잭션이 필요한 작업 실행
        self.performTransactionalTask();

        System.out.println("Main service logic execution completed.");
    }

    // 일반 비즈니스 로직
    public void processLogic() {
        System.out.println("Processing business logic.");
    }

    // 트랜잭션이 필요한 메서드
    @Transactional
    public void performTransactionalTask() {
        System.out.println("Performing transactional task.");
    }
}

위임 빈을 사용한 개선된 예제

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

// 트랜잭션이 필요한 작업을 처리하는 위임 빈
@Service
public class TransactionalTaskService {

    @Transactional
    public void performTransactionalTask() {
        System.out.println("Performing transactional task.");
    }
}

@Service
public class MyService {

    @Autowired
    private TransactionalTaskService transactionalTaskService; // 위임 빈 주입

    // 메인 서비스 메서드
    public void performService() {
        System.out.println("Main service logic execution started.");

        // 비즈니스 로직 실행
        processLogic();

        // 위임 빈을 통해 트랜잭션 작업 실행
        transactionalTaskService.performTransactionalTask();

        System.out.println("Main service logic execution completed.");
    }

    // 일반 비즈니스 로직
    public void processLogic() {
        System.out.println("Processing business logic.");
    }
}

다음 예제에서 보여주는 것처럼, 특정 타입의 배열을 기대하는 필드나 메서드에 @Autowired 어노테이션을 추가하여, Spring이 ApplicationContext에서 해당 타입의 모든 빈을 제공하도록 지시할 수도 있습니다.

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

다음 예제에서 보여주는 것처럼, 타입이 지정된 컬렉션에도 동일하게 적용됩니다.

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

타겟 빈을 배열이나 리스트에서 특정 순서로 정렬하려면, 해당 빈이 org.springframework.core.Ordered 인터페이스를 구현하거나, @Order 또는 표준 @Priority 어노테이션을 사용할 수 있습니다. 그렇지 않으면, 빈의 순서는 컨테이너에서 해당 빈 정의가 등록된 순서를 따릅니다.
@Order 어노테이션은 타겟 클래스 레벨이나 @Bean 메서드에 선언할 수 있으며, 이는 개별 빈 정의에 적용될 수 있습니다(동일한 빈 클래스를 사용하는 여러 정의의 경우). @Order 값은 주입 지점에서 우선순위에 영향을 줄 수 있지만, 싱글톤의 시작 순서에는 영향을 주지 않음을 유의해야 합니다. 시작 순서는 의존 관계 및 @DependsOn 선언에 의해 결정되는 별개의 문제입니다.
또한, @Order 어노테이션이 설정 클래스에 있으면, 전체 설정 클래스 집합 내에서 평가 순서에만 영향을 미칩니다. 이러한 설정 레벨의 순서 값은 구성 클래스 내의 @Bean 메서드에는 전혀 영향을 미치지 않습니다. 빈 레벨에서의 순서를 지정하려면, 각각의 @Bean 메서드가 해당 빈 타입에 대한 다중 매치 집합 내에서 적용될 @Order 어노테이션을 가져야 합니다(팩토리 메서드에서 리턴된 경우).
표준 jakarta.annotation.Priority 어노테이션은 메서드에 선언할 수 없기 때문에 @Bean 레벨에서 사용할 수 없다는 점을 유의하세요. 그 의미는 @Order 값을 @Primary와 조합하여 각 타입에 대해 단일 빈에 적용함으로써 모델링할 수 있습니다.

타입이 지정된 Map 인스턴스도 예상되는 키 타입이 String이면 자동 주입될 수 있습니다. Map의 값에는 예상되는 타입의 모든 빈이 포함되고, 키에는 해당 빈 이름이 포함됩니다. 다음 예제에서 보여주는 것처럼 사용할 수 있습니다.

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}

디폴트로, 자동 주입(autowiring)은 주어진 주입 지점에 적합한 빈 후보가 없을 때 실패합니다. 배열, 컬렉션 또는 맵이 선언된 경우, 적어도 하나의 일치하는 엘리먼트가 있어야 합니다.

디폴트 동작은 어노테이션이 달린 메서드와 필드를 필수 의존성으로 처리합니다. 이 동작은 다음 예제에서 설명한 것처럼 변경할 수 있으며, @Autowiredrequired 속성을 false로 설정하여 프레임워크가 만족되지 않는 주입 지점을 건너뛸 수 있도록 할 수 있습니다.

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}

필수적이지 않은 메서드는 해당 의존성(또는 여러 아규먼트가 있는 경우 그 중 하나의 의존성)이 사용 가능하지 않으면 호출되지 않습니다. 필수적이지 않은 필드는 그러한 경우 전혀 채워지지 않으며, 디폴트 값이 그대로 유지됩니다.
다시 말해, required 속성을 false로 설정하면 해당 속성이 자동 주입에서 선택 사항임을 의미하며, 주입이 불가능한 경우 해당 속성은 무시됩니다. 이를 통해 속성에 디폴트 값을 할당하고, 의존성 주입을 통해 선택적으로 이를 덮어쓸 수 있게 됩니다.

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

@Component
public class SimpleMovieLister {

    // 필수적이지 않은 의존성 (디폴트 값이 사용될 수 있음)
    @Autowired(required = false)
    private MovieFinder movieFinder = new DefaultMovieFinder();  // 디폴트 값으로 설정된 MovieFinder

    public void listMovies() {
        movieFinder.findMovies();  // MovieFinder가 주입되지 않으면 기본 값이 사용됨
    }
}

주입된 생성자와 팩토리 메서드 아규먼트는 특별한 경우입니다. 이는 Spring의 생성자 해석 알고리즘 덕분에 @Autowired의 required 속성이 약간 다른 의미를 가지기 때문입니다. 다중 생성자를 처리할 가능성이 있는 상황에서, 생성자와 팩토리 메서드 아규먼트는 기본적으로 필수이지만, 단일 생성자 시나리오에서는 몇 가지 특별한 규칙이 적용됩니다. 예를 들어, 배열, 컬렉션, 맵과 같은 다중 요소 주입 지점이 일치하는 빈이 없을 경우 빈 인스턴스로 해결됩니다. 이러한 방식은 모든 의존성을 고유한 다중 아규먼트 생성자에서 선언할 수 있는 일반적인 구현 패턴을 허용하며, 예를 들어 @Autowired 어노테이션 없이 단일 public 생성자로 선언할 수 있습니다.

import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class MovieRecommender {

    private final List<MovieCatalog> movieCatalogs;

    // 단일 public 생성자 (여기서 @Autowired는 필요하지 않음)
    public MovieRecommender(List<MovieCatalog> movieCatalogs) {
        // 빈 리스트로 자동 주입될 수 있음
        this.movieCatalogs = movieCatalogs;
    }

    public void recommendMovies() {
        if (movieCatalogs.isEmpty()) {
            System.out.println("No movie catalogs available.");
        } else {
            for (MovieCatalog catalog : movieCatalogs) {
                System.out.println("Recommending from: " + catalog.getCatalogName());
                catalog.displayMovies();
            }
        }
    }
}

주어진 빈 클래스의 생성자 중 하나만 @Autowired 어노테이션을 required=true로 선언할 수 있으며, 이를 통해 해당 생성자가 Spring 빈으로 사용될 때 자동 주입 대상임을 나타냅니다. 결과적으로, required 속성을 기본 값인 true로 남겨두면 하나의 생성자만 @Autowired로 어노테이션될 수 있습니다. 여러 생성자가 어노테이션을 선언하면, 이들은 모두 required=false로 설정해야 자동 주입의 후보로 간주됩니다(이는 XML에서 autowire=constructor와 유사합니다). Spring 컨테이너에서 일치하는 빈으로 충족할 수 있는 의존성이 가장 많은 생성자가 선택됩니다. 만약 후보 생성자 중 어느 것도 충족할 수 없다면, primary/default 생성자(존재하는 경우)가 사용됩니다.

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

@Component
public class MovieRecommender {

    private final MovieCatalog actionMovieCatalog;
    private final MovieCatalog comedyMovieCatalog;

    // 첫 번째 생성자: required=false로 설정 (자동 주입 후보로 간주됨)
    @Autowired(required = false)
    public MovieRecommender(MovieCatalog actionMovieCatalog) {
        this.actionMovieCatalog = actionMovieCatalog;
        this.comedyMovieCatalog = null;
        System.out.println("Single parameter constructor called");
    }

    // 두 번째 생성자: required=true로 설정된 생성자 (기본적으로 선택됨)
    @Autowired
    public MovieRecommender(MovieCatalog actionMovieCatalog, MovieCatalog comedyMovieCatalog) {
        this.actionMovieCatalog = actionMovieCatalog;
        this.comedyMovieCatalog = comedyMovieCatalog;
        System.out.println("Two parameter constructor called");
    }

    public void recommendMovies() {
        if (actionMovieCatalog != null) {
            System.out.println("Recommending from: " + actionMovieCatalog.getCatalogName());
            actionMovieCatalog.displayMovies();
        }

        if (comedyMovieCatalog != null) {
            System.out.println("Recommending from: " + comedyMovieCatalog.getCatalogName());
            comedyMovieCatalog.displayMovies();
        }
    }
}

마찬가지로, 클래스가 여러 개의 생성자를 선언했지만 어느 것도 @Autowired로 어노테이션되지 않은 경우, primary/default 생성자(존재하는 경우)가 사용됩니다. 클래스가 단 하나의 생성자를 선언한 경우, 어노테이션이 없어도 항상 해당 생성자가 사용됩니다. 어노테이션된 생성자가 반드시 public일 필요는 없다는 점도 유의해야 합니다.

import org.springframework.stereotype.Component;

@Component
public class MovieRecommender {

    private final MovieCatalog actionMovieCatalog;
    private final MovieCatalog comedyMovieCatalog;

    // 두 개의 생성자가 선언됨, 하지만 어노테이션 없음
    public MovieRecommender(MovieCatalog actionMovieCatalog) {
        this.actionMovieCatalog = actionMovieCatalog;
        this.comedyMovieCatalog = null;
        System.out.println("Single parameter constructor called");
    }

    // 주 생성자로 사용될 생성자
    public MovieRecommender(MovieCatalog actionMovieCatalog, MovieCatalog comedyMovieCatalog) {
        this.actionMovieCatalog = actionMovieCatalog;
        this.comedyMovieCatalog = comedyMovieCatalog;
        System.out.println("Two parameter constructor called");
    }

    public void recommendMovies() {
        if (actionMovieCatalog != null) {
            System.out.println("Recommending from: " + actionMovieCatalog.getCatalogName());
            actionMovieCatalog.displayMovies();
        }

        if (comedyMovieCatalog != null) {
            System.out.println("Recommending from: " + comedyMovieCatalog.getCatalogName());
            comedyMovieCatalog.displayMovies();
        }
    }
}

대안으로, Java 8의 java.util.Optional을 사용하여 특정 의존성이 필수적이지 않음을 표현할 수 있습니다. 다음 예제가 이를 보여줍니다.

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}

// 
// Optional.of(new MovieRecommender());

Spring Framework 5.0부터는 @Nullable 어노테이션(모든 패키지의 모든 종류, 예: JSR-305의 javax.annotation.Nullable)을 사용하거나 Kotlin에서 기본 제공하는 null-safety 지원을 활용할 수도 있습니다.

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

@Service
public class MovieService {

    // 영화 제목을 받아서 감독 이름을 반환하는 메서드
    @Nullable
    public String findDirectorByTitle(@Nullable String title) {
        // null 체크
        if (title == null) {
            System.out.println("Movie title is null.");
            return null; // 제목이 null인 경우 null 반환
        }

        // 간단한 예로 영화 제목이 "Inception"이면 감독 이름을 반환
        if ("Inception".equals(title)) {
            return "Christopher Nolan";
        } else {
            return null; // 일치하는 영화가 없으면 null 반환
        }
    }
}

다음과 같이 @Autowired를 사용할 수 있는 인터페이스들은 잘 알려진 해결 가능한 의존성-별도 설정 없이도 Spring에서 자동으로 주입-들입니다: BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, 그리고 MessageSource. ConfigurableApplicationContextResourcePatternResolver와 같은 이러한 인터페이스들과 그 확장 인터페이스들은 특별한 설정 없이 자동으로 해결됩니다. 다음 예제는 ApplicationContext 객체를 자동 주입하는 예시입니다.

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

@Autowired, @Inject, @Value, 그리고 @Resource 어노테이션은 Spring의 BeanPostProcessor 구현체들에 의해 처리됩니다. 이는 BeanPostProcessorBeanFactoryPostProcessor 타입 내에서 이러한 어노테이션을 사용할 수 없음을 의미합니다. 이러한 타입들은 XML이나 Spring의 @Bean 메서드를 사용해 명시적으로 설정해야 합니다.

'Spring Framework > Spring IoC' 카테고리의 다른 글

Dependency Injection  (0) 2024.06.11
Bean Overview  (0) 2024.06.11
Dependencies and Configuration in Detail  (0) 2023.12.10
Fine-tuning Annotation-based Autowiring with @Primary  (0) 2023.07.08
Dependency Injection of Spring Framework  (0) 2023.05.01