3.12.5 组合Java基本配置
使用@Import注解
与Spring XML文件中使用<import/>元素来帮助进行模块化配置一样,@Import注解允许从另一个配置类加载@Bean定义:
@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();
}
}
现在,不需要在实例化上下文时同时指定ConfigA.class和ConfigB.class,而是仅需要提供ConfigB即可:
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);
}
这种方法简化了容器实例化,因为只有一个类需要处理,而不是要求开发人员在构建期间记住一个潜在的大量的@Configuration类。
![]() |
|---|
从Spring Framework 4.2开始,@Import也可以支持对普通组件类的引用了,类似于AnnotationConfigApplicationContext.register方法。 如果你想避免组件扫描,使用几个配置类作为明确定义你所有组件的入口点,这是非常有用的。 |
在导入的@Bean定义上注入依赖
上面的例子可以很好运行,但是太简单了。 在大多数实际情况下,bean将在配置类之间相互依赖。 当使用XML时,这本身不是一个问题,因为没有涉及编译器,可以简单地声明ref =“someBean”并且相信Spring将在容器初始化期间可以很好地处理它。 当然,当使用@Configuration类时,Java编译器会有一些限制,符合Java的语法。
幸运的是,解决这个问题很简单。 因为我们已经讨论过,@Bean方法可以有任意的参数用于描述其依赖。 让我们考虑一个更加真实的场景,使用了多个@Configuration类,每个配置都依赖其他配置中是bean声明:
@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");
}
还有另一种方法来实现相同的结果。 记住@Configuration类也是容器中的一个bean:这意味着他们可以像任何其他bean一样使用@Autowired和@Value注解!
![]() |
|---|
确保你注入的依赖关系是最简单的类型。 @Configuration类会在上下文初始化的早期被处理,所以它的依赖会在更早期被初始化。如果可能的话,请像上面这样使用参数化注入。同样地,对于通过@Bean声明的BeanPostProcessor和BeanFactoryPostProcessor请谨慎对待。 这些通常应该声明为“静态的 @Bean”方法,不会触发包含它们的配置类的实例化。 否则,@Autowired和@Value在配置类本身上是不起作用的,因为它们太早被实例化了. |
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
@Autowired
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");
}
![]() |
|---|
在@Configuration类中的构造函数注入只在Spring 4.3以后才支持。 还要注意,如果目标bean只定义一个构造函数,则不需要指定@Autowired; 在上面的例子中,@Autowired在RepositoryConfig构造函数中不是必需的。 |
在上面的场景中,@Autowired可以很好的工作,使设计更具模块化,但是自动注入的是哪个bean依然有些模糊不清。 例如,作为一个开发者查看ServiceConfig类时,你怎么知道@Autowired AccountRepository bean在哪定义的呢? 代码中并未明确指出,还好,Spring Tool Suite提供了工具,可以展示bean之间是如何装配的 - 这可能是你需要的。 此外,你的Java IDE可以轻松找到所有的定义和AccountRepository类型引用的地方,并可以快速地找出@Bean方法定义的地方。
万一需求不允许这种模糊的装配,并且你要在IDE内从Configuration类直接定位到依赖类bean,考虑使用硬编码,即由依赖类本身定位:
@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());
}
}
在上面的情况中,AccountRepository的定义就很明确了。 然而,ServiceConfig现在紧紧耦合到RepositoryConfig; 这是一种折衷的方法。 这种紧耦合某种程度上可以通过接口或抽象解决,如下:
@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");
}
现在ServiceConfig就与具体的DefaultRepositoryConfig松散耦合了,内置的IDE工具仍然有用:开发人员很容易得到一个RepositoryConfig实现类的继承体系。 以这种方式,操纵@Configuration类及其依赖关系与操作基于接口的代码的过程没有什么区别。
有条件地包含@Configuration类或@Bean方法
根据特定的系统状态,开启或者关闭一个@Configuration类,甚至是针对个别@Bean方法开启或者关闭,通常很有用。一个常见的例子是使用@Profile注解来激活bean,只有在SpringEnvironment中启用了一个特定的配置文件才有效(参见第3.13.1节“Bean定义配置文件”)。
@Profile注解实际上是使用一个更灵活的注解@Conditional实现的。 @Conditional注解表示特定的org.springframework.context.annotation.Condition实现,表明一个@Bean被注册之前会先询问@Condition。
Condition接口的实现只提供一个返回true或false的matches(...)方法。例如,这里是用于@Profile的一个具体的Condition实现:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
// 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;
}
有关更多信息,请参阅@Conditional javadocs 。
绑定Java与XML配置
Spring的@Configuration类并不能100%地替代XML配置。一 些情况下使用XML的命名空间仍然是最理想的方式来配置容器。 在某些场景下,XML是很方便或必要的,你可以选择:使用例如ClassPathXmlApplicationContext,或者以Java为主使用AnnotationConfigApplicationContext并在需要的时候使用@ImportResource注解导入XML配置
基于XML混合使用@Configuration类
更受人喜爱的方法是从XML启动容器并包含@Configuration类。 例如,在使用Spring的现有系统中,大量使用的是Spring XML配置的,所以很容易根据需要创建@Configuration类,并包含他们到XML文件中。 接下来看看此场景。
请记住,@Configuration类最终也只是容器中的bean定义。 在这个例子中,我们创建一个名为AppConfig的@Configuration类,并将它包含在system-test-config.xml中作为<bean />定义。 因为<context:annotation-config />被打开,容器将识别@Configuration注解,并正确处理在AppConfig中声明的@Bean方法。
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new 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=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
![]() |
|---|
在上面的system-test-config.xml中,AppConfig的<bean/>并没有声明一个id元素。 虽然这样做是可以接受的,但没有其他bean会引用它,并且不太可能通过名称从容器中显式提取它。 与DataSourcebean类似,它只能通过类型自动注入,所以一个显式beanid并不是严格要求的 |
因为@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配置
在@Configuration类为配置容器的主要方式的应用程序中,也需要使用一些XML配置。 在这些情况下,只需使用@ImportResource,并只定义所需的XML。 这样做实现了一种“以Java为主”的方法来配置容器并将尽可能少的使用XML。
@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);
}
}
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);
// ...
}
![[Tip]](http://docs.spring.io/spring/docs/5.0.0.M4/spring-framework-reference/htmlsingle/images/tip.png.pagespeed.ce.w22Wv-tZ37.png)
![[Warning]](http://docs.spring.io/spring/docs/5.0.0.M4/spring-framework-reference/htmlsingle/images/warning.png.pagespeed.ce.aG27Rp2bJY.png)
![[Note]](http://docs.spring.io/spring/docs/5.0.0.M4/spring-framework-reference/htmlsingle/images/note.png.pagespeed.ce.9zQ_1wVwzR.png)