2023. 12. 10. 20:37ㆍSpring 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
어노테이션을 사용하여 의존성을 주입할 때 발생할 수 있는 문제점들이 있습니다"
- 일관된 타입 선언의 중요성:
@Autowired
어노테이션을 사용하여 의존성을 주입할 때, 대상 컴포넌트(예:MovieCatalog
,CustomerPreferenceDao
)가 주입 지점에서 사용하는 타입과 일치하도록 선언되어야 합니다. 예를 들어, 만약@Autowired
어노테이션이 특정 인터페이스 타입을 주입하기 위해 사용되었다면, 해당 인터페이스를 구현하는 클래스는 그 인터페이스 타입으로 선언되어야 합니다. 만약 타입이 일치하지 않으면, 런타임에 "일치하는 타입을 찾을 수 없음" 오류가 발생할 수 있습니다. - XML로 정의된 빈 및 클래스패스 스캐닝:
XML 설정 파일에서 정의된 빈이나 클래스패스 스캐닝을 통해 발견된 컴포넌트는 Spring 컨테이너가 미리 구체적인 타입을 알고 있습니다. 즉, Spring이 해당 컴포넌트가 어떤 구체적인 클래스를 사용하는지 알기 때문에, 주입 시 문제가 발생할 가능성이 적습니다. @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
어노테이션을 통해MovieCatalog
와MovieService
빈을 생성합니다. 이때,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)은 주어진 주입 지점에 적합한 빈 후보가 없을 때 실패합니다. 배열, 컬렉션 또는 맵이 선언된 경우, 적어도 하나의 일치하는 엘리먼트가 있어야 합니다.
디폴트 동작은 어노테이션이 달린 메서드와 필드를 필수 의존성으로 처리합니다. 이 동작은 다음 예제에서 설명한 것처럼 변경할 수 있으며, @Autowired
의 required
속성을 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
. ConfigurableApplicationContext
나 ResourcePatternResolver
와 같은 이러한 인터페이스들과 그 확장 인터페이스들은 특별한 설정 없이 자동으로 해결됩니다. 다음 예제는 ApplicationContext
객체를 자동 주입하는 예시입니다.
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Autowired
,@Inject
,@Value
, 그리고@Resource
어노테이션은 Spring의BeanPostProcessor
구현체들에 의해 처리됩니다. 이는BeanPostProcessor
나BeanFactoryPostProcessor
타입 내에서 이러한 어노테이션을 사용할 수 없음을 의미합니다. 이러한 타입들은 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 |