对于最新的稳定版本,请使用 Spring Framework 7.0.6!spring-doc.cadn.net.cn

组合基于 Java 的配置

Spring的基于Java的配置功能允许你使用注解,这可以降低配置的复杂性。spring-doc.cadn.net.cn

使用 @Import 注解

就像在Spring XML文件中使用<import/>元素有助于模块化配置一样,@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 定义上注入依赖项

前面的示例有效,但过于简单。在大多数实际情况下,配置类中的 beans 之间会有依赖关系。使用 XML 时,这不是问题,因为没有编译器参与,您可以声明 ref="someBean" 并信任 Spring 在容器初始化期间处理它。 使用 @Configuration 类时,Java 编译器会对配置模型施加约束,即对其他 beans 的引用必须是有效的 Java 语法。spring-doc.cadn.net.cn

幸运的是,解决这个问题很简单。正如我们已经讨论过的,一个@Bean方法可以拥有任意数量的参数来描述bean依赖关系。考虑以下更现实的场景,其中有几个@Configuration类,每个类都依赖于其他类中声明的beans: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:这意味着它们可以像其他任何 Bean 一样利用 @Autowired@Value 注入和其他功能。spring-doc.cadn.net.cn

确保通过这种方式注入的依赖项只能是最简单的类型。@Configuration类在上下文初始化期间被处理得相当早,强制通过这种方式注入依赖项可能导致意外的早期初始化。只要可能,应使用基于参数的注入,如前面的示例所示。spring-doc.cadn.net.cn

避免在同一个配置类的 @PostConstruct 方法中访问本地定义的 bean。这实际上会导致循环引用,因为非静态 @Bean 方法在语义上需要一个完全初始化的配置类实例才能被调用。由于禁止循环引用(例如在 Spring Boot 2.6+ 中),这可能会触发 BeanCurrentlyInCreationExceptionspring-doc.cadn.net.cn

同时,特别注意通过BeanPostProcessorBeanFactoryPostProcessor定义的@Bean。这些通常应声明为static @Bean方法,而不是触发其包含配置类的实例化。否则,@Autowired@Value可能无法在配置类本身上工作,因为有可能在AutowiredAnnotationBeanPostProcessor之前就创建了它作为bean实例。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")
}
构造函数注入在 @Configuration 个类中仅从 Spring 框架 4.3 版本开始受支持。另外,如果目标 bean 只定义了一个构造函数,则无需指定 @Autowired
用于导航的完全限定导入的bean

在前面的场景中,使用@Autowired可以很好地提供所需的模块化,但确定自动注入的bean定义具体在哪里声明仍然有些模糊。例如,作为查看ServiceConfig的开发人员,您如何知道@Autowired AccountRepository bean是在哪里声明的?这在代码中并不明确,这可能没问题。请记住,Eclipse的Spring工具提供了可以显示所有连接关系的图表的工具,这可能正是您需要的。此外,您的Java IDE可以轻松查找AccountRepository类型的全部声明和用法,并快速显示返回该类型的@Bean方法的位置。spring-doc.cadn.net.cn

在某些情况下,这种歧义是不可接受的,而您希望从IDE内部直接从一个<code>0</code>类导航到另一个类时,可以考虑将配置类本身进行自动连线。下面的示例显示了如何操作: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的启动创建顺序,可以考虑将其中一些声明为@Lazy(在首次访问时创建而不是在启动时创建) 或声明为@DependsOn,以确保其他某些Bean在当前Bean之前被创建,这超出了当前Bean直接依赖所暗示的范围。

按条件包含 @Configuration 个类或 @Bean 个方法

通常根据某些任意系统状态,有条件地启用或禁用整个 @Configuration 类或甚至单独的 @Bean 方法会很有用。一个常见的例子是使用 @Profile 注解,仅在 Spring Environment 中启用了特定配置文件时激活 beans(有关详细信息,请参阅 Bean Definition Profiles)。spring-doc.cadn.net.cn

@Profile 注解实际上是通过使用一个更灵活的注解 @Conditional 来实现的。 @Conditional 注解表示在注册 @Bean 之前应查阅的特定 org.springframework.context.annotation.Condition 实现。spring-doc.cadn.net.cn

Condition 接口的实现提供了一个返回 matches(…​)true 方法,或者返回 false。例如,以下列表显示了用于 @Profile 的实际 Condition 实现: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().acceptsProfiles(((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.acceptsProfiles(Profiles.of(*value as Array<String>))) {
				return true
			}
		}
		return false
	}
	return true
}

查看 @Conditional javadoc 以获取更多详细信息。spring-doc.cadn.net.cn

将 Java 和 XML 配置相结合

Spring 的 @Configuration 类支持并不是要完全替代 Spring XML。一些功能,例如 Spring XML 命名空间,仍然是配置容器的理想方式。在 XML 方便或必要的情况下,你有两种选择:要么以“以 XML 为中心”的方式实例化容器,例如使用 ClassPathXmlApplicationContext,要么以“以 Java 为中心”的方式实例化容器,使用 AnnotationConfigApplicationContext@ImportResource 注解按需导入 XML。spring-doc.cadn.net.cn

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

从XML引导Spring容器并以临时方式包含@Configuration类可能更为可取。例如,在使用Spring XML的大型现有代码库中,按需创建@Configuration类并从现有的XML文件中包含它们会更加方便。在本节的后面部分,我们将介绍在这种“以XML为中心”的情况下使用@Configuration类的选项。spring-doc.cadn.net.cn

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

请注意,@Configuration 类实际上是容器中的 bean 定义。在这些示例中,我们创建了一个名为 @ConfigurationAppConfig 类,并将其作为 <bean/> 定义包含在 system-test-config.xml 中。由于 <context:annotation-config/> 被启用,容器会识别 @Configuration 注解,并正确处理 AppConfig 中声明的 @Bean 方法。spring-doc.cadn.net.cn

以下示例显示了一个普通的Java配置类: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 TransferService(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 引用它,而且很可能不会通过名称从容器中显式获取,因此是不必要的。同样,DataSource bean 只是通过类型自动注入的,所以不需要显式的 bean id
使用 <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

在以<code>0</code>类为主要配置容器机制的应用程序中,可能仍然需要使用一些XML。在这些情况下,您可以使用<code>1</code>并仅定义您需要的XML部分。这样可以实现一种“以Java为中心”的容器配置方法,并将XML保持在最低限度。下面的示例(包括一个配置类、一个定义bean的XML文件、一个属性文件以及<code>2</code>类)展示了如何使用<code>3</code>注解来实现需要时使用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);
	}
}
@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)
	}
}
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>()
	// ...
}