Каков предпочтительный способ передачи зависимостей компонентов в Spring

#java #spring #dependency-injection #spring-boot #javabeans

#java #spring #внедрение зависимостей #spring-boot #javabeans

Вопрос:

При чтении документации Spring framework я наткнулся на два разных стиля передачи зависимостей 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);
    }

}
  

Я хотел бы знать, каковы плюсы и минусы двух решений и какое из них предпочтительнее?

Ответ №1:

В обычных случаях использования не должно быть такой большой разницы между двумя подходами. Но если вы хотите разделить свою конфигурацию на несколько классов, первое решение нельзя просто использовать. Предполагается, что вы хотите поместить два ваших метода @Bean в отдельные классы и импортировать первый во второй через @Import, у компилятора нет возможности узнать метод ConnectionFactory() первого класса в конструкторе RabbitTemplate(ConnectionFactory()) второго класса. Таким образом, вы столкнетесь с ошибкой компилятора. Чтобы решить эту проблему, вы можете использовать второй подход, рекомендованный в документации spring: внедрение зависимостей от импортированных определений @Bean

Внедрение зависимостей от импортированных определений @Bean

Приведенный выше пример работает, но является упрощенным. В большинстве практических сценариев компоненты будут иметь зависимости друг от друга по классам конфигурации. При использовании XML это само по себе не является проблемой, потому что компилятор не задействован, и можно просто объявить ref=»SomeBean» и верить, что Spring выполнит это во время инициализации контейнера. Конечно, при использовании классов @Configuration компилятор Java накладывает ограничения на модель конфигурации, поскольку ссылки на другие компоненты должны быть допустимым синтаксисом Java.

К счастью, решить эту проблему просто. Как мы уже обсуждали, метод @Bean может иметь произвольное количество параметров, описывающих зависимости bean. Давайте рассмотрим более реальный сценарий с несколькими классами @Configuration, каждый из которых зависит от компонентов, объявленных в других:

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

Ответ №2:

Я не верю, что вы получите консенсус по «предпочтительному» вопросу об этом.

Плюсом второго стиля является то, что он будет работать как есть, независимо от того, где определен зависимый компонент.

Например, если бы два рассматриваемых компонента были определены в двух отдельных классах @Configuration, стиль one стал бы таким:

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

}
  

Второй плюс заключается в том, что это облегчает распознавание того, когда у bean слишком много зависимостей, что означает, что он может делать слишком много, а это значит, что может быть время для рефакторинга.

На самом деле я не знал о втором стиле до нескольких недель назад, но с тех пор я его использую.

Удачи!

Ответ №3:

Второй (если вы также рассматриваете тестирование на этапе написания вашего кода). Всегда старайтесь избегать жесткой проводки и создавать экземпляры с помощью внедрения зависимостей, чтобы было намного проще тестировать ваш код (например, издеваться https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html ) и оставайтесь настраиваемыми.

Комментарии:

1. Что вы подразумеваете под жесткой проводкой? Оба фрагмента выполняют внедрение конструктора в RabbitTemplate .

2. В вашем первом примере RabbitTemplate используется прямой результат connectionFactory() метода.

3. Я не OP. Результат вызова connectionFactory кэшируется. Все вызовы будут возвращать одно и то же значение.

4. угадайте, что соответствующий раздел в документации по тестированию Spring находится здесь: docs.spring.io/spring-boot/docs/current/reference/html /… — если я правильно понял, первый фрагмент выше не позволяет Spring вводить издевательскую зависимость, тогда как второй метод позволяет делатьитак, я прав?

5. Пожалуйста, отредактируйте свой ответ, чтобы уточнить это и часть о стольких экземплярах .