在 Spring 中传递 Bean 依赖项的最佳方式是什么

What is the preferable way for passing Bean dependencies in Spring

在阅读 Spring 框架的文档时,我偶然发现了两种不同的在工厂方法中传递 Bean 依赖项的方式。第一个是这样的(直接使用依赖的工厂方法):

@Configuration
public class MessagingConfiguration {

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        return new RabbitTemplate(connectionFactory());
    }

}

第二个看起来像这样(在工厂方法中将依赖项作为参数注入):

@Configuration
public class MessagingConfiguration {

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        return new RabbitTemplate(connectionFactory);
    }

}

我想知道这两种解决方案的优缺点,哪一种更可取?

第二个(如果您也考虑在编写代码时进行测试)。始终尝试避免硬连接并通过依赖注入创建实例,以便更容易测试您的代码(例如 Mocking https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html)并保持可配置性。

我不相信你会就此达成共识"preferrable"。

第二种风格的优点是它会按原样工作,而不管依赖 bean 在哪里定义。

例如,如果有问题的两个 bean 在两个单独的 @Configuration 类 中定义,样式一将变成这样:

@Configuration
public class MessagingConfiguration1 {

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }

}

@Configuration
public class MessagingConfiguration2 {

    @Autowired
    ConnectionFactory connectionFactory;    

    @Bean
    public RabbitTemplate rabbitTemplate() {
        return new RabbitTemplate(this.connectionFactory);
    }

}

而第二种样式将保持不变:

@Configuration
public class MessagingConfiguration1 {

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        return connectionFactory;
    }

}

@Configuration
public class MessagingConfiguration1 {

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        return new RabbitTemplate(connectionFactory);
    }

}

第二个 PRO 是当 bean 具有太多依赖项时更容易看到,这意味着它可能做得太多,这意味着可能是重构的时候了。

直到几周前我才意识到第二种风格,但从那以后我就一直在使用它。

祝你好运!

在正常使用情况下,这两种方法之间不应有如此大的差异。但是,如果您想将配置拆分为多个 classes,则不能简单地使用第一个解决方案。假设您想将两个@Bean 方法放入单独的 classes 中,并通过 @Import 将第一个方法导入到第二个方法中,编译器就没有机会知道第一个方法的 connectionFactory() class 在第二个 class 的 RabbitTemplate(connectionFactory()) 构造函数中。所以你会 运行 变成一个编译器错误。要解决此问题,您可以使用 spring 文档推荐的第二种方法:Injecting dependencies on imported @Bean definitions

Injecting dependencies on imported @Bean definitions

The example above works, but is simplistic. In most practical scenarios, beans will have dependencies on one another across configuration classes. When using XML, this is not an issue, per se, because there is no compiler involved, and one can simply declare ref="someBean" and trust that Spring will work it out during container initialization. Of course, when using @Configuration classes, the Java compiler places constraints on the configuration model, in that references to other beans must be valid Java syntax.

Fortunately, solving this problem is simple. As we already discussed, @Bean method can have an arbitrary number of parameters describing the bean dependencies. Let’s consider a more real-world scenario with several @Configuration classes, each depending on beans declared in the others:

@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");
}