此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

编写基于 Java 的配置

Spring 基于 Java 的配置功能允许您编写注释,这可以减少配置的复杂性。spring-doc.cadn.net.cn

使用@Import注解

就像<import/>元素在 Spring XML 文件中使用,以帮助模块化配置,则@Import注释允许加载@Bean定义来自另一个配置类,如以下示例所示:spring-doc.cadn.net.cn

@Configuration
public class ConfigA {

	@Bean
	public A a() {
		return new A();
	}
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

	@Bean
	public B b() {
		return new B();
	}
}
@Configuration
class ConfigA {

	@Bean
	fun a() = A()
}

@Configuration
@Import(ConfigA::class)
class ConfigB {

	@Bean
	fun b() = B()
}

现在,不需要同时指定两者ConfigA.classConfigB.class什么时候 实例化上下文,仅ConfigB需要显式提供,如以下示例所示:spring-doc.cadn.net.cn

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

	// now both beans A and B will be available...
	A a = ctx.getBean(A.class);
	B b = ctx.getBean(B.class);
}
import org.springframework.beans.factory.getBean

fun main() {
	val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)

	// now both beans A and B will be available...
	val a = ctx.getBean<A>()
	val b = ctx.getBean<B>()
}

这种方法简化了容器实例化,因为只需要处理一个类,而不是要求您记住潜在的大量@Configuration施工期间的课程。spring-doc.cadn.net.cn

从 Spring Framework 4.2 开始,@Import还支持对常规组件的引用类,类似于AnnotationConfigApplicationContext.register方法。 如果您想避免组件扫描,这特别有用,方法是使用一些配置类作为入口点来显式定义所有组件。

在导入的@Bean定义

前面的示例有效,但很简单。在大多数实际场景中,bean 具有跨配置类相互依赖。使用 XML 时,这不是问题,因为不涉及编译器,并且您可以声明ref="someBean"并相信 Spring 会在容器初始化期间解决问题。使用@Configuration类,Java 编译器对配置模型施加约束,因为对其他 bean 的引用必须是有效的 Java 语法。spring-doc.cadn.net.cn

幸运的是,解决这个问题很简单。正如我们已经讨论过的, 一个@Bean方法可以具有任意数量的描述 bean 的参数 依赖。 考虑以下更现实的场景,其中包含几个@Configuration类,每个类都取决于其他 bean 中声明的 bean:spring-doc.cadn.net.cn

@Configuration
public class ServiceConfig {

	@Bean
	public TransferService transferService(AccountRepository accountRepository) {
		return new TransferServiceImpl(accountRepository);
	}
}

@Configuration
public class RepositoryConfig {

	@Bean
	public AccountRepository accountRepository(DataSource dataSource) {
		return new JdbcAccountRepository(dataSource);
	}
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

	@Bean
	public DataSource dataSource() {
		// return new DataSource
	}
}

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
	// everything wires up across configuration classes...
	TransferService transferService = ctx.getBean(TransferService.class);
	transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

	@Bean
	fun transferService(accountRepository: AccountRepository): TransferService {
		return TransferServiceImpl(accountRepository)
	}
}

@Configuration
class RepositoryConfig {

	@Bean
	fun accountRepository(dataSource: DataSource): AccountRepository {
		return JdbcAccountRepository(dataSource)
	}
}

@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {

	@Bean
	fun dataSource(): DataSource {
		// return new DataSource
	}
}


fun main() {
	val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
	// everything wires up across configuration classes...
	val transferService = ctx.getBean<TransferService>()
	transferService.transfer(100.00, "A123", "C456")
}

还有另一种方法可以达到相同的结果。记住@Configuration类是最终容器中只有另一个 bean:这意味着它们可以利用@Autowired@Value注射和其他功能与任何其他 Bean 相同。spring-doc.cadn.net.cn

确保以这种方式注入的依赖项仅属于最简单的类型。@Configuration类在上下文初始化期间很早就被处理,强制依赖项以这种方式注入可能会导致意外的早期初始化。只要有可能,请诉诸基于参数的注入,如前面的示例所示。spring-doc.cadn.net.cn

避免访问@PostConstruct方法相同配置 类。 这有效地导致了循环参考,因为非静态@Bean方法语义上需要调用一个完全初始化的配置类实例。使用循环引用不允许(例如,在 Spring Boot 2.6+ 中),这可能会触发BeanCurrentlyInCreationException.spring-doc.cadn.net.cn

另外,要特别小心BeanPostProcessorBeanFactoryPostProcessor定义 通过@Bean.这些通常应该声明为static @Bean方法,而不是触发 实例化其包含的配置类。否则@Autowired@Value可能不会 处理配置类本身,因为可以在AutowiredAnnotationBeanPostProcessor.spring-doc.cadn.net.cn

以下示例显示了如何将一个 Bean 自动连接到另一个 Bean:spring-doc.cadn.net.cn

@Configuration
public class ServiceConfig {

	@Autowired
	private AccountRepository accountRepository;

	@Bean
	public TransferService transferService() {
		return new TransferServiceImpl(accountRepository);
	}
}

@Configuration
public class RepositoryConfig {

	private final DataSource dataSource;

	public RepositoryConfig(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	@Bean
	public AccountRepository accountRepository() {
		return new JdbcAccountRepository(dataSource);
	}
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

	@Bean
	public DataSource dataSource() {
		// return new DataSource
	}
}

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
	// everything wires up across configuration classes...
	TransferService transferService = ctx.getBean(TransferService.class);
	transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

	@Autowired
	lateinit var accountRepository: AccountRepository

	@Bean
	fun transferService(): TransferService {
		return TransferServiceImpl(accountRepository)
	}
}

@Configuration
class RepositoryConfig(private val dataSource: DataSource) {

	@Bean
	fun accountRepository(): AccountRepository {
		return JdbcAccountRepository(dataSource)
	}
}

@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {

	@Bean
	fun dataSource(): DataSource {
		// return new DataSource
	}
}

fun main() {
	val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
	// everything wires up across configuration classes...
	val transferService = ctx.getBean<TransferService>()
	transferService.transfer(100.00, "A123", "C456")
}
构造函数注入@Configurationclasses 仅从 Spring 开始受支持 框架 4.3.另请注意,无需指定@Autowired如果目标 bean 只定义了一个构造函数。

完全合格的导入 Bean,便于导航

在前面的场景中,使用@Autowired效果很好,提供了所需的 模块化,但确定自动连接 bean 定义的确切声明位置是 还是有些模棱两可。例如,作为开发人员,将ServiceConfig、如何 您确切地知道@Autowired AccountRepositorybean 被声明了吗?它不是 在代码中显式,这可能没问题。请记住,Spring Tools for Eclipse 提供了以下工具: 可以渲染显示所有内容如何连接的图表,这可能就是您所需要的。也 您的 Java IDE 可以轻松找到AccountRepository类型 并快速向您展示@Bean返回该类型的方法。spring-doc.cadn.net.cn

如果这种歧义是不可接受的,并且您希望直接导航 从您的 IDE 中从一个@Configuration类到另一个,请考虑自动连接 配置类本身。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@Configuration
public class ServiceConfig {

	@Autowired
	private RepositoryConfig repositoryConfig;

	@Bean
	public TransferService transferService() {
		// navigate 'through' the config class to the @Bean method!
		return new TransferServiceImpl(repositoryConfig.accountRepository());
	}
}
@Configuration
class ServiceConfig {

	@Autowired
	private lateinit var repositoryConfig: RepositoryConfig

	@Bean
	fun transferService(): TransferService {
		// navigate 'through' the config class to the @Bean method!
		return TransferServiceImpl(repositoryConfig.accountRepository())
	}
}

在上述情况下,其中AccountRepository定义是完全明确的。 然而ServiceConfig现在紧密耦合到RepositoryConfig.那就是 权衡。这种紧密耦合可以通过使用基于接口或 抽象类@Configuration类。请考虑以下示例:spring-doc.cadn.net.cn

@Configuration
public class ServiceConfig {

	@Autowired
	private RepositoryConfig repositoryConfig;

	@Bean
	public TransferService transferService() {
		return new TransferServiceImpl(repositoryConfig.accountRepository());
	}
}

@Configuration
public interface RepositoryConfig {

	@Bean
	AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

	@Bean
	public AccountRepository accountRepository() {
		return new JdbcAccountRepository(...);
	}
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

	@Bean
	public DataSource dataSource() {
		// return DataSource
	}

}

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
	TransferService transferService = ctx.getBean(TransferService.class);
	transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean

@Configuration
class ServiceConfig {

	@Autowired
	private lateinit var repositoryConfig: RepositoryConfig

	@Bean
	fun transferService(): TransferService {
		return TransferServiceImpl(repositoryConfig.accountRepository())
	}
}

@Configuration
interface RepositoryConfig {

	@Bean
	fun accountRepository(): AccountRepository
}

@Configuration
class DefaultRepositoryConfig : RepositoryConfig {

	@Bean
	fun accountRepository(): AccountRepository {
		return JdbcAccountRepository(...)
	}
}

@Configuration
@Import(ServiceConfig::class, DefaultRepositoryConfig::class)  // import the concrete config!
class SystemTestConfig {

	@Bean
	fun dataSource(): DataSource {
		// return DataSource
	}

}

fun main() {
	val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
	val transferService = ctx.getBean<TransferService>()
	transferService.transfer(100.00, "A123", "C456")
}

现在ServiceConfig相对于混凝土松耦合DefaultRepositoryConfig,并且内置的 IDE 工具仍然有用:您可以轻松地 获取类型层次结构RepositoryConfig实现。在这个 方式, 导航@Configuration类及其依赖关系没有什么不同 比导航基于接口的代码的通常过程。spring-doc.cadn.net.cn

影响启动@Bean定义的单例

如果要影响某些单例 Bean 的启动创建顺序,请考虑 将其中一些声明为@Lazy用于在首次访问时而不是在启动时创建。spring-doc.cadn.net.cn

@DependsOn强制首先初始化某些其他 bean,确保 指定的 bean 是在当前 bean 之前创建的,超出后者的 直接依赖关系意味着。spring-doc.cadn.net.cn

后台初始化

从 6.2 开始,有一个后台初始化选项:@Bean(bootstrap=BACKGROUND)允许挑选出特定的 bean 进行后台初始化,包括 上下文启动时每个此类 Bean 的整个 Bean 创建步骤。spring-doc.cadn.net.cn

具有非惰性注入点的依赖 Bean 会自动等待 Bean 实例 待完成。所有常规后台初始化都会在最后强制完成 的上下文启动。只有额外标记为@Lazy被允许完成 稍后(直到第一次实际访问)。spring-doc.cadn.net.cn

后台初始化通常与@Lazy(或ObjectProvider) 依赖 bean 中的注入点。否则,主引导线程将 当需要提前注入实际的后台初始化 Bean 实例时,块。spring-doc.cadn.net.cn

这种形式的并发启动适用于单个 bean:如果这样的 bean 依赖于其他 bean,它们需要已经被初始化,要么简单地通过之前声明,要么通过@DependsOn在主 在触发受影响 Bean 的后台初始化之前引导线程。spring-doc.cadn.net.cn

一个bootstrapExecutor类型Executor必须声明为背景 引导才能真正活跃。否则,背景标记将被忽略 运行。spring-doc.cadn.net.cn

引导执行器可以是仅用于启动目的的有界执行器,也可以是共享的 线程池,也可用于其他目的。spring-doc.cadn.net.cn

有条件地包括@Configuration类或@Bean方法

有条件地启用或禁用完整的@Configuration类 甚至个人@Bean方法,基于一些任意系统状态。一个常见的例如,使用@Profile注释仅在 Spring 中启用了特定的配置文件时才激活 beanEnvironment(有关详细信息,请参阅 Bean 定义配置文件)。spring-doc.cadn.net.cn

@Profile注释实际上是通过使用更灵活的注释来实现的 叫@Conditional. 这@Conditional注释表示特定的org.springframework.context.annotation.Condition应该在@Bean已注册。spring-doc.cadn.net.cn

的实现Condition接口提供matches(…​)返回truefalse. 例如,以下列表显示了实际的Condition用于@Profile:spring-doc.cadn.net.cn

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
	// Read the @Profile annotation attributes
	MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
	if (attrs != null) {
		for (Object value : attrs.get("value")) {
			if (context.getEnvironment().matchesProfiles((String[]) value)) {
				return true;
			}
		}
		return false;
	}
	return true;
}
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
	// Read the @Profile annotation attributes
	val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
	if (attrs != null) {
		for (value in attrs["value"]!!) {
			if (context.environment.matchesProfiles(*value as Array<String>)) {
				return true
			}
		}
		return false
	}
	return true
}

请参阅@Conditionaljavadoc 了解更多详情。spring-doc.cadn.net.cn

结合 Java 和 XML 配置

Spring的@Configuration类支持的目标不是 100% 完全替代对于 Spring XML。一些工具,例如 Spring XML 命名空间,仍然是配置容器的理想方式。在 XML 方便或必要的情况下,您有一个选择:要么以“以 XML 为中心”的方式实例化容器,例如,使用ClassPathXmlApplicationContext,或使用AnnotationConfigApplicationContext@ImportResource注释以导入 XML根据需要。spring-doc.cadn.net.cn

以 XML 为中心的使用@Configuration

最好从 XML 引导 Spring 容器并包含@Configuration类。例如,在使用 Spring XML 的大型现有代码库中,创建起来更容易@Configuration类 根据需要,并从现有 XML 文件中包含它们。在本节后面,我们将介绍 使用选项@Configuration类在这种“以 XML 为中心”的情况下。spring-doc.cadn.net.cn

声明@Configuration类作为普通 Spring<bean/>元素

记住@Configuration类最终是容器中的 Bean 定义。 在本系列示例中,我们创建了一个@Configuration名为AppConfig和 将其包含在system-test-config.xml作为<bean/>定义。因为<context:annotation-config/>打开时,容器会识别@Configuration注释并处理@BeanAppConfig适当地。spring-doc.cadn.net.cn

以下示例显示了AppConfigJava 和 Kotlin 中的配置类:spring-doc.cadn.net.cn

@Configuration
public class AppConfig {

	@Autowired
	private DataSource dataSource;

	@Bean
	public AccountRepository accountRepository() {
		return new JdbcAccountRepository(dataSource);
	}

	@Bean
	public TransferService transferService() {
		return new TransferServiceImpl(accountRepository());
	}
}
@Configuration
class AppConfig {

	@Autowired
	private lateinit var dataSource: DataSource

	@Bean
	fun accountRepository(): AccountRepository {
		return JdbcAccountRepository(dataSource)
	}

	@Bean
	fun transferService() = TransferService(accountRepository())
}

以下示例显示了示例的一部分system-test-config.xml文件:spring-doc.cadn.net.cn

<beans>
	<!-- enable processing of annotations such as @Autowired and @Configuration -->
	<context:annotation-config/>

	<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

	<bean class="com.acme.AppConfig"/>

	<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="url" value="${jdbc.url}"/>
		<property name="username" value="${jdbc.username}"/>
		<property name="password" value="${jdbc.password}"/>
	</bean>
</beans>

以下示例显示了可能的jdbc.properties文件:spring-doc.cadn.net.cn

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
	ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
	TransferService transferService = ctx.getBean(TransferService.class);
	// ...
}
fun main() {
	val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
	val transferService = ctx.getBean<TransferService>()
	// ...
}
system-test-config.xml文件,则AppConfig <bean/>不声明id属性。虽然这样做是可以接受的,但考虑到没有其他 bean ever 引用它,并且不太可能按名称从容器中显式获取它。 同样,DataSourcebean 仅按类型自动连接,因此显式 beanid不是严格要求的。

使用 <context:component-scan/> 进行拾取@Configuration

因为@Configuration元注释为@Component,@Configuration-注释 类自动成为组件扫描的候选者。使用与 在前面的例子中描述,我们可以重新定义system-test-config.xml拿 组件扫描的优势。请注意,在这种情况下,我们不需要显式声明<context:annotation-config/>因为<context:component-scan/>启用相同的 功能性。spring-doc.cadn.net.cn

以下示例显示了修改后的system-test-config.xml文件:spring-doc.cadn.net.cn

<beans>
	<!-- picks up and registers AppConfig as a bean definition -->
	<context:component-scan base-package="com.acme"/>

	<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

	<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="url" value="${jdbc.url}"/>
		<property name="username" value="${jdbc.username}"/>
		<property name="password" value="${jdbc.password}"/>
	</bean>
</beans>

@Configuration以类为中心的 XML 使用@ImportResource

在以下应用中@Configuration类是配置的主要机制 容器,可能仍然需要至少使用一些 XML。在这种情况下,您 可以使用@ImportResource并仅定义您需要的 XML。这样做可以实现 “以 Java 为中心”的方法来配置容器,并将 XML 保持在最低限度。 以下示例(包括一个配置类,一个定义 bean、属性文件和main()方法)展示了如何使用@ImportResource注释以实现根据需要使用 XML 的“以 Java 为中心”的配置:spring-doc.cadn.net.cn

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

	@Value("${jdbc.url}")
	private String url;

	@Value("${jdbc.username}")
	private String username;

	@Value("${jdbc.password}")
	private String password;

	@Bean
	public DataSource dataSource() {
		return new DriverManagerDataSource(url, username, password);
	}

	@Bean
	public AccountRepository accountRepository(DataSource dataSource) {
		return new JdbcAccountRepository(dataSource);
	}

	@Bean
	public TransferService transferService(AccountRepository accountRepository) {
		return new TransferServiceImpl(accountRepository);
	}

}
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
class AppConfig {

	@Value("\${jdbc.url}")
	private lateinit var url: String

	@Value("\${jdbc.username}")
	private lateinit var username: String

	@Value("\${jdbc.password}")
	private lateinit var password: String

	@Bean
	fun dataSource(): DataSource {
		return DriverManagerDataSource(url, username, password)
	}

	@Bean
	fun accountRepository(dataSource: DataSource): AccountRepository {
		return JdbcAccountRepository(dataSource)
	}

	@Bean
	fun transferService(accountRepository: AccountRepository): TransferService {
		return TransferServiceImpl(accountRepository)
	}

}
properties-config.xml
<beans>
	<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
	TransferService transferService = ctx.getBean(TransferService.class);
	// ...
}
import org.springframework.beans.factory.getBean

fun main() {
	val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
	val transferService = ctx.getBean<TransferService>()
	// ...
}