스프링 프레임워크/IoC (Inversion of Control)

Using the @Configuration annotation

GLaDiDos 2024. 11. 19. 16:01

https://sundaland.tistory.com/480

 

▶ Using the @Configuration annotation

@Configuration은 객체가 빈 정의의 소스임을 나타내는 클래스 레벨 어노테이션이다. @Configuration 클래스는 @Bean 어노테이션이 달린 메서드를 통해 빈을 선언한다. @Configuration 클래스에서 @Bean 메서드에 대한 호출은 빈 간 종속성을 정의하는 데에도 사용할 수 있다.

 

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 메소드 간의 교차 호출이 인터셉트되지 않으므로 생성자나 메소드 수준의 의존성 주입에만 의존해야 한다.