Créer une configuration automatique personnalisée avec un démarrage printanier

Créer une configuration automatique personnalisée avec un démarrage printanier

1. Vue d'ensemble

Autrement dit, l'autoconfiguration Spring Boot représente un moyen de configurer automatiquement une application Spring en fonction des dépendances présentes dans le chemin d'accès aux classes.

Cela peut accélérer et faciliter le développement en éliminant la nécessité de définir certains beans inclus dans les classes de configuration automatique.

Dans la section suivante, nous allons jeter un œil àcreating our custom Spring Boot auto-configuration.

2. Dépendances Maven

Commençons par les dépendances dont nous avons besoin:


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


    mysql
    mysql-connector-java
    8.0.11

Les dernières versions despring-boot-starter-data-jpa etmysql-connector-java peuvent être téléchargées depuis Maven Central.

3. Création d'une configuration automatique personnalisée

Pour créer une configuration automatique personnalisée, nous devons créer une classe annotée en tant que@Configuration et l'enregistrer.

Créons une configuration personnalisée pour une source de donnéesMySQL:

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

La prochaine étape obligatoire consiste à enregistrer la classe en tant que candidat à la configuration automatique, en ajoutant le nom de la classe sous la cléorg.springframework.boot.autoconfigure.EnableAutoConfiguration dans le fichier standardresources/META-INF/spring.factories:

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

Si nous voulons que notre classe de configuration automatique ait la priorité sur les autres candidats de configuration automatique, nous pouvons ajouter l'annotation@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE).

La configuration automatique est conçue à l'aide de classes et de beans marqués des annotations@Conditional afin que la configuration automatique ou des parties spécifiques de celle-ci puissent être remplacées.

Notez que la configuration automatique n'est effective que si les beans auto-configurés ne sont pas définis dans l'application. Si vous définissez votre bean, celui par défaut sera remplacé.

3.1. Conditions de classe

Les conditions de classe nous permettent despecify that a configuration bean will be included if a specified class is present en utilisant l'annotation@ConditionalOnClass,or if a class is absent en utilisant l'annotation@ConditionalOnMissingClass.

Précisons que nosMySQLConfiguration ne seront chargés que si la classeDataSource est présente, auquel cas nous pouvons supposer que l'application utilisera une base de données:

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

3.2. Conditions du haricot

Si nous voulonsinclude a bean only if a specified bean is present or not, nous pouvons utiliser les annotations@ConditionalOnBean et@ConditionalOnMissingBean.

Pour illustrer cela, ajoutons un beanentityManagerFactory à notre classe de configuration, et spécifions que nous voulons que ce bean soit créé uniquement si un bean appelédataSource est présent et si un bean appeléentityManagerFactory est pas déjà défini:

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

Configurons également un beantransactionManager qui ne sera chargé que si un bean de typeJpaTransactionManager n’est pas déjà défini:

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

3.3. Conditions de propriété

L'annotation@ConditionalOnProperty est utilisée pourspecify if a configuration will be loaded based on the presence and value of a Spring Environment property.

Tout d'abord, ajoutons un fichier source de propriété pour notre configuration qui déterminera où les propriétés seront lues:

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

Nous pouvons configurer le beanDataSource principal qui sera utilisé pour créer des connexions à la base de données de telle manière qu'il ne sera chargé que si une propriété appeléeusemysql est présente.

Nous pouvons utiliser l'attributhavingValue pour spécifier certaines valeurs de la propriétéusemysql qui doivent être mises en correspondance.

Définissons le beandataSource avec les valeurs par défaut qui se connectent à une base de données locale appeléemyDb si la propriétéusemysql est définie surlocal:

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

Si la propriétéusemysql est définie surcustom,, le beandataSource sera configuré à l'aide des valeurs de propriétés personnalisées pour l'URL de la base de données, l'utilisateur et le mot de passe:

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

Le fichiermysql.properties contiendra la propriétéusemysql:

usemysql=local

Si une application qui utilise lesMySQLAutoconfiguration souhaite remplacer les propriétés par défaut, il lui suffit d'ajouter des valeurs différentes pour les propriétésmysql.url,mysql.user etmysql.pass et leusemysql=custom ligne dans le fichiermysql.properties.

3.4. Conditions des ressources

L'ajout de l'annotation@ConditionalOnResource signifie que leconfiguration will only be loaded when a specified resource is present.

Définissons une méthode appeléeadditionalProperties() qui retournera un objetProperties contenant des propriétés spécifiques à Hibernate à utiliser par le beanentityManagerFactory, uniquement si le fichier de ressourcesmysql.properties est présent:

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

Nous pouvons ajouter les propriétés spécifiques d'Hibernate au fichiermysql.properties:

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

3.5. Conditions personnalisées

Si nous ne voulons utiliser aucune des conditions disponibles dans Spring Boot, nous pouvons égalementdefine custom conditions by extending the SpringBootCondition class and overriding the getMatchOutcome() method.

Créons une condition appeléeHibernateCondition pour notre méthodeadditionalProperties() qui vérifiera si une classeHibernateEntityManager est présente sur le chemin de classe:

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

Ensuite, nous pouvons ajouter la condition à la méthodeadditionalProperties():

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

3.6. Conditions d'application

On peut aussispecify that the configuration can be loaded only inside/outside a web context, en ajoutant l'annotation@ConditionalOnWebApplication ou@ConditionalOnNotWebApplication.

4. Test de la configuration automatique

Créons un exemple très simple pour tester notre configuration automatique. Nous allons créer une classe d'entité appeléeMyUser et une interfaceMyUserRepository en utilisant Spring Data:

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

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

Pour activer la configuration automatique, nous pouvons utiliser l'une des annotations@SpringBootApplication ou@EnableAutoConfiguration:

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

Ensuite, écrivons un testJUnit qui enregistre une entité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);
    }
}

Puisque nous n'avons pas défini notre configurationDataSource, l'application utilisera la configuration automatique que nous avons créée pour se connecter à une base de donnéesMySQL appeléemyDb.

La chaîne de connexion contient la propriétécreateDatabaseIfNotExist=true, la base de données n'a donc pas besoin d'exister. Cependant, l'utilisateurmysqluser ou celui spécifié via la propriétémysql.user s'il est présent, doit être créé.

Nous pouvons vérifier le journal de l'application pour voir que la source de donnéesMySQL est utilisée:

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

5. Désactivation des classes de configuration automatique

Si nous voulionsexclude the auto-configuration from being loaded, nous pourrions ajouter l'annotation@EnableAutoConfiguration avec l'attributexclude ouexcludeName à une classe de configuration:

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

Une autre option pour désactiver des configurations automatiques spécifiques consiste à définir la propriétéspring.autoconfigure.exclude:

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

6. Conclusions

Dans ce didacticiel, nous avons montré comment créer une configuration automatique Spring Boot personnalisée. Le code source complet de l'exemple peut être trouvéover on GitHub.

Le test JUnit peut être exécuté en utilisant le profilautoconfiguration:mvn clean install -Pautoconfiguration.