Create a Custom Auto-Configuration with Spring Boot – 用Spring Boot创建一个自定义的自动配置

最后修改: 2017年 4月 23日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Simply put, the Spring Boot auto-configuration helps us automatically configure a Spring application based on the dependencies that are present on the classpath.

简单地说,Spring Boot自动配置帮助我们根据classpath上存在的依赖关系自动配置Spring应用程序。

This can make development faster and easier by eliminating the need to define certain beans included in the auto-configuration classes.

这可以使开发变得更快、更容易,因为不需要定义某些包含在自动配置类中的Bean。

In the following section, we’ll look at creating our custom Spring Boot auto-configuration.

在下一节中,我们将讨论创建我们的自定义Spring Boot自动配置。

2. Maven Dependencies

2.Maven的依赖性

Let’s start with the dependencies:

让我们从依赖性开始。

<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>

The latest versions of spring-boot-starter-data-jpa and mysql-connector-java can be downloaded from Maven Central.

spring-boot-starter-data-jpamysql-connector-java的最新版本可以从Maven中心下载。

3. Creating a Custom Auto-Configuration

3.创建一个自定义的自动配置

In order to create a custom auto-configuration, we need to create a class annotated as @Configuration and register it.

为了创建一个自定义的自动配置,我们需要创建一个注释为@Configuration的类,并注册它。

Let’s create a custom configuration for a MySQL data source:

让我们为MySQL数据源创建一个自定义配置。

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

Next, we need to register the class as an auto-configuration candidate.

接下来,我们需要将该类注册为自动配置候选者。

We do this by adding the name of the class under the key org.springframework.boot.autoconfigure.EnableAutoConfiguration in the standard file resources/META-INF/spring.factories:

我们通过在标准文件resources/META-INF/spring.plants中的键org.springframework.boot.autoconfigure.EnableAutoConfiguration下添加该类的名称来实现。

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

If we want our auto-configuration class to have priority over other candidates, we can add the @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) annotation.

如果我们希望我们的自动配置类比其他候选类有优先权,我们可以添加@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)注解。

We design the auto-configuration using classes and beans marked with @Conditional annotations so that we can replace the auto-configuration or specific parts of it.

我们使用标有@Conditional注解的类和Bean来设计自动配置,这样我们就可以替换自动配置或它的特定部分。

Note that the auto-configuration is only in effect if we don’t define the auto-configured beans in the application. If we define our bean, it will override the default one.

请注意,只有当我们不在应用程序中定义自动配置的Bean时,自动配置才会生效。如果我们定义了我们的Bean,它将覆盖默认的Bean。

3.1. Class Conditions

3.1.班级条件

Class conditions allow us to specify that we want to include a configuration bean if a specified class is present using the @ConditionalOnClass annotation, or if a class is absent using the @ConditionalOnMissingClass annotation.

类条件允许我们使用@ConditionalOnClass注解指定我们要在指定的类存在的情况下包含配置Bean,或者使用@ConditionalOnMissingClass注解如果一个类不存在

Let’s specify that our MySQLConfiguration will load only if the class DataSource is present, in which case we can assume the application will use a database:

让我们指定我们的MySQLConfiguration只有在DataSource类存在时才会加载,在这种情况下,我们可以假设应用程序将使用一个数据库。

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

3.2. Bean Conditions

3.2.Bean条件

If we want to include a bean only if a specified bean is present or not, we can use the @ConditionalOnBean and @ConditionalOnMissingBean annotations.

如果我们想只在指定的Bean存在与否的情况下包含一个Bean,我们可以使用@ConditionalOnBean@ConditionalOnMissingBean注释。

To look at this, let’s add an entityManagerFactory bean to our configuration class.

为了研究这个问题,让我们在我们的配置类中添加一个entityManagerFactorybean。

First, we’ll specify that we only want to create this bean if a bean called dataSource is present and if a bean called entityManagerFactory is not already defined:

首先,我们将指定只有在存在名为dataSource的bean,并且尚未定义名为entityManagerFactory的bean的情况下,我们才要创建这个bean。

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

Let’s also configure a transactionManager bean that will load only if we haven’t already defined a bean of type JpaTransactionManager:

让我们也配置一个transactionManager bean,只有当我们还没有定义一个JpaTransactionManager类型的bean时才会加载。

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

3.3. Property Conditions

3.3.财产条件

We use the @ConditionalOnProperty annotation to specify if a configuration loads based on the presence and value of a Spring Environment property.

我们使用@ConditionalOnProperty注解来指定配置是否基于Spring环境属性的存在和值而加载。

First, let’s add a property source file for our configuration that will determine where the properties will be read from:

首先,让我们为我们的配置添加一个属性源文件,该文件将决定从哪里读取属性。

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

We can configure the main DataSource bean that we’ll use to create connections to the database so that it will load only if a property called usemysql is present.

我们可以配置主DataSourcebean,我们将用它来创建与数据库的连接,这样它将只在名为usemysql的属性存在时才加载。

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

我们可以使用属性havingValue来指定必须匹配的usemysql属性的某些值。

Now let’s define the dataSource bean with default values that connect to a local database called myDb if we set the usemysql property to local:

现在让我们定义带有默认值的dataSource Bean,如果我们将usemysql属性设置为local,它将连接到一个名为myDb>的本地数据库。

@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 we set the usemysql property to custom, we’ll configure the dataSource bean using custom properties values for the database URL, user and password:

如果我们将usemysql属性设置为custom,我们将使用数据库URL、用户和密码的自定义属性值配置dataSourcebean。

@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:

mysql.properties文件将包含usemysql属性。

usemysql=local

An application that uses the MySQLAutoconfiguration may need to override the default properties. In this case, it just needs to add different values for the mysql.url, mysql.user and mysql.pass properties and the usemysql=custom line in the mysql.properties file.

一个使用MySQLAutoconfiguration的应用程序可能需要覆盖默认属性。在这种情况下,它只需要为mysql.urlmysql.usermysql.pass属性以及usemysql=custom行添加不同的值。

3.4. Resource Conditions

3.4.资源条件

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

添加@ConditionalOnResource注解意味着配置仅在指定资源存在时加载。

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:

让我们定义一个名为additionalProperties()的方法,该方法将返回一个Properties对象,该对象包含Hibernate特定的属性,将由entityManagerFactorybean使用,只有当资源文件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;
}

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

我们可以在mysql.properties文件中添加Hibernate的特定属性。

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

3.5. Custom Conditions

3.5.自定义条件

Let’s say we don’t want to use any of the conditions available in Spring Boot.

假设我们不想使用Spring Boot中的任何条件。

We can also define custom conditions by extending the SpringBootCondition class and overriding the getMatchOutcome() method.

我们还可以通过扩展SpringBootCondition类并重写getMatchOutcome()方法来定义自定义条件。

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

让我们为我们的additionalProperties()方法创建一个名为HibernateCondition的条件,它将验证HibernateEntityManager类是否存在于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:

然后我们就可以把这个条件添加到additionalProperties()方法中。

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

3.6. Application Conditions

3.6.申请条件

We can also specify that the configuration can load only inside/outside a web context. In order to do this, we can add the @ConditionalOnWebApplication or @ConditionalOnNotWebApplication annotation.

我们还可以指定配置只能在Web上下文内部/外部加载。为了做到这一点,我们可以添加@ConditionalOnWebApplication@ConditionalOnNotWebApplication注释。

4. Testing the Auto-Configuration

4.测试自动配置

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:

我们将使用Spring Data创建一个名为MyUser的实体类和一个MyUserRepository接口。

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

    // standard constructor, getters, setters
}
public interface MyUserRepository 
  extends JpaRepository<MyUser, String> { }

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

为了启用自动配置,我们可以使用@SpringBootApplication@EnableAutoConfiguration注释之一。

@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:

接下来,让我们写一个JUnit测试,保存一个MyUser实体。

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

    @Autowired
    private MyUserRepository userRepository;

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

Since we didn’t define our DataSource configuration, the application will use the auto-configuration we created to connect to a MySQL database called myDb.

由于我们没有定义我们的DataSource配置,应用程序将使用我们创建的自动配置来连接到一个名为MyDbMySQL数据库。

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.

连接字符串包含createDatabaseIfNotExist=true属性,所以数据库不需要存在。然而,需要创建用户mysqluser,或者通过mysql.user属性指定的用户(如果存在)。

We can check the application log to see that we’re using the MySQL data source:

我们可以检查应用程序日志,看看我们是否在使用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. Disabling Auto-Configuration Classes

5.禁用自动配置类

Let’s say we want to exclude the auto-configuration from loading.

比方说,我们想排除自动配置的加载。

We could add the @EnableAutoConfiguration annotation with exclude or excludeName attribute to a configuration class:

我们可以将@EnableAutoConfiguration注解与excludeexcludeName属性添加到一个配置类。

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

We can also set the spring.autoconfigure.exclude property:

我们还可以设置spring.autoconfigure.exclude属性。

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

6. Conclusion

6.结论

In this article, we’ve shown how to create a custom Spring Boot auto-configuration.

在这篇文章中,我们展示了如何创建一个自定义的Spring Boot自动配置。

The full source code of the example can be found over on GitHub.

该示例的完整源代码可以在GitHub上找到over

The JUnit test can be run using the autoconfiguration profile mvn clean install -Pautoconfiguration.

JUnit测试可以使用autoconfiguration配置文件mvn clean install -Pautoconfiguration运行。