Приложения 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, с которым вы можете поиграть. Повеселись!