Статьи

Избегайте условной логики в @Configuration

Приложения Integration Testing Spring обязуются создавать небольшие выделенные фрагменты конфигурации и собирать их либо во время обычного запуска приложения, либо во время тестов. Даже в последнем случае разные фрагменты могут быть собраны в разных тестах.

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

Мое единственное требование состоит в том, чтобы по умолчанию использовался источник данных JNDI, а при активации флага — профиля должен переключаться на прямое соединение. В этом случае рефлекс Павлова должен был добавить простое условие в  @Configuration классе.

@Configuration
public class MyConfiguration {
 
    @Autowired
    private Environment env;
 
    @Bean
    public DataSource dataSource() throws Exception {
        if (env.acceptsProfiles("dev")) {
            org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
            dataSource.setDriverClassName("org.h2.Driver");
            dataSource.setUrl("jdbc:h2:file:~/conditional");
            dataSource.setUsername("sa");
            return dataSource;
        }
        JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
        return dataSourceLookup.getDataSource("java:comp/env/jdbc/conditional"); 
    }
}

Начало использования таких операторов управления потоком — это начало конца, так как в будущем это приведет к добавлению операторов управления потоком, что, в свою очередь, приведет к запутанному беспорядку   конфигурации спагетти и, в конечном итоге, к не поддерживаемому приложению.

Spring Boot предлагает хорошую альтернативу для обработки этого варианта использования с различными вариантами  @ConditionalXXX аннотаций. Их использование имеет следующие преимущества при выполнении работы: простота использования, удобочитаемость и ограниченность. Хотя последний момент может показаться недостатком, это самый большой актив IMHO (в отличие от плагинов Maven). Код является мощным, и с большой властью должна прийти большая ответственность, что вряд ли возможно в ходе проекта с крайними сроками и давлением со стороны руководства. Это основная причина, по которой один из моих коллег выступает за XML вместо JavaConfig: с XML вы уверены, что не будет никаких злоупотреблений, пока проект работает.

Но давайте остановимся на философии и вернемся к  @ConditionalXXX аннотациям. По сути, добавление такой аннотации к  @Bean методу вызовет этот метод и поместит компонент в фабрику на основе специального условия. Их много, вот несколько важных:

  • Зависит от версии Java, новее или старше — @ConditionalOnJava
  • Зависит от бобов, присутствующих на фабрике  @ConditionalOnBean, и наоборот, зависит от имени бобов, которых нет. @ConditionalOnMissingBean
  • Зависит от класса, присутствующего на пути к классам  @ConditionalOnClass, и его противоположности @ConditionalOnMissingClass
  • Будь то веб-приложение или нет —  @ConditionalOnWebApplication и @ConditionalOnNotWebApplication
  • и т.п.

Обратите внимание, что весь список существующих условий можно просмотреть в  пакете org.springframework.boot.autoconfigure.condition в Spring Boot  .

С помощью этой информации мы можем перенести приведенный выше фрагмент в более надежную реализацию:

@Configuration
public class MyConfiguration {
 
    @Bean
    @Profile("dev")
    public DataSource dataSource() throws Exception {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:file:~/localisatordb");
        dataSource.setUsername("sa");
        return dataSource;
    }
 
    @Bean
    @ConditionalOnMissingBean(DataSource.class)
    public DataSource fakeDataSource() {
        JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
        return dataSourceLookup.getDataSource("java:comp/env/jdbc/conditional");
    }
}

Конфигурация теперь аккуратно разделена на два разных метода, первый метод будет вызываться только тогда, когда  dev профиль активен, а второй будет, когда первый метод не вызывается, следовательно, когда  dev профиль  не  активен.

Наконец, лучшее, что есть в этой функции, это то, что она легко расширяема, так как зависит только от  @Conditional аннотации и  Condition интерфейса (которые являются частью самой Spring, а не Spring Boot).

Вот простой  пример  в формате Maven / IntelliJ, с которым вы можете поиграть. Повеселись!