此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
编写基于 Java 的配置
Spring 基于 Java 的配置功能允许您编写 Comments,这可以减少 配置的复杂程度。
使用@Import
注解
就像<import/>
元素在 Spring XML 文件中用于帮助模块化
配置、@Import
annotation 允许加载@Bean
定义来自
另一个 Configuration 类,如下例所示:
-
Java
-
Kotlin
@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.class
和ConfigB.class
什么时候
仅实例化上下文ConfigB
需要显式提供,因为
以下示例显示:
-
Java
-
Kotlin
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>()
}
这种方法简化了容器实例化,因为只需要处理一个类
替换为 Bean 的 Git,而不是要求您记住可能大量的@Configuration
类。
从 Spring Framework 4.2 开始,@Import 还支持对常规组件的引用
类,类似于AnnotationConfigApplicationContext.register 方法。
如果您想通过使用一些
configuration 类作为入口点来显式定义所有组件。 |
在 Imported 上注入依赖项@Bean
定义
前面的示例有效,但过于简单。在大多数实际场景中,bean 具有
跨配置类彼此依赖。使用 XML 时,这不是
issue,因为不涉及编译器,你可以声明ref="someBean"
并相信 Spring 会在容器初始化期间解决它。
使用@Configuration
类中,Java 编译器对
配置模型,因为对其他 bean 的引用必须是有效的 Java 语法。
幸运的是,解决这个问题很简单。正如我们已经讨论过的,
一个@Bean
method 可以具有任意数量的描述 bean 的参数
依赖。考虑以下更现实的场景,其中包含几个@Configuration
类,每个类都依赖于其他类中声明的 bean:
-
Java
-
Kotlin
@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
injection 和其他功能与任何其他 bean 相同。
确保您以这种方式注入的依赖项只是最简单的类型。 避免在 此外,要特别小心 |
下面的示例展示了如何将一个 bean 自动连接到另一个 bean:
-
Java
-
Kotlin
@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 classes 仅从 Spring 开始受支持
框架 4.3.另请注意,无需指定@Autowired 如果目标
Bean 只定义了一个构造函数。 |
完全合格的导入 bean 以方便导航
在前面的场景中,使用@Autowired
效果很好,并提供所需的
模块化,但确定自动装配的 bean 定义的确切声明位置是
仍然有点模棱两可。例如,作为查看ServiceConfig
、如何
您确切地知道@Autowired AccountRepository
bean 被声明了吗?事实并非如此
explicit 的 intent 函数,这可能就好了。请记住,Spring Tools for Eclipse 提供了以下工具
可以渲染显示所有内容是如何连接的图表,这可能就是您所需要的。也
您的 Java IDE 可以轻松找到AccountRepository
类型
并快速显示@Bean
返回该类型的方法。
如果这种歧义是不可接受的,并且您希望进行直接导航
从 IDE 中从一个@Configuration
类添加到另一个类中,请考虑自动将
configuration 类本身。以下示例显示了如何执行此作:
-
Java
-
Kotlin
@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
类。请考虑以下示例:
-
Java
-
Kotlin
@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
类及其依赖项也不例外
而不是导航基于接口的代码的通常过程。
影响 的启动@Bean
定义的单例
如果要影响某些单例 bean 的启动创建顺序,请考虑
将其中一些声明为@Lazy
用于在首次访问时创建,而不是在启动时创建。
@DependsOn
强制首先初始化某些其他 bean,确保
指定的 bean 是在当前 bean 之前创建的,超出了后者的
直接依赖意味着。
后台初始化
从 6.2 开始,有一个 background initialization 选项:@Bean(bootstrap=BACKGROUND)
允许挑选出特定的 bean 进行后台初始化,涵盖
上下文启动时每个此类 bean 的整个 bean 创建步骤。
具有非惰性注入点的依赖 bean 会自动等待 bean 实例
完成。所有常规后台初始化都强制在结束时完成
的上下文启动。只有额外标记为@Lazy
允许完成
稍后(直到第一次实际访问)。
后台初始化通常与@Lazy
(或ObjectProvider
)
依赖 bean 中的注入点。否则,主引导线程将
块。
这种形式的并发启动适用于单个 bean:如果这样的 bean 依赖于
其他 bean 需要已经初始化,或者简单地通过
提前声明或通过@DependsOn
它在 main
引导线程。
一个 引导程序执行程序可以是仅用于启动目的的有界执行程序,也可以是共享的 thread 池,该池也用于其他目的。 |
有条件地包含@Configuration
Classes 或@Bean
方法
有条件地启用或禁用完整的@Configuration
类
甚至是个人@Bean
方法,基于一些任意的系统状态。一个普通
例如,使用@Profile
注解,仅当特定的
配置文件已在 Spring 中启用Environment
(有关详细信息,请参见 Bean 定义配置文件)。
这@Profile
Annotation 实际上是通过使用更灵活的 Comments 来实现的
叫@Conditional
.
这@Conditional
annotation 表示特定org.springframework.context.annotation.Condition
应该
在@Bean
已注册。
的Condition
interface 提供matches(…)
方法,该方法返回true
或false
.例如,下面的清单显示了实际的Condition
用于@Profile
:
-
Java
-
Kotlin
@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
}
请参阅@Conditional
javadoc 了解更多详情。
组合 Java 和 XML 配置
Spring的@Configuration
Class Support 的目标不是成为 100% 完全的替代品
用于 Spring XML。一些工具(例如 Spring XML 名称空间)仍然是
配置容器。在 XML 方便或必要的情况下,您有一个
choice:以“以 XML 为中心”的方式实例化容器,例如,ClassPathXmlApplicationContext
,或者使用AnnotationConfigApplicationContext
和@ImportResource
用于导入 XML 的注释
根据需要。
以 XML 为中心的使用@Configuration
类
最好从 XML 引导 Spring 容器并包含@Configuration
类。例如,在大型现有代码库中
使用 Spring XML,则更容易创建@Configuration
类
根据需要,并从现有 XML 文件中包含它们。在本节的后面部分,我们将介绍
使用选项@Configuration
类。
声明@Configuration
类作为普通 Spring<bean/>
元素
请记住@Configuration
类最终是容器中的 bean 定义。
在本系列示例中,我们将创建一个@Configuration
类名称AppConfig
和
将其包含在system-test-config.xml
作为<bean/>
定义。因为<context:annotation-config/>
处于打开状态时,容器会识别@Configuration
注解并处理@Bean
在AppConfig
适当地。
以下示例显示了AppConfig
configuration 类的 Java 和 Kotlin 中:
-
Java
-
Kotlin
@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
文件:
<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
文件:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
-
Java
-
Kotlin
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 引用它,并且不太可能按名称从容器中显式获取它。
同样,DataSource bean 只按类型自动装配,因此显式 beanid 并非严格要求。 |
使用 <context:component-scan/> 来拾取@Configuration
类
因为@Configuration
使用@Component
,@Configuration
-注释
类会自动成为组件扫描的候选对象。使用与
在前面的例子中描述,我们可以重新定义system-test-config.xml
取
组件扫描的优势。请注意,在这种情况下,我们不需要显式声明<context:annotation-config/>
因为<context:component-scan/>
启用相同的
功能性。
以下示例显示了修改后的system-test-config.xml
文件:
<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 为中心” 的配置:
-
Java
-
Kotlin
@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)
}
}
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
-
Java
-
Kotlin
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>()
// ...
}