1. 概述
簡單來説,Spring Boot的自動配置功能可以根據類路徑上存在的依賴自動配置一個Spring應用程序。
這可以加快和簡化開發過程,避免了定義某些在自動配置類中包含的Bean的需求。
在下一部分,我們將學習如何創建自定義的Spring Boot自動配置。
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>從 Maven Central 可以下載最新版本的 spring-boot-starter-data-jpa 和 mysql-connector-java。
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,它將覆蓋默認的 Bean。
3.1. 類條件
類條件允許我們使用 <em @ConditionalOnClass</em> 註解指定,如果存在指定的類,則包含一個配置 Bean;或者使用 <em @ConditionalOnMissingClass</em> 註解指定,如果類不存在時。
例如,我們可以指定 <em MySQLConfiguration</em> 只在 <em DataSource</em> 類存在時加載,從而推斷應用程序將使用數據庫:
@Configuration
@ConditionalOnClass(DataSource.class)
public class MySQLAutoconfiguration {
//...
}3.2. Bean 條件
如果希望僅在指定 Bean 存在或不存在時才包含 Bean,可以使用 <em>@ConditionalOnBean</em> 和 <em>@ConditionalOnMissingBean</em> 註解。
為了説明這一點,讓我們將 <em>entityManagerFactory</em> Bean 添加到我們的配置類中。
首先,我們將指定僅在名為 <em>dataSource</em> 的 Bean 存在的情況下才創建此 Bean,並且 <em>entityManagerFactory</em> 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 環境中屬性的存在和值加載。
首先,讓我們添加一個用於確定屬性讀取位置的屬性源文件:
@PropertySource("classpath:mysql.properties")
public class MySQLAutoconfiguration {
//...
}我們可以配置主 DataSource Bean,以便在 usemysql 屬性存在時才加載連接到數據庫的連接。
我們可以使用 havingValue 屬性來指定必須匹配的 usemysql 屬性的值。
現在,讓我們定義 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;
}如果我們將 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.url、mysql.user 和 mysql.pass 屬性添加不同的值,以及在 mysql.properties 文件中的 usemysql=custom 行進行修改。
3.4. 資源條件
添加 <em/>@ConditionalOnResource</em/> 註解意味着配置僅在指定的資源存在時才加載。
定義一個名為 <em/>additionalProperties()</em/> 的方法,該方法將返回一個 <em/>Properties</em/> 對象,其中包含用於entityManagerFactory</em/> bean 的 Hibernate 專用屬性,僅當資源文件 <em/>mysql.properties</em/> 存在時才使用:
@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-drop3.5. 自定義條件
假設我們不想使用 Spring Boot 中提供的任何條件。
我們可以通過擴展 SpringBootCondition 類並覆蓋 getMatchOutcome() 方法來定義自定義條件。
讓我們為我們的 additionalProperties() 方法創建一個名為 HibernateCondition 的條件,該條件將驗證 classpath 上是否存在 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 的實體類,以及使用 Spring Data 創建的 MyUserRepository 接口:
@Entity
public class MyUser {
@Id
private String email;
// standard constructor, getters, setters
}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 的 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.Driver5. 取消自動配置類
假設我們想排除自動配置的加載。
我們可以通過在配置類上添加 @EnableAutoConfiguration 註解,並使用 exclude 或 excludeName 屬性來完成:
@Configuration
@EnableAutoConfiguration(
exclude={MySQLAutoconfiguration.class})
public class AutoconfigurationApplication {
//...
}我們還可以設置 spring.autoconfigure.exclude 屬性:
spring.autoconfigure.exclude=com.baeldung.autoconfiguration.MySQLAutoconfiguration6. 結論
在本文中,我們展示瞭如何創建自定義 Spring Boot 自定義配置。
可以使用 autoconfiguration 配置文件運行 JUnit 測試:mvn clean install -Pautoconfiguration。