使用 @Configuration 註解

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

注入 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 方法在 @Configuration 類別中宣告時,此宣告 Bean 間依賴關係的方法才有效。您無法使用純 @Component 類別宣告 Bean 間的依賴關係。

查找方法注入

如先前所述,查找方法注入 是一個進階功能,您應該很少使用。在單例作用域的 Bean 依賴於原型作用域的 Bean 的情況下,它很有用。針對此類型的組態使用 Java 提供了一種實作此模式的自然方法。以下範例示範如何使用查找方法注入

  • 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 類別上)或透過使用 @Configuration(proxyBeanMethods = false) 註解您的組態類別來宣告您的 @Bean 方法。然後,@Bean 方法之間的跨方法呼叫不會被攔截,因此您必須完全依賴建構子或方法層級的依賴注入。