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

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

1. обзор

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

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

В следующем разделе мы рассмотримcreating our custom Spring Boot auto-configuration.

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

Начнем с необходимых нам зависимостей:


    org.springframework.boot
    spring-boot-starter-data-jpa
    2.0.1.RELEASE


    mysql
    mysql-connector-java
    8.0.11

Последние версии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.example.autoconfiguration.MySQLAutoconfiguration

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

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

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

3.1. Условия занятий

Условия класса позволяют намspecify that a configuration bean will be included if a specified class is present использовать аннотацию@ConditionalOnClass,or if a class is absent использовать аннотацию@ConditionalOnMissingClass.

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

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

3.2. Условия Бина

Если мы хотимinclude a bean only if a specified bean is present or not, мы можем использовать аннотации@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.example.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 используется дляspecify if a configuration will be loaded based on the presence and value of a Spring Environment property.

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

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

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

Мы можем использовать атрибутhavingValue, чтобы указать определенные значения свойства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,, компонент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 означает, чтоconfiguration will only be loaded when a specified resource is present.

Давайте определим метод с именемadditionalProperties(), который будет возвращать объектProperties, содержащий специфические для Hibernate свойства, которые будут использоваться компонентом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, мы также можемdefine custom conditions by extending the SpringBootCondition class and overriding the getMatchOutcome() method.

Давайте создадим условие под названием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. Условия применения

Мы также можемspecify that the configuration can be loaded only inside/outside a web context, добавив аннотацию@ConditionalOnWebApplication или@ConditionalOnNotWebApplication.

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

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

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

    // standard constructor, getters, setters
}
public interface MyUserRepository
  extends JpaRepository { }

Чтобы включить автоконфигурацию, мы можем использовать одну из аннотаций@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.example.autoconfiguration.example" })
public class AutoconfigurationTest {

    @Autowired
    private MyUserRepository userRepository;

    @Test
    public void whenSaveUser_thenOk() {
        MyUser user = new MyUser("[email protected]");
        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. Отключение классов автоконфигурации

Если бы мы хотелиexclude the auto-configuration from being loaded, мы могли бы добавить аннотацию@EnableAutoConfiguration с атрибутомexclude илиexcludeName в класс конфигурации:

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

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

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

6. Выводы

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

Тест JUnit можно запустить с использованием профиляautoconfiguration:mvn clean install -Pautoconfiguration.