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

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

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

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 .