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会引用它,并且不太可能通过名称从容器中显式提取它。 与DataSource bean类似,它只能通过类型自动注入,所以一个显式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);
// ...
}