«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.baeldung.autoconfiguration.MySQLAutoconfiguration

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

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

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

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

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

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

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

3.2. Условия компонента

Если мы хотим включить компонент только в том случае, если указанный компонент присутствует или нет, мы можем использовать аннотации @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.baeldung.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
public class AutoconfigurationApplication {
    public static void main(String[] args) {
        SpringApplication.run(AutoconfigurationApplication.class, args);
    }
}

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

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

    @Autowired
    private MyUserRepository userRepository;

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

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

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

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

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

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

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

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

Если мы хотим исключить загрузку автоконфигурации, мы можно добавить аннотацию @EnableAutoConfiguration с атрибутом exclude или excludeName в класс конфигурации:


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

Другой способ отключить определенные автоконфигурации — установить свойство spring.autoconfigure.exclude:

6. Выводы

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