Spring Bootでカスタム自動設定を作成する

Spring Bootでカスタム自動構成を作成する

1. 概要

簡単に言えば、Spring Bootの自動構成は、クラスパスに存在する依存関係に基づいてSpringアプリケーションを自動的に構成する方法を表します。

これにより、自動構成クラスに含まれる特定のBeanを定義する必要がなくなるため、開発が迅速かつ容易になります。

次のセクションでは、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の最新バージョンは、MavenCentralからダウンロードできます。

3. カスタム自動構成の作成

カスタム自動構成を作成するには、@Configurationという注釈が付けられたクラスを作成して登録する必要があります。

MySQLデータソースのカスタム構成を作成しましょう。

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

次の必須ステップは、標準ファイルresources/META-INF/spring.factoriesのキーorg.springframework.boot.autoconfigure.EnableAutoConfigurationの下にクラスの名前を追加することにより、クラスを自動構成候補として登録することです。

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

自動構成クラスを他の自動構成候補よりも優先させたい場合は、@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)アノテーションを追加できます。

自動構成は、@Conditionalアノテーションが付けられたクラスとBeanを使用して設計されているため、自動構成またはその特定の部分を置き換えることができます。

自動構成は、自動構成されたBeanがアプリケーションで定義されていない場合にのみ有効であることに注意してください。 Beanを定義すると、デフォルトのBeanがオーバーライドされます。

3.1. クラスの条件

クラス条件により、@ConditionalOnClassアノテーションを使用してspecify that a configuration bean will be included if a specified class is present@ConditionalOnMissingClassアノテーションを使用してor if a class is absentを実行できます。

クラスDataSourceが存在する場合にのみ、MySQLConfigurationがロードされるように指定しましょう。この場合、アプリケーションはデータベースを使用すると想定できます。

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

3.2. 豆の状態

include a bean only if a specified bean is present or notが必要な場合は、@ConditionalOnBeanおよび@ConditionalOnMissingBeanアノテーションを使用できます。

これを例証するために、構成クラスにentityManagerFactory Beanを追加し、dataSourceというBeanが存在し、entityManagerFactoryというBeanが存在する場合にのみ、このBeanを作成するように指定しましょう。まだ定義されていません:

@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;
}

タイプJpaTransactionManagerのBeanがまだ定義されていない場合にのみロードされるtransactionManagerBeanも構成しましょう。

@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 {
    //...
}

データベースへの接続を作成するために使用されるメインのDataSource Beanを、usemysqlというプロパティが存在する場合にのみロードされるように構成できます。

属性havingValueを使用して、一致する必要があるusemysqlプロパティの特定の値を指定できます。

usemysqlプロパティがlocalに設定されている場合、myDbというローカルデータベースに接続するデフォルト値を使用してdataSourceBeanを定義しましょう。

@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 Beanは、データベース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.urlmysql.usermysql.passプロパティと%に異なる値を追加するだけです。 mysql.propertiesファイルの(t4)s行。

3.4. リソース条件

@ConditionalOnResource注釈を追加することは、configuration will only be loaded when a specified resource is presentを意味します。

リソースファイルmysql.propertiesが存在する場合にのみ、entityManagerFactory Beanで使用されるHibernate固有のプロパティを含むPropertiesオブジェクトを返すadditionalProperties()というメソッドを定義しましょう。

@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を使用することもできます。

HibernateEntityManagerクラスがクラスパスに存在するかどうかを確認するadditionalProperties()メソッドのHibernateConditionという条件を作成しましょう。

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アノテーションを追加することにより、specify that the configuration can be loaded only inside/outside a web contextを作成することもできます。

4. 自動構成のテスト

自動構成をテストするための非常に簡単な例を作成しましょう。 Spring Dataを使用してMyUserというエンティティークラスとMyUserRepositoryインターフェースを作成します。

@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);
    }
}

次に、MyUserエンティティを保存するJUnitテストを作成しましょう。

@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構成を定義していないため、アプリケーションは、作成した自動構成を使用して、myDbと呼ばれるMySQLデータベースに接続します。

接続文字列には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が必要な場合は、excludeまたはexcludeName属性を持つ@EnableAutoConfigurationアノテーションを構成クラスに追加できます。

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

特定の自動構成を無効にする別のオプションは、spring.autoconfigure.excludeプロパティを設定することです。

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

6. 結論

このチュートリアルでは、カスタムのSpringBoot自動構成を作成する方法を示しました。 例の完全なソースコードはover on GitHubにあります。

JUnitテストは、autoconfigurationプロファイルmvn clean install -Pautoconfigurationを使用して実行できます。