SpringBoot——自定義自動配置與起步依賴

JavaCoderPan發表於2023-03-17

SpringBoot——自定義自動配置與起步依賴

SpringBoot為我們提供了靈活強大的自動配置與起步依賴功能,接下來我們參考其實現原理,實現專屬於我們自己的自動配置與起步依賴。
不僅如此,我們對其稍作修改,讓它適用於非SpringBoot環境,甚至是低版本的Spring Framework環境

1自動配置

在編寫自己的自動配置之前,我們先來看一下SpringBoot自動配置類的實現原理。

SpringBoot可以根據CLASSPATH、配置項等條件自動進行常規配置,省去了我們自己手動把一模一樣的配置複製來複制去的麻煩。這樣大大提升了開發效率!
在這之前,我們已經看到過@SpringBootApplication註解了,檢視這個註解,可以發現它上面新增了一個@EnableAutoConfiguration,基於這個註解就可以開啟自動配置功能。
這兩個註解上都有exclude屬性,我們可以在其中排除一些不想啟用的自動配置類。
如果不想啟用自動配置功能,也可以在配置檔案中配置spring.boot.enableautoconfiguration=false,關閉該功能。

1.1自動配置的實現原理

自動配置類其實就是新增了@Configuration的普通Java配置類,它利用Spring Framework 4.0加入的條件註解@Conditional來實現“根據特定條件啟用相關配置類”,註解中傳入的Condition類就是不同條件的判斷邏輯。SpringBoot內建了很多條件註解,如下表所示:

條件註解 生效條件
@ConditionalOnBean 存在特定名稱、特定型別、特定泛型引數或帶有特定註解的Bean
@ConditionalOnMissingBean 與前者相反,不存在特定的Bean
@ConditionalOnClass 存在特定的類
@ConditionalOnMissingClass 與前者相反,不存在特定類
@ConditionalOnCloudPlatform 執行在特定的雲平臺上,截止2.6.3版本,代表雲平臺的列舉類支援無雲平臺,可以透過spring.main.cloud-platform配置強制使用的雲平臺
@ConditionalOnExpression 指定的SpEL表示式為真
@ConditionalOnJava 執行在滿足條件的Java上,可以比指定版本新,也可以比指定版本舊
@ConditionalOnJndi 指定的JNDI位置必須存在一個,如沒有指定,則需要存在InitalContext
@ConditionalOnProperty 屬性值滿足特定條件,比如給定的屬性值都不能為false
@ConditionalOnResource 存在特定資源
@ConditionalOnSingleCandidate 當前上下文中,特定型別的Bean有且僅有一個
@ConditionalOnWarDeployment 應用程式是透過傳統的War方式部署的,而非內嵌容器
@ConditionalOnWebApplication 應用程式是一個Web應用程式
@ConditionalOnNotWebApplication 與前者相反,應用程式不是一個Web應用程式

?更多如圖所示,就不一一列舉了:
image

以@ConditionalOnClass註解為例,它的定義如下所示,@Target指明該註解可用於型別和方法定義,@Retention指明註解的資訊在執行時也能獲取到,而其中最關鍵的就是OnClassCondition條件類,裡面是具體的條件計算邏輯:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

	/**
	 * The classes that must be present. Since this annotation is parsed by loading class
	 * bytecode, it is safe to specify classes here that may ultimately not be on the
	 * classpath, only if this annotation is directly on the affected component and
	 * <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
	 * use this annotation as a meta-annotation, only use the {@link #name} attribute.
	 * @return the classes that must be present
	 */
	Class<?>[] value() default {};

	/**
	 * The classes names that must be present.
	 * @return the class names that must be present.
	 */
	String[] name() default {};

}

瞭解了條件註解後,再來看看它們是如何與配置類結合使用的。以JdbcTemplateAutoConfiguration為例:


@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class,
		NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {
}

可以看到這個配置類的生效條件是存在DataSource和JdbcTemplate類,且在上下文中只能有一個DataSource。此外,這個自動配置需要在DataSourceAutoConfiguration之後再配置(可以用@AutoConfigureBefore、@AutoConfigureAfter和@AutoConfigureOrder來控制自動配置的順序)

2編寫自已的自動配置

根據上面的描述,我們很容易想到,要編寫自己的自動配置,只需要以下三個步驟:
(1)編寫常規的配置類
(2)為配置類增加生效條件與順序
(3)在/META-INF/spring.factories檔案中新增自動配置類

2.1建立專案

在Spring Initializr中,建立一個Maven工程,新增如下依賴:

    <dependencies>
        <!--    新增自動配置所使用的包    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <!--    使用者在寫配置檔案時,會有提示效果    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2.2編寫一個簡單的MyAutoConfigure類和配置類Bean

編寫一個簡單的MyAutoConfigure類,上面增加了@Configuration註解,表示這是一個配置類,這個配置類的生效條件是myconfig.ready屬性的值為true,除此之外的值或者不存在該屬性時MyAutoConfigure都不會生效。

/**
 * TODO 1、編寫配置類並指定檔案
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/17 21:24
 */
@Configuration

@EnableConfigurationProperties(CommonBean.class)
@ConditionalOnProperty(name = "myconfig.ready",havingValue = "true")
public class MyAutoConfigure {
}

/**
 * TODO 2、建立配置類Bean
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/3/17 22:40
 */
@ConfigurationProperties("myconfigbean")
@Data
public class MyConfigBean {
    private boolean ready;
    private String info;
}

2.3配置spring.factories檔案

為了讓SpringBoot能找到我們寫的這個配置類,我們需要在src/resources目錄中建立META-INF/spring.factories檔案,其內容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.pp.myspringbootstart.beans.MyAutoConfigure

2.4測試自動配置是否生效

@SpringBootTest
public class MyConfigurationEnableTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void testPropertiesBeanAvailableTest() throws Exception {
        assertNotNull(applicationContext.getBean(MyConfigBean.class));
        assertTrue(applicationContext.containsBean("org.pp.myspringbootstart.beans.MyConfigBean"));
    }

    @Test
    void testPropertyValues(){
        MyConfigBean bean = applicationContext.getBean(MyConfigBean.class);
        assertTrue(bean.isReady());
        assertEquals("hello", bean.getInfo());
    }
}