Loading...
Spring Framework Reference Documentation 7.0.2의 Using the @Configuration annotation의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
@Configuration annotation@Configuration은 객체가 bean definition의 소스임을 나타내는 class-level 어노테이션입니다. @Configuration 클래스는 @Bean 어노테이션이 붙은 메서드를 통해 bean을 선언합니다. @Configuration 클래스의 @Bean 메서드 호출은 inter-bean 의존성을 정의하는 데에도 사용할 수 있습니다. 일반적인 소개는 Basic Concepts: @Bean and @Configuration를 참고하십시오.
bean들이 서로에 대한 의존성을 가질 때, 다음 예제에서 보듯이 하나의 bean 메서드가 다른 bean 메서드를 호출하도록 하는 것만큼 간단하게 그 의존성을 표현할 수 있습니다:
1@Configuration 2public class AppConfig { 3 4 @Bean 5 public BeanOne beanOne() { 6 return new BeanOne(beanTwo()); 7 } 8 9 @Bean 10 public BeanTwo beanTwo() { 11 return new BeanTwo(); 12 } 13}
1@Configuration 2class AppConfig { 3 4 @Bean 5 fun beanOne() = BeanOne(beanTwo()) 6 7 @Bean 8 fun beanTwo() = BeanTwo() 9}
앞의 예제에서 beanOne은 생성자 주입을 통해 beanTwo에 대한 참조를 받습니다.
이러한 방식의 inter-bean 의존성 선언은
@Bean메서드가@Configuration클래스 안에 선언된 경우에만 동작합니다. 일반@Component클래스를 사용해서는 inter-bean 의존성을 선언할 수 없습니다.
앞서 언급했듯이, lookup method injection은 드물게만 사용해야 하는 고급 기능입니다. 이는 singleton scope bean이 prototype scope bean에 대한 의존성을 가질 때 유용합니다. 이러한 타입의 설정에 Java를 사용하면 이 패턴을 구현하기 위한 자연스러운 수단을 제공합니다.
다음 예제는 lookup method injection을 사용하는 방법을 보여 줍니다:
1public abstract class CommandManager { 2 public Object process(Object commandState) { 3 // grab a new instance of the appropriate Command interface 4 Command command = createCommand(); 5 // set the state on the (hopefully brand new) Command instance 6 command.setState(commandState); 7 return command.execute(); 8 } 9 10 // okay... but where is the implementation of this method? 11 protected abstract Command createCommand(); 12}
1abstract class CommandManager { 2 fun process(commandState: Any): Any { 3 // grab a new instance of the appropriate Command interface 4 val command = createCommand() 5 // set the state on the (hopefully brand new) Command instance 6 command.setState(commandState) 7 return command.execute() 8 } 9 10 // okay... but where is the implementation of this method? 11 protected abstract fun createCommand(): Command 12}
Java 설정을 사용하면 abstract createCommand() 메서드가 새로운 (prototype) command 객체를 lookup하는 방식으로 override된 CommandManager의 서브클래스를 만들 수 있습니다. 다음 예제는 이를 수행하는 방법을 보여 줍니다:
1@Bean 2@Scope("prototype") 3public AsyncCommand asyncCommand() { 4 AsyncCommand command = new AsyncCommand(); 5 // inject dependencies here as required 6 return command; 7} 8 9@Bean 10public CommandManager commandManager() { 11 // return new anonymous implementation of CommandManager with createCommand() 12 // overridden to return a new prototype Command object 13 return new CommandManager() { 14 protected Command createCommand() { 15 return asyncCommand(); 16 } 17 }; 18}
1@Bean 2@Scope("prototype") 3fun asyncCommand(): AsyncCommand { 4 val command = AsyncCommand() 5 // inject dependencies here as required 6 return command 7} 8 9@Bean 10fun commandManager(): CommandManager { 11 // return new anonymous implementation of CommandManager with createCommand() 12 // overridden to return a new prototype Command object 13 return object : CommandManager() { 14 override fun createCommand(): Command { 15 return asyncCommand() 16 } 17 } 18}
다음 예제를 고려해 보십시오. 이 예제는 @Bean 어노테이션이 붙은 메서드가 두 번 호출되는 것을 보여 줍니다:
1@Configuration 2public class AppConfig { 3 4 @Bean 5 public ClientService clientService1() { 6 ClientServiceImpl clientService = new ClientServiceImpl(); 7 clientService.setClientDao(clientDao()); 8 return clientService; 9 } 10 11 @Bean 12 public ClientService clientService2() { 13 ClientServiceImpl clientService = new ClientServiceImpl(); 14 clientService.setClientDao(clientDao()); 15 return clientService; 16 } 17 18 @Bean 19 public ClientDao clientDao() { 20 return new ClientDaoImpl(); 21 } 22}
1@Configuration 2class AppConfig { 3 4 @Bean 5 fun clientService1(): ClientService { 6 return ClientServiceImpl().apply { 7 clientDao = clientDao() 8 } 9 } 10 11 @Bean 12 fun clientService2(): ClientService { 13 return ClientServiceImpl().apply { 14 clientDao = clientDao() 15 } 16 } 17 18 @Bean 19 fun clientDao(): ClientDao { 20 return ClientDaoImpl() 21 } 22}
clientDao()는 clientService1()에서 한 번, clientService2()에서 한 번 호출되었습니다. 이 메서드가 ClientDaoImpl의 새로운 인스턴스를 생성해서 반환하므로, 일반적으로 두 개의 인스턴스(각 서비스마다 하나씩)가 있다고 예상할 것입니다.
이는 분명히 문제가 될 수 있습니다. Spring에서 생성된 bean은 기본적으로 singleton scope를 갖습니다. 여기서 매직이 발생합니다. 모든 @Configuration 클래스는 시작 시점에 CGLIB으로 서브클래싱됩니다. 서브클래스에서 child 메서드는 parent 메서드를 호출하여 새로운 인스턴스를 생성하기 전에 먼저 컨테이너에서 캐시된(또는 스코프된) bean이 있는지 확인합니다.
bean의 scope에 따라 동작은 달라질 수 있습니다. 여기서는 singleton에 대해 이야기하고 있습니다.
CGLIB 클래스는
org.springframework.cglib패키지 아래에 재패키징되어 직접spring-coreJAR 안에 포함되어 있으므로, classpath에 CGLIB를 추가할 필요는 없습니다.
CGLIB이 시작 시점에 동적으로 기능을 추가한다는 사실로 인해 몇 가지 제약이 있습니다. 특히 설정 클래스는 final이면 안 됩니다. 그러나 설정 클래스에서는
@Autowired사용을 포함하여 어떤 생성자든 허용되며, 기본 주입을 위한 single non-default 생성자 선언도 허용됩니다.<br>CGLIB이 부과하는 제약을 피하고 싶다면,@Bean메서드를 non-@Configuration클래스(예를 들어, plain@Component클래스)에 선언하거나, 설정 클래스에@Configuration(proxyBeanMethods = false)를 어노테이션으로 추가하는 것을 고려하십시오. 이 경우@Bean메서드 사이의 cross-method 호출은 가로채지지 않으므로, 생성자나 메서드 레벨에서의 의존성 주입에만 전적으로 의존해야 합니다.
Using the @Bean Annotation
Composing Java-based Configurations