使用@Configuration注解

@Configuration是一个类级别的注解,表示一个对象是bean定义的来源。通过@Bean注解的方法在@Configuration类中声明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之间的依赖关系的这种方法仅在@Bean方法在@Configuration类中声明时有效。您不能通过使用普通的@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-based配置内部工作原理的更多信息

考虑以下示例,展示了一个被@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施加的限制,请考虑将您的@Bean方法声明在非@Configuration类上(例如,而是在普通的@Component类上)或通过在配置类上注解@Configuration(proxyBeanMethods = false)。那么@Bean方法之间的跨方法调用将不会被拦截,因此您必须完全依赖构造函数或方法级别的依赖注入。