Using the @Configuration annotation

2024. 11. 14. 14:17Spring Framework/Spring IoC

@Configuration은 객체가 Bean Definition의 소스임을 나타내는 클래스 레벨 어노테이션입니다. @Configuration 클래스는 @Bean 어노테이션이 적용된 메서드를 통해 빈을 선언합니다. @Configuration 클래스에서 @Bean 메서드에 대한 호출은 Beans 간 종속성을 정의하는 데에도 사용할 수 있습니다. 일반적인 소개는 기본 개념: @Bean 및 @Configuration을 참조하세요.

 

Injecting Inter-bean Dependencies

빈이 서로 종속성을 가질 때, 그 종속성을 표현하는 것은 다음 예제에서 보듯이 한 빈 메서드가 다른 빈 메서드를 호출하는 것만큼 간단합니다.

@Configuration
public class AppConfig {

	@Bean
	public BeanOne beanOne() {
		return new BeanOne(beanTwo());
	}

	@Bean
	public BeanTwo beanTwo() {
		return new BeanTwo();
	}
}

 

앞의 예제에서 beanOne은 생성자 주입을 통해 beanTwo에 대한 참조를 받습니다.

이 빈 간 종속성 선언 방법은 @Bean 메서드가 @Configuration 클래스 내에서 선언될 때만 작동합니다. 일반 @Component 클래스를 사용하여 빈 간 종속성을 선언할 수 없습니다.

 

Lookup Method Injection

앞서 언급했듯이, lookup method injection은 드물게 사용해야 하는 고급 기능입니다. 싱글톤 스코프의 빈이 프로토타입 스코프의 빈에 종속되어 있는 경우에 유용합니다. 이러한 유형의 구성에 Java를 사용하면 이 패턴을 구현하는 자연스러운 수단이 제공됩니다. 다음 예에서는 lookup method injection을 사용하는 방법을 보여줍니다.

public abstract class CommandManager {
	public Object process(Object commandState) {
		// grab a new instance of the appropriate Command interface
		Command command = createCommand();
		// set the state on the (hopefully brand new) Command instance
		command.setState(commandState);
		return command.execute();
	}

	// okay... but where is the implementation of this method?
	protected abstract Command createCommand();
}

 

Java 구성을 사용하면 abstract createCommand() 메서드가 새(프로토타입) Command 클래스 객체를 찾는 방식으로 재정의되는 CommandManager의 하위 클래스를 만들 수 있습니다. 다음 예제는 이를 수행하는 방법을 보여줍니다.

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
	AsyncCommand command = new AsyncCommand();
	// inject dependencies here as required
	return command;
}

@Bean
public CommandManager commandManager() {
	// return new anonymous implementation of CommandManager with createCommand()
	// overridden to return a new prototype Command object
	return new CommandManager() {
		protected Command createCommand() {
			return asyncCommand();
		}
	}
}

// SpringLookupInjection 참조

 

Further Information About How Java-based Configuration Works Internally

다음 예제에서는 @Bean 메서드가 두 번 호출되는 것을 보여줍니다.

@Configuration
public class AppConfig {

	@Bean
	public ClientService clientService1() {
		ClientServiceImpl clientService = new ClientServiceImpl();
		clientService.setClientDao(clientDao());
		return clientService;
	}

	@Bean
	public ClientService clientService2() {
		ClientServiceImpl clientService = new ClientServiceImpl();
		clientService.setClientDao(clientDao());
		return clientService;
	}

	@Bean
	public ClientDao clientDao() {
		return new ClientDaoImpl();
	}
}

 

clientDao()는 clientService1()에서 한 번, clientService2()에서 한 번 호출되었습니다. 이 메서드는 ClientDaoImpl의 새 인스턴스를 생성하여 리턴하므로 일반적으로 두 개의 인스턴스(각 서비스당 하나씩)가 있을 것으로 예상합니다. 이는 확실히 문제가 될 것입니다. Spring에서 인스턴스화된 빈은 기본적으로 싱글톤 스코프를 갖습니다. 여기서 마법이 등장합니다. 모든 @Configuration 클래스는 시작 시 CGLIB로 서브클래싱됩니다. 서브클래스에서 자식 메서드는 부모 메서드를 호출하고 새 인스턴스를 만들기 전에 먼저 컨테이너에서 캐시된(범위가 지정된) 빈을 확인합니다.

동작은 빈의 스코프에 따라 다를 수 있습니다. 여기서는 싱글톤에 대해 이야기하고 있습니다.
CGLIB 클래스는 org.springframework.cglib 패키지로 다시 패키징되어 spring-core JAR에 직접 포함되므로 클래스 경로에 CGLIB를 추가할 필요가 없습니다.
CGLIB이 시작 시점에 동적으로 기능을 추가하기 때문에 몇 가지 제한 사항이 있습니다. 특히, 구성 클래스는 final로 선언되어서는 안 됩니다. 그러나 구성 클래스에서 생성자는 @Autowired를 사용하거나 디폴트가 아닌 생성자를 선언하여 디폴트 주입을 위한 단일 생성자를 포함하여 모든 생성자가 허용됩니다.

만약 CGLIB에 의해 부과되는 제한을 피하고 싶다면, @Bean 메소드를 non-@Configuration 클래스(예: 일반 @Component 클래스)에서 선언하거나, 구성 클래스에 @Configuration(proxyBeanMethods = false)로 애노테이션을 지정하는 것을 고려하십시오. 이 경우, @Bean 메소드 간의 교차 호출이 인터셉트되지 않으므로 생성자나 메소드 수준의 의존성 주입에만 의존해야 합니다.