使用 @Configuration 註解

@Configuration 是一個類級別註解,指示該物件是 Bean 定義的來源。@Configuration 類透過 @Bean 註解的方法宣告 Bean。在 @Configuration 類上呼叫 @Bean 方法也可以用來定義 Bean 之間的依賴關係。關於通用介紹,請參閱基本概念:@Bean@Configuration

注入 Bean 之間的依賴關係

當 Bean 之間存在依賴關係時,表達這種依賴就像一個 Bean 方法呼叫另一個 Bean 方法一樣簡單,示例如下

  • Java

  • Kotlin

@Configuration
public class AppConfig {

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

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

	@Bean
	fun beanOne() = BeanOne(beanTwo())

	@Bean
	fun beanTwo() = BeanTwo()
}

在前面的示例中,beanOne 透過建構函式注入獲取了 beanTwo 的引用。

這種宣告 Bean 之間依賴關係的方法僅在 @Bean 方法宣告在 @Configuration 類內部時有效。你不能透過普通的 @Component 類來宣告 Bean 之間的依賴關係。

Lookup 方法注入

如前所述,lookup 方法注入是一個高階特性,應很少使用。當單例作用域的 Bean 依賴於原型作用域的 Bean 時,此特性會非常有用。使用 Java 進行此類配置為實現此模式提供了一種自然的方式。下面的示例展示瞭如何使用 lookup 方法注入

  • Java

  • Kotlin

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();
}
abstract class CommandManager {
	fun process(commandState: Any): Any {
		// grab a new instance of the appropriate Command interface
		val 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 fun createCommand(): Command
}

透過使用 Java 配置,你可以建立 CommandManager 的一個子類,其中抽象方法 createCommand() 被重寫,以便它查詢新的(原型)命令物件。以下示例展示瞭如何實現這一點

  • Java

  • Kotlin

@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();
		}
	}
}
@Bean
@Scope("prototype")
fun asyncCommand(): AsyncCommand {
	val command = AsyncCommand()
	// inject dependencies here as required
	return command
}

@Bean
fun commandManager(): CommandManager {
	// return new anonymous implementation of CommandManager with createCommand()
	// overridden to return a new prototype Command object
	return object : CommandManager() {
		override fun createCommand(): Command {
			return asyncCommand()
		}
	}
}

關於基於 Java 的配置如何工作的更多內部資訊

考慮以下示例,它展示了一個 @Bean 註解的方法被呼叫兩次

  • Java

  • Kotlin

@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();
	}
}
@Configuration
class AppConfig {

	@Bean
	fun clientService1(): ClientService {
		return ClientServiceImpl().apply {
			clientDao = clientDao()
		}
	}

	@Bean
	fun clientService2(): ClientService {
		return ClientServiceImpl().apply {
			clientDao = clientDao()
		}
	}

	@Bean
	fun clientDao(): ClientDao {
		return ClientDaoImpl()
	}
}

clientDao()clientService1() 中被呼叫了一次,在 clientService2() 中也被呼叫了一次。由於此方法建立並返回了 ClientDaoImpl 的一個新例項,你通常會期望有兩個例項(每個服務一個)。這無疑是個問題:在 Spring 中,例項化的 Bean 預設是 singleton 作用域。這就是“魔術”所在:所有的 @Configuration 類在啟動時都會被 CGLIB 建立子類。在這個子類中,子方法會在呼叫父方法建立新例項之前,首先檢查容器中是否有任何快取(作用域)的 Bean。

根據你的 Bean 的作用域不同,行為也可能不同。我們這裡討論的是單例。

你無需將 CGLIB 新增到你的類路徑中,因為 CGLIB 類已被重新打包到 org.springframework.cglib 包下,並直接包含在 spring-core JAR 中。

由於 CGLIB 在啟動時動態新增功能,存在一些限制。特別是,配置類不能是 final 的。然而,配置類允許使用任何建構函式,包括使用 @Autowired 或用於預設注入的單個非預設建構函式宣告。

如果你希望避免 CGLIB 施加的任何限制,可以考慮在非 @Configuration 類(例如,普通的 @Component 類)上宣告你的 @Bean 方法,或者透過使用 @Configuration(proxyBeanMethods = false) 註解你的配置類。這樣,@Bean 方法之間的跨方法呼叫將不會被攔截,因此你必須完全依賴建構函式或方法級別的依賴注入。