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.classConfigB.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类。

[Tip]
从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注解!

[Warning]
确保你注入的依赖关系是最简单的类型。 @Configuration类会在上下文初始化的早期被处理,所以它的依赖会在更早期被初始化。如果可能的话,请像上面这样使用参数化注入。同样地,对于通过@Bean声明的BeanPostProcessorBeanFactoryPostProcessor请谨慎对待。 这些通常应该声明为“静态的 @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");
}
[Tip]
@Configuration类中的构造函数注入只在Spring 4.3以后才支持。 还要注意,如果目标bean只定义一个构造函数,则不需要指定@Autowired; 在上面的例子中,@AutowiredRepositoryConfig构造函数中不是必需的。

在上面的场景中,@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接口的实现只提供一个返回truefalsematches(...)方法。例如,这里是用于@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);
    // ...
}
[Note]
在上面的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);
    // ...
}

results matching ""

    No results matching ""