Erstellen Sie eine benutzerdefinierte automatische Konfiguration mit Spring Boot

1. Übersicht

Einfach ausgedrückt stellt die automatische Konfiguration von Spring Boot eine Möglichkeit dar, eine Spring-Anwendung basierend auf den im Klassenpfad vorhandenen Abhängigkeiten automatisch zu konfigurieren.

Dies kann die Entwicklung beschleunigen und vereinfachen, da bestimmte Beans, die in den Klassen für die automatische Konfiguration enthalten sind, nicht mehr definiert werden müssen.

Im folgenden Abschnitt werden wir uns mit der Erstellung unserer benutzerdefinierten automatischen Spring Boot-Konfiguration befassen .

2. Maven-Abhängigkeiten

Beginnen wir mit den Abhängigkeiten, die wir brauchen:

 org.springframework.boot spring-boot-starter-data-jpa 2.2.2.RELEASE   mysql mysql-connector-java 8.0.19 

Die neuesten Versionen von Spring-Boot-Starter-Data-JPA und MySQL-Connector-Java können von Maven Central heruntergeladen werden.

3. Erstellen einer benutzerdefinierten automatischen Konfiguration

Um eine benutzerdefinierte automatische Konfiguration zu erstellen, müssen Sie eine Klasse mit der Bezeichnung @Configuration erstellen und registrieren.

Erstellen wir eine benutzerdefinierte Konfiguration für eine MySQL- Datenquelle:

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

Der nächste obligatorische Schritt ist das Registrieren der Klasse als Kandidat für die automatische Konfiguration, indem der Name der Klasse unter dem Schlüssel org.springframework.boot.autoconfigure.EnableAutoConfiguration in den Standarddateiressourcen / META-INF / spring.factories hinzugefügt wird :

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

Wenn unsere automatische Konfigurationsklasse Vorrang vor anderen Kandidaten für die automatische Konfiguration haben soll, können wir die Annotation @AutoConfigureOrder (Ordered.HIGHEST_PRECEDENCE) hinzufügen .

Die automatische Konfiguration wird mithilfe von Klassen und Beans entworfen, die mit @ Bedingten Annotationen gekennzeichnet sind, damit die automatische Konfiguration oder bestimmte Teile davon ersetzt werden können.

Beachten Sie, dass die automatische Konfiguration nur wirksam ist, wenn die automatisch konfigurierten Beans nicht in der Anwendung definiert sind. Wenn Sie Ihre Bean definieren, wird die Standard-Bean überschrieben.

3.1. Klassenbedingungen

Mit Klassenbedingungen können wir angeben, dass eine Konfigurations-Bean enthalten sein soll, wenn eine angegebene Klasse mithilfe der Annotation @ConditionalOnClass vorhanden ist oder wenn eine Klasse mithilfe der Annotation @ConditionalOnMissingClass fehlt .

Geben Sie an, dass unsere MySQLConfiguration nur geladen wird, wenn die Klasse DataSource vorhanden ist. In diesem Fall können wir davon ausgehen, dass die Anwendung eine Datenbank verwendet:

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

3.2. Bohnenbedingungen

Wenn wir eine Bean nur einschließen möchten, wenn eine angegebene Bean vorhanden ist oder nicht , können wir die Annotationen @ConditionalOnBean und @ConditionalOnMissingBean verwenden .

Um dies zu veranschaulichen, fügen wir unserer Konfigurationsklasse eine entityManagerFactory- Bean hinzu und geben an, dass diese Bean nur erstellt werden soll, wenn eine Bean mit dem Namen dataSource vorhanden ist und eine Bean mit dem Namen entityManagerFactory noch nicht definiert ist:

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

Konfigurieren Sie auch eine TransactionManager- Bean, die nur geladen wird, wenn noch keine Bean vom Typ JpaTransactionManager definiert ist:

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

3.3. Eigentumsbedingungen

Die Annotation @ConditionalOnProperty wird verwendet, um anzugeben, ob eine Konfiguration basierend auf dem Vorhandensein und dem Wert einer Spring Environment-Eigenschaft geladen wird .

Fügen wir zunächst eine Eigenschaftsquelldatei für unsere Konfiguration hinzu, die bestimmt, woher die Eigenschaften gelesen werden:

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

Wir können die Haupt- DataSource- Bean, die zum Erstellen von Verbindungen zur Datenbank verwendet wird, so konfigurieren , dass sie nur geladen wird, wenn eine Eigenschaft namens usemysql vorhanden ist.

We can use the attribute havingValue to specify certain values of the usemysql property that have to be matched.

Let's define the dataSource bean with default values that connect to a local database called myDb if the usemysql property is set to 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; }

If the usemysql property is set to custom, the dataSource bean will be configured using custom properties values for the database URL, user, and password:

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

The mysql.properties file will contain the usemysql property:

usemysql=local

If an application that uses the MySQLAutoconfiguration wishes to override the default properties, all it needs to do is add different values for the mysql.url, mysql.user and mysql.pass properties and the usemysql=custom line in the mysql.properties file.

3.4. Resource Conditions

Adding the @ConditionalOnResource annotation means that the configuration will only be loaded when a specified resource is present.

Let's define a method called additionalProperties() that will return a Properties object containing Hibernate-specific properties to be used by the entityManagerFactory bean, only if the resource file mysql.properties is present:

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

We can add the Hibernate specific properties to the mysql.properties file:

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

3.5. Custom Conditions

If we don't want to use any of the conditions available in Spring Boot, we can also define custom conditions by extending the SpringBootCondition class and overriding the getMatchOutcome() method.

Let's create a condition called HibernateCondition for our additionalProperties() method that will verify whether a HibernateEntityManager class is present on the classpath:

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

Then we can add the condition to the additionalProperties() method:

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

3.6. Application Conditions

We can also specify that the configuration can be loaded only inside/outside a web context, by adding the @ConditionalOnWebApplication or @ConditionalOnNotWebApplication annotation.

4. Testing the Auto-Configuration

Let's create a very simple example to test our auto-configuration. We will create an entity class called MyUser, and a MyUserRepository interface using Spring Data:

@Entity public class MyUser { @Id private String email; // standard constructor, getters, setters }
public interface MyUserRepository extends JpaRepository { }

To enable auto-configuration, we can use one of the @SpringBootApplication or @EnableAutoConfiguration annotations:

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

Next, let's write a JUnit test that saves a MyUser entity:

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

Since we have not defined our DataSource configuration, the application will use the auto-configuration we have created to connect to a MySQL database called myDb.

The connection string contains the createDatabaseIfNotExist=true property, so the database does not need to exist. However, the user mysqluser or the one specified through the mysql.user property if it is present, needs to be created.

We can check the application log to see that the MySQL data source is being used:

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

5. Disabling Auto-Configuration Classes

If we wanted to exclude the auto-configuration from being loaded, we could add the @EnableAutoConfiguration annotation with exclude or excludeName attribute to a configuration class:

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

Eine weitere Option zum Deaktivieren bestimmter automatischer Konfigurationen ist das Festlegen der Eigenschaft spring.autoconfigure.exclude :

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

6. Schlussfolgerungen

In diesem Tutorial haben wir gezeigt, wie Sie eine benutzerdefinierte automatische Spring Boot-Konfiguration erstellen. Den vollständigen Quellcode des Beispiels finden Sie auf GitHub.

Der JUnit-Test kann mithilfe des Autokonfigurationsprofils ausgeführt werden : mvn clean install -Pautoconfiguration .