Перейти к основному содержимому

Создайте пользовательскую автоконфигурацию с помощью Spring Boot

· 7 мин. чтения

Задача: Сумма двух

Дано массив целых чисел и целая сумма. Нужно найти индексы двух чисел, сумма которых равна заданной ...

ANDROMEDA

1. Обзор

Проще говоря, автоматическая настройка Spring Boot помогает нам автоматически настроить приложение Spring на основе зависимостей, присутствующих в пути к классам.

Это может ускорить и упростить разработку, устраняя необходимость определения определенных bean-компонентов, включенных в классы автоконфигурации.

В следующем разделе мы рассмотрим создание нашей пользовательской автоматической конфигурации Spring Boot.

2. Зависимости Maven

Начнем с зависимостей:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>

Последние версии spring-boot-starter-data-jpa и mysql-connector-java можно загрузить с Maven Central.

3. Создание пользовательской автоматической конфигурации

Чтобы создать пользовательскую автоконфигурацию, нам нужно создать класс с аннотацией @Configuration и зарегистрировать его.

Давайте создадим пользовательскую конфигурацию для источника данных MySQL :

@Configuration
public class MySQLAutoconfiguration {
//...
}

Далее нам нужно зарегистрировать класс в качестве кандидата на автоматическую настройку.

Делаем это, добавляя имя класса по ключу org.springframework.boot.autoconfigure.EnableAutoConfiguration в стандартном файле resources/META-INF/spring.factories :

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.foreach.autoconfiguration.MySQLAutoconfiguration

Если мы хотим, чтобы наш класс автоконфигурации имел приоритет над другими кандидатами, мы можем добавить аннотацию @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) .

Мы разрабатываем автоконфигурацию, используя классы и bean-компоненты, помеченные аннотациями @Conditional , чтобы мы могли заменить автоконфигурацию или определенные ее части.

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

3.1. Условия класса

Условия класса позволяют нам указать, что мы хотим включить компонент конфигурации, если указанный класс присутствует с помощью аннотации @ConditionalOnClass или если класс отсутствует с помощью аннотации @ConditionalOnMissingClass .

Давайте укажем, что наша MySQLConfiguration будет загружаться только в том случае, если присутствует класс DataSource , и в этом случае мы можем предположить, что приложение будет использовать базу данных:

@Configuration
@ConditionalOnClass(DataSource.class)
public class MySQLAutoconfiguration {
//...
}

3.2. Условия бобов

Если мы хотим включить bean-компонент только в том случае, если указанный bean-компонент присутствует или нет , мы можем использовать аннотации @ConditionalOnBean и @ConditionalOnMissingBean .

Чтобы посмотреть на это, давайте добавим bean-компонент entityManagerFactory в наш класс конфигурации.

Во-первых, мы укажем, что мы хотим создать этот bean-компонент только в том случае, если bean-компонент с именем dataSource присутствует и если bean-компонент с именем entityManagerFactory еще не определен:

@Bean
@ConditionalOnBean(name = "dataSource")
@ConditionalOnMissingBean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.foreach.autoconfiguration.example");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
if (additionalProperties() != null) {
em.setJpaProperties(additionalProperties());
}
return em;
}

Давайте также настроим bean-компонент transactionManager , который будет загружаться, только если мы еще не определили bean-компонент типа JpaTransactionManager :

@Bean
@ConditionalOnMissingBean(type = "JpaTransactionManager")
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}

3.3. Условия недвижимости

Мы используем аннотацию @ConditionalOnProperty , чтобы указать, загружается ли конфигурация на основе наличия и значения свойства Spring Environment.

Во-первых, давайте добавим исходный файл свойств для нашей конфигурации, который будет определять, откуда будут считываться свойства:

@PropertySource("classpath:mysql.properties")
public class MySQLAutoconfiguration {
//...
}

Мы можем настроить основной bean-компонент DataSource , который мы будем использовать для создания соединений с базой данных, чтобы он загружался только при наличии свойства, называемого usemysql .

Мы можем использовать атрибут haveValue , чтобы указать определенные значения свойства usemysql , которые должны сопоставляться.

Теперь давайте определим bean- компонент dataSource со значениями по умолчанию, который подключается к локальной базе данных с именем myDb , если мы установим для свойства usemysql значение local :

@Bean
@ConditionalOnProperty(
name = "usemysql",
havingValue = "local")
@ConditionalOnMissingBean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();

dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/myDb?createDatabaseIfNotExist=true");
dataSource.setUsername("mysqluser");
dataSource.setPassword("mysqlpass");

return dataSource;
}

Если мы установим для свойства usemysql значение custom , мы настроим bean- компонент dataSource, используя значения пользовательских свойств для URL-адреса базы данных, пользователя и пароля:

@Bean(name = "dataSource")
@ConditionalOnProperty(
name = "usemysql",
havingValue = "custom")
@ConditionalOnMissingBean
public DataSource dataSource2() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();

dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl(env.getProperty("mysql.url"));
dataSource.setUsername(env.getProperty("mysql.user") != null
? env.getProperty("mysql.user") : "");
dataSource.setPassword(env.getProperty("mysql.pass") != null
? env.getProperty("mysql.pass") : "");

return dataSource;
}

Файл mysql.properties будет содержать свойство usemysql :

usemysql=local

Приложению, использующему MySQLAutoconfiguration , может потребоваться переопределить свойства по умолчанию. В этом случае нужно просто добавить разные значения для свойств mysql.url , mysql.user и mysql.pass и строку usemysql=custom в файле mysql.properties .

3.4. Условия ресурсов

Добавление аннотации @ConditionalOnResource означает, что конфигурация загружается только при наличии указанного ресурса.

Давайте определим метод с именем AdditionalProperties () , который будет возвращать объект Properties , содержащий специфичные для Hibernate свойства, которые будут использоваться bean- компонентом entityManagerFactory , только если присутствует файл ресурсов mysql.properties :

@ConditionalOnResource(
resources = "classpath:mysql.properties")
@Conditional(HibernateCondition.class)
Properties additionalProperties() {
Properties hibernateProperties = new Properties();

hibernateProperties.setProperty("hibernate.hbm2ddl.auto",
env.getProperty("mysql-hibernate.hbm2ddl.auto"));
hibernateProperties.setProperty("hibernate.dialect",
env.getProperty("mysql-hibernate.dialect"));
hibernateProperties.setProperty("hibernate.show_sql",
env.getProperty("mysql-hibernate.show_sql") != null
? env.getProperty("mysql-hibernate.show_sql") : "false");
return hibernateProperties;
}

Мы можем добавить специфичные для Hibernate свойства в файл mysql.properties :

mysql-hibernate.dialect=org.hibernate.dialect.MySQLDialect
mysql-hibernate.show_sql=true
mysql-hibernate.hbm2ddl.auto=create-drop

3.5. Пользовательские условия

Допустим, мы не хотим использовать какие-либо условия, доступные в Spring Boot.

Мы также можем определить пользовательские условия, расширив класс SpringBootCondition и переопределив метод getMatchOutcome() .

Давайте создадим условие с именем HibernateCondition для нашего метода AdditionalProperties () , который будет проверять, присутствует ли класс HibernateEntityManager в пути к классам:

static class HibernateCondition extends SpringBootCondition {

private static String[] CLASS_NAMES
= { "org.hibernate.ejb.HibernateEntityManager",
"org.hibernate.jpa.HibernateEntityManager" };

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {

ConditionMessage.Builder message
= ConditionMessage.forCondition("Hibernate");
return Arrays.stream(CLASS_NAMES)
.filter(className -> ClassUtils.isPresent(className, context.getClassLoader()))
.map(className -> ConditionOutcome
.match(message.found("class")
.items(Style.NORMAL, className)))
.findAny()
.orElseGet(() -> ConditionOutcome
.noMatch(message.didNotFind("class", "classes")
.items(Style.NORMAL, Arrays.asList(CLASS_NAMES))));
}
}

Затем мы можем добавить условие в метод AdditionalProperties() :

@Conditional(HibernateCondition.class)
Properties additionalProperties() {
//...
}

3.6. Условия применения

Мы также можем указать, что конфигурация может загружаться только внутри/вне веб-контекста. Для этого мы можем добавить аннотацию @ConditionalOnWebApplication или @ConditionalOnNotWebApplication .

4. Тестирование автоконфигурации

Давайте создадим очень простой пример для проверки нашей автоматической настройки.

Мы создадим класс сущности с именем MyUser и интерфейс MyUserRepository , используя Spring Data:

@Entity
public class MyUser {
@Id
private String email;

// standard constructor, getters, setters
}
public interface MyUserRepository 
extends JpaRepository<MyUser, String> { }

Чтобы включить автоконфигурацию, мы можем использовать одну из аннотаций @SpringBootApplication или @EnableAutoConfiguration :

@SpringBootApplication
public class AutoconfigurationApplication {
public static void main(String[] args) {
SpringApplication.run(AutoconfigurationApplication.class, args);
}
}

Далее давайте напишем тест JUnit , который сохраняет сущность MyUser :

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(
classes = AutoconfigurationApplication.class)
@EnableJpaRepositories(
basePackages = { "com.foreach.autoconfiguration.example" })
public class AutoconfigurationTest {

@Autowired
private MyUserRepository userRepository;

@Test
public void whenSaveUser_thenOk() {
MyUser user = new MyUser("user@email.com");
userRepository.save(user);
}
}

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

Строка подключения содержит свойство createDatabaseIfNotExist=true , поэтому база данных может не существовать. Однако необходимо создать пользователя mysqluser или указанного в свойстве mysql.user , если оно присутствует.

Мы можем проверить журнал приложения, чтобы увидеть, что мы используем источник данных MySQL :

web - 2017-04-12 00:01:33,956 [main] INFO  o.s.j.d.DriverManagerDataSource - Loaded JDBC driver: com.mysql.cj.jdbc.Driver

5. Отключение классов автоконфигурации

Допустим, мы хотим исключить из загрузки автоконфигурацию.

Мы могли бы добавить аннотацию @EnableAutoConfiguration с атрибутом exclude или excludeName в класс конфигурации:

@Configuration
@EnableAutoConfiguration(
exclude={MySQLAutoconfiguration.class})
public class AutoconfigurationApplication {
//...
}

Мы также можем установить свойство spring.autoconfigure.exclude :

spring.autoconfigure.exclude=com.foreach.autoconfiguration.MySQLAutoconfiguration

6. Заключение

В этой статье мы показали, как создать пользовательскую автоконфигурацию Spring Boot.

Полный исходный код примера можно найти на GitHub .

Тест JUnit можно запустить с помощью профиля автоконфигурации mvn clean install -Pautoconfiguration .