2. Maven依赖

首先添加必要的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

最新版 spring-boot-starter-data-jpamysql-connector-java 可从 Maven Central 下载。

3. 创建自定义自动配置

要创建自定义自动配置,需要创建一个 @Configuration 注解的类并注册它

下面创建一个 MySQL 数据源的自定义配置:

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

接下来需要将类注册为自动配置候选。在标准文件 resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中添加类名:

com.baeldung.autoconfiguration.MySQLAutoconfiguration

若希望自定义配置优先级更高,可添加 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 注解。

自动配置的设计原则是使用带 @Conditional 注解的类和 Bean,这样便于替换整个或部分自动配置。

⚠️ 注意:自动配置仅在应用未定义相关 Bean 时生效。如果手动定义了 Bean,将覆盖默认配置。

3.1 类条件

类条件允许我们 使用 @ConditionalOnClass 注解指定当某个类存在时加载配置或使用 @ConditionalOnMissingClass 在类不存在时加载

指定 MySQLConfiguration 仅在 DataSource 类存在时加载(此时假定应用使用数据库):

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

3.2 Bean条件

若要 在指定 Bean 存在或不存在时才包含某个 Bean,可使用 @ConditionalOnBean@ConditionalOnMissingBean 注解。

向配置类添加 entityManagerFactory Bean,并设置以下条件:

  • 仅当名为 dataSource 的 Bean 存在时创建
  • entityManagerFactory 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;
}

再配置 transactionManager Bean,仅在未定义 JpaTransactionManager 类型 Bean 时加载:

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

3.3 属性条件

使用 @ConditionalOnProperty 注解 根据 Spring Environment 属性的存在和值决定是否加载配置

首先为配置添加属性源文件,指定属性读取位置:

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

配置主 DataSource Bean(用于数据库连接),仅在 usemysql 属性存在时加载。使用 havingValue 属性指定需匹配的值。

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

usemysql=custom 时,使用自定义属性值配置 dataSource

@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 值及 usemysql=custom 来覆盖默认属性。

3.4 资源条件

添加 @ConditionalOnResource 注解表示 仅当指定资源存在时加载配置

定义 additionalProperties() 方法,返回 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;
}

mysql.properties 中添加 Hibernate 特有属性:

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

3.5 自定义条件

若不想使用 Spring Boot 的内置条件,可继承 SpringBootCondition 类并重写 getMatchOutcome() 方法创建自定义条件

additionalProperties() 方法创建 HibernateCondition 条件,验证 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 应用条件

还可 指定配置仅在 Web 上下文内部/外部加载。使用 @ConditionalOnWebApplication@ConditionalOnNotWebApplication 注解实现。

4. 测试自动配置

创建简单示例测试自动配置。定义 MyUser 实体类和 MyUserRepository 接口(使用 Spring Data):

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

    // 标准构造器、getter/setter
}
public interface MyUserRepository 
  extends JpaRepository<MyUser, String> { }

使用 @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.baeldung.autoconfiguration.example" })
public class AutoconfigurationLiveTest {

    @Autowired
    private MyUserRepository userRepository;

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

由于未手动定义 DataSource 配置,应用将使用我们创建的自动配置连接 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. 禁用自动配置类

若要 阻止自动配置加载,可在配置类上添加带 excludeexcludeName 属性的 @EnableAutoConfiguration 注解:

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

也可设置 spring.autoconfigure.exclude 属性:

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

6. 总结

本文展示了如何创建自定义 Spring Boot 自动配置。JUnit 测试可通过 autoconfiguration 配置文件运行:mvn clean install -Pautoconfiguration


原始标题:A Custom Auto-Configuration with Spring Boot | Baeldung