一文詳解SpringBoot如何創(chuàng)建自定義的自動配置
在實(shí)際開發(fā)中,僅靠SpringBoot的自動配置是遠(yuǎn)遠(yuǎn)不夠的,比如要訪問多個(gè)數(shù)據(jù)源,自動配置就完全無能為力了。
自動配置的本質(zhì)
本質(zhì)就是在容器中預(yù)配置要整合的框架所需的基礎(chǔ)Bean。
以MyBatis為例,spring整合MyBatis無非就是完成以下事情:
- 配置SqlSessionFactory Bean,當(dāng)然,該Bean需要注入一個(gè)DataSource
- 配置SqlSessionTemplate Bean,將上面的SqlSessionFactory 注入該Bean
- 注冊Mapper組件的自動掃描,相當(dāng)于添加<mybatis:scan.../>元素
自動配置非常簡單,無非就是有框架提供一個(gè)@Configuration修飾的配置類(相當(dāng)于傳統(tǒng)的xml配置文件),在該配置類中用@Bean預(yù)先配置默認(rèn)的SqlSessionFactory、SqlSessionTemplate,并注冊Mapper組件的自動掃描即可。
比如MybatisAutoConfiguration源代碼:
@Configuration // 被修飾的類變成配置類
// 當(dāng)SqlSessionFactory、SqlSessionFactoryBean類存在時(shí),才會生效。
// 條件注解之一
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
// 當(dāng)DataSource Bean存在時(shí),才會生效
// 條件注解之一
@ConditionalOnSingleCandidate(DataSource.class)
// 啟用Mybatis的屬性處理類
// 啟動屬性處理類
@EnableConfigurationProperties({MybatisProperties.class})
//指定該配置類在DataSourceAutoConfiguration和MybatisLanguageDriverAutoConfiguration之后加載
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
// 實(shí)現(xiàn) InitializingBean 接口,該接口中的 afterPropertiesSet 方法會在該Bean初始化完成后被自動調(diào)用
public class MybatisAutoConfiguration implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.class);
// Mybatis的配置屬性
private final MybatisProperties properties;
// Mybatis的攔截器、類型處理器、語言驅(qū)動等
private final Interceptor[] interceptors;
private final TypeHandler[] typeHandlers;
private final LanguageDriver[] languageDrivers;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
private final List<SqlSessionFactoryBeanCustomizer> sqlSessionFactoryBeanCustomizers;
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider, ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) {
this.properties = properties;
this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
this.typeHandlers = (TypeHandler[])typeHandlersProvider.getIfAvailable();
this.languageDrivers = (LanguageDriver[])languageDriversProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable();
this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable();
this.sqlSessionFactoryBeanCustomizers = (List)sqlSessionFactoryBeanCustomizers.getIfAvailable();
}
// 在Bean初始化完成后調(diào)用該方法
public void afterPropertiesSet() {
this.checkConfigFileExists();
}
// 檢查Mybatis配置文件是否存在
private void checkConfigFileExists() {
if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
// 獲取配置文件的資源
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
// 如果resource.exists()方法返回false,則拋出異常
Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
}
}
// 創(chuàng)建SqlSessionFactory Bean
@Bean
// 當(dāng)沒有SqlSessionFactory Bean時(shí)才會創(chuàng)建
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// 創(chuàng)建SqlSessionFactoryBean實(shí)例
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
// 注入數(shù)據(jù)源
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
// 如果配置文件路徑不為空,則設(shè)置配置文件位置
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
this.applyConfiguration(factory);
// 如果配置屬性不為空,則設(shè)置配置屬性
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
// 應(yīng)用所有的攔截器
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
// 應(yīng)用所有databaseIdProvider
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
// 根據(jù)包名應(yīng)用TypeAliases
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
// 根據(jù)父類型應(yīng)用TypeAliases
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
// 根據(jù)包名應(yīng)用TypeHandler
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
// 應(yīng)用所有TypeHandler
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
// 設(shè)置mapper的加載位置
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = (Set) Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
this.applySqlSessionFactoryBeanCustomizers(factory);
// 返回SqlSessionFactory對象
return factory.getObject();
}
private void applyConfiguration(SqlSessionFactoryBean factory) {
org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new org.apache.ibatis.session.Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
Iterator var3 = this.configurationCustomizers.iterator();
while(var3.hasNext()) {
ConfigurationCustomizer customizer = (ConfigurationCustomizer)var3.next();
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
}
private void applySqlSessionFactoryBeanCustomizers(SqlSessionFactoryBean factory) {
if (!CollectionUtils.isEmpty(this.sqlSessionFactoryBeanCustomizers)) {
Iterator var2 = this.sqlSessionFactoryBeanCustomizers.iterator();
while(var2.hasNext()) {
SqlSessionFactoryBeanCustomizer customizer = (SqlSessionFactoryBeanCustomizer)var2.next();
customizer.customize(factory);
}
}
}
// 創(chuàng)建SqlSessionTemplate Bean
@Bean
// 當(dāng)沒有SqlSessionTemplate Bean時(shí)才會創(chuàng)建
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
// 如果executorType不為null,則創(chuàng)建SqlSessionTemplate時(shí)使用該executorType
return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
@Configuration
// 導(dǎo)入MapperScannerRegistrarNotFoundConfiguration注冊類
@Import({org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
// 當(dāng)MapperFactoryBean和MapperScannerConfigurer都不存在時(shí),才會生效
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
// 實(shí)現(xiàn) InitializingBean 接口,該接口中的 afterPropertiesSet 方法會在該Bean初始化完成后被自動調(diào)用
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
public MapperScannerRegistrarNotFoundConfiguration() {
}
// 重寫afterPropertiesSet方法
public void afterPropertiesSet() {
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
// 注冊Mapper掃描器的自動配置類
// 實(shí)現(xiàn) BeanFactoryAware接口可訪問spring容器、
// 實(shí)現(xiàn)ImportBeanDefinitionRegistrar 接口可配置額外的bean
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
// BeanFactory對象,用于保存Spring容器
private BeanFactory beanFactory;
private Environment environment;
public AutoConfiguredMapperScannerRegistrar() {
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
} else {
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
// 獲取自動配置要處理的包
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.logger.isDebugEnabled()) {
packages.forEach((pkg) -> {
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
});
}
// 創(chuàng)建BeanDefinitionBuilder對象
// 它幫助開發(fā)者以反射的方式創(chuàng)建任意類的實(shí)例
// 此處就是幫助創(chuàng)建MapperScannerConfigurer類的實(shí)例
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
// 為要?jiǎng)?chuàng)建的對象設(shè)置屬性
builder.addPropertyValue("processPropertyPlaceHolders", true);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Set<String> propertyNames = (Set)Stream.of(beanWrapper.getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
if (propertyNames.contains("lazyInitialization")) {
builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
}
if (propertyNames.contains("defaultScope")) {
builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
}
boolean injectSqlSession = (Boolean)this.environment.getProperty("mybatis.inject-sql-session-on-mapper-scan", Boolean.class, Boolean.TRUE);
if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory)this.beanFactory;
Optional<String> sqlSessionTemplateBeanName = Optional.ofNullable(this.getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory));
Optional<String> sqlSessionFactoryBeanName = Optional.ofNullable(this.getBeanNameForType(SqlSessionFactory.class, listableBeanFactory));
if (!sqlSessionTemplateBeanName.isPresent() && sqlSessionFactoryBeanName.isPresent()) {
builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName.get());
} else {
builder.addPropertyValue("sqlSessionTemplateBeanName", sqlSessionTemplateBeanName.orElse("sqlSessionTemplate"));
}
}
builder.setRole(2);
// 在容器中注冊BeanDefinitionBuilder創(chuàng)建的MapperScannerConfigurer對象
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
// 獲取spring容器和環(huán)境對象
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
private String getBeanNameForType(Class<?> type, ListableBeanFactory factory) {
String[] beanNames = factory.getBeanNamesForType(type);
return beanNames.length > 0 ? beanNames[0] : null;
}
}
}
開開發(fā)完自動配置類后,還需要使用META-INF/spring.factories文件來定義自動配置類,比如:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
自動配置類只能通過META-INF/spring.factories來加載,并確保它們處于一個(gè)特殊的包空間內(nèi),尤其不能讓他們變成普通@ComponentScan的目標(biāo)。此外,自動配置類不應(yīng)該使用@ComponentScan來掃描其他組件,如果需要加載其他配置文件,應(yīng)使用@Import來加載。
如果要為自動配置類指定加載順序,可使用以下的注解:
- @AutoConfigureAfter:指定被修飾的類必須在一個(gè)或多個(gè)自動配置類之后加載
- @AutoConfigureBefore:指定被修飾的類必須在一個(gè)或多個(gè)自動配置類之前加載
如果自動配置包中包含多個(gè)自動配置類,且以特定的順序來加載,可使用@AutoConfigureOrder來修飾它們,@AutoConfigureOrder類似于@Order注解,只不過專門修飾自動配置類。
條件注解
條件注解用于修飾@Configuration類 或@Bean方法等,表示只有條件有效時(shí),被修飾的 配置類或配置方法才生效。SpringBoot的條件 注解可支持如下幾種條件:
- 類條件注解:@ConditionalOnClass(表示某些類存在時(shí),可通過Value或 name指定所要求存在的類,value屬性是 被檢查類的 Class對象;name屬性是被檢查類的全限定類名的字符串形式)、@ConditionalOnMissingClass(某些類不存在時(shí),只能通過value屬性指定不存在的類,value屬性值只能是被檢查類的全限定類名的字符串形式)
- Bean條件注解 :@ConditionalOnMissingBean、@ConditionalOnSingleCandidate、@ConditionalOnBean、@ConditionalOnMissingFilterBean
- 屬性條件注解:@ConditionalOnProperity
- 資源條件注解:@ConditionalOnResource
- Web應(yīng)用條件注解:@ConditionalOnWebApplication、@ConditionalOnNotWebApplication、@ConditionalOnWarDeployment
- SpEL表達(dá)式條件注解:@ConditionalOnExpression
- 特殊條件注解:@ConditionalOnCloudPlatform、@ConditionalOnJava、@ConditionalOnJndi、@ConditionalOnRepositoryType
代碼示例:
@Configuration(proxyBeanMethods = false)
// 僅當(dāng)com.mysql.cj.jdbc.Driver類存在時(shí)該配置類生效
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public class FkConfig
{
@Bean
public MyBean myBean()
{
return new MyBean();
}
}
@ConditionalOnMissingBean、@ConditionalOnSingleCandidate、@ConditionalOnBean可指定 如下屬性:
- Class<? extends Annotation>[] annotattion:指定 要檢查的 Bean必須用該屬性指定的注解修飾
- Class<?>[] ignored:指定要忽略哪些類型 的Bean。該屬性及ignoredType 僅對@ConditionalOnMissingBean注解有效
- String[] ignoredType :與ignored屬性的作用相同,只不過該屬性用字符串形式 的全限定類名
- String[] name:指定要檢查的Bean的ID
- search:指定搜索目標(biāo)Bean的 搜索策略、支持CURRENT(僅在容器中搜索)、ACESTORS(僅在祖先容器中搜索)、ALL(在所有容器中搜索)三個(gè)枚舉值
- Class<?> [] value:指定要檢查的Bean的類型
- String[] type:與value屬性作用相同,只不過該屬性用字符串形式 的全限定類名
@ConditionalOnSingleCandidate注解相當(dāng)于@ConditionalOnMissingBean的增強(qiáng)版,不僅要求被檢查的Bean必須存在,而且只能有一個(gè)“候選者”--能滿足byType依賴注入條件。
如果@ConditionalOnMissingBean、@ConditionalOnBean注解不指定任何屬性,默認(rèn)根據(jù)目標(biāo)Bean的類型進(jìn)行檢查,默認(rèn)檢查被修飾的方法返回的Bean類型,代碼示例:
// 僅當(dāng)容器中不存在名為myService的Bean時(shí),才創(chuàng)建該Bean
@ConditionalOnMissingBean
@Bean
public MyService myService()
{
...
}
// 當(dāng)容器中不存在名為jdbcTemplate的Bean時(shí),才創(chuàng)建該Bean
@ConditionalOnMissingBean(name="jdbcTemplate")
@Bean
public JdbcTemplate JdbcTemplate()
{
...
}
@ConditionalOnMissingFilterBean相當(dāng)于@ConditionalOnMissingBean的特殊版本,專門檢查容器中是否有指定類型的javax.servlet.Filter,因此只能通過value指定要檢查的Filter的類型。
@ConditionalOnProperity注解 用于檢查特定屬性是否具有指定的屬性值。該注解支持如下屬性:
- String[] value:指定要檢查的屬性
- String[] name:指定value屬性的別名
- String havingValue:被檢查屬性必須具有的屬性值
- String prefix:自動為各屬性名添加該屬性指定的前綴
- boolean matchMissing:指定當(dāng)屬性未設(shè)置屬性值時(shí),是否通過檢查
代碼示例:
@Configuration(proxyBeanMethods = false)
public class FkConfig
{
@Bean
// 只有當(dāng)org.fkjava.test屬性具有foo屬性值時(shí),下面配置方法才會生效
@ConditionalOnProperty(name = "test", havingValue = "foo",
prefix = "org.fkjava")
public DateFormat dateFormat()
{
return DateFormat.getDateInstance();
}
}
啟動類代碼:
@SpringBootApplication
public class App
{
public static void main(String[] args)
{
// 創(chuàng)建Spring容器、運(yùn)行Spring Boot應(yīng)用
var ctx = SpringApplication.run(App.class, args);
System.out.println(ctx.getBean("dateFormat"));
}
}
此時(shí)直接運(yùn)行程序會有異常。

在application.properties文件添加如下配置:
org.fkjava.test=foo
運(yùn)行結(jié)果如下

@ConditionalOnResource的作用很簡單,它要求指定的資源必須存在,修飾的配置類才會生效。使用該注解只需指定resource屬性,該屬性指定必須存在的資源。
@ConditionalOnWebApplication要求當(dāng)前應(yīng)用必須是Web應(yīng)用時(shí),修飾 的配置類才會生效??赏ㄟ^type屬性指定Web應(yīng)用類型。該屬性支持如下三個(gè)枚舉值:
- ANY:任何Web應(yīng)用
- REACTIVE:當(dāng)應(yīng)用時(shí)反應(yīng)式Web應(yīng)用時(shí)
- SERVLET:基于servlet的Web應(yīng)用
代碼示例:
@Configuration(proxyBeanMethods = false)
public class FkConfig
{
@Bean
// 只有當(dāng)前應(yīng)用是反應(yīng)式Web應(yīng)用時(shí),該配置才會生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public DateFormat dateFormat()
{
return DateFormat.getDateInstance();
}
}
啟動類:
@SpringBootApplication
public class App
{
public static void main(String[] args)
{
var app = new SpringApplication(App.class);
// 設(shè)置Web應(yīng)用的類型,如果不設(shè)置則使用默認(rèn)的類型:
// 如果有Sping Web依賴,自動是基于Servlet的Web應(yīng)用
// 如果有Sping WebFlux依賴,自動是反應(yīng)式Web應(yīng)用
app.setWebApplicationType(WebApplicationType.REACTIVE); // ①
// 創(chuàng)建Spring容器、運(yùn)行Spring Boot應(yīng)用
var ctx = app.run(args);
System.out.println(ctx.getBean("dateFormat"));
}
}
@ConditionalOnNotWebApplication要求當(dāng)前應(yīng)用不是Web應(yīng)用時(shí),修飾的配置類或方法才生效
@ConditionalOnWarDeployment要求當(dāng)前應(yīng)用以War包部署 到Web服務(wù)器或應(yīng)用服務(wù)器中時(shí)(不以獨(dú)立的java程序的方式運(yùn)行),才生效。
@ConditionalOnNotWebApplication、@ConditionalOnWarDeployment這2個(gè)注解使用簡單,不需要指定任何屬性
@ConditionalOnExpression要求指定SpEL表達(dá)式的值為true,所修飾的配置類或方法才會生效。代碼示例:
@Configuration(proxyBeanMethods = false)
public class FkConfig
{
@Bean
public User user()
{
return new User("fkjava", true);
}
@Bean
// 只有當(dāng)user.active表達(dá)式為true時(shí),該方法才生效。也就是容器中User Bean的active屬性為true時(shí),該方法才生效
@ConditionalOnExpression("user.active")
public DateFormat dateFormat()
{
return DateFormat.getDateInstance();
}
}
@ConditionalOnCloudPlatform要求應(yīng)用被部署在特定云平臺,修飾的配置類或方法才生效??赏ㄟ^value屬性指定要求的云平臺,支持如下枚舉值:
- CLOUD_FOUNDRY
- HEROKU
- KUBERNETES
- SAP
@ConditionalOnJava對目標(biāo)平臺的java版本進(jìn)行檢測,既可以要求java版本是某個(gè)具體的版本,也可以要求高于或低于某個(gè)版本??芍付ㄈ缦聝蓚€(gè)屬性:
- JavaVersion value:指定要求的java版本
- ConditionalOnJava.Range range:該屬性支持EQUAL_OR_NEWER(大于或等于某版本)和OLDER_THAN(小于某版本)兩個(gè)枚舉值。如果不指定該屬性,則要求java版本必須是value屬性所指定的版本。
代碼示例:
@Configuration(proxyBeanMethods = false)
public class FkConfig
{
@Bean
// 只有當(dāng)目標(biāo)平臺的Java版本是11或更新的平臺時(shí),該方法才生效
@ConditionalOnJava(value = JavaVersion.ELEVEN,range = ConditionalOnJava.Range.EQUAL_OR_NEWER)
public DateFormat dateFormat()
{
return DateFormat.getDateInstance();
}
}
@ConditionalOnJndi要求指定JNDI必須存在,通過value屬性指定要檢查的JNDI。
@ConditionalOnRepositoryType要求特定的Spring Data Repository被啟用時(shí),修飾的配置類或方法才會生效。
自定義條件注解
自定義條件注解的關(guān)鍵就是要有一個(gè)Condition實(shí)現(xiàn)類,該類負(fù)責(zé)條件注解的處理邏輯--它所實(shí)現(xiàn)的matches()方法決定了條件注解的要求是否得到滿足。
代碼示例:Condition實(shí)現(xiàn)類
public class MyCondition implements Condition
{
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata)
{
// 獲取@ConditionalCustom注解的全部屬性
Map<String, Object> map = metadata.getAnnotationAttributes(
ConditionalCustom.class.getName());
// 獲取注解的value屬性值(String[]數(shù)組)
String[] vals = (String[]) map.get("value");
Environment env = context.getEnvironment();
// 遍歷每個(gè)屬性值
for (Object val : vals)
{
// 如果某個(gè)屬性值對應(yīng)的配置屬性不存在,返回false
if (env.getProperty(val.toString()) == null)
{
return false;
}
}
return true;
}
}
此處邏輯是要求value屬性所指定的所有配置屬性必須存在,至于屬性值是什么無所謂,這些屬性是否有值也無所謂。
自定義條件注解的代碼:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 指定Conditional的實(shí)現(xiàn)類
@Conditional(MyCondition.class)
public @interface ConditionalCustom
{
String[] value() default {};
}
使用自定義條件注解:
@Configuration(proxyBeanMethods = false)
public class FkConfig
{
@Bean
// 只有當(dāng)org.fkjava.test和org.crazyit.abc兩個(gè)配置屬性存在時(shí)該方法才生效
@ConditionalCustom({"org.fkjava.test", "org.crazyit.abc"})
public DateFormat dateFormat()
{
return DateFormat.getDateInstance();
}
}
自定義自動配置
開發(fā)自定義的自動配置很簡單,分為兩步:
- 使用@Configuration和條件注解自定義配置類
- 在META-INF/spring.factories文件中注冊自動配置類
為了演示,先自行開發(fā)一個(gè)funny框架,功能是用文件或數(shù)據(jù)庫保存程序輸出信息。
先新建一個(gè)maven項(xiàng)目,pom.xml如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.crazyit</groupId>
<artifactId>funny</artifactId>
<version>1.0-SNAPSHOT</version>
<name>funny</name>
<properties>
<!-- 定義所使用的Java版本和源代碼所用的字符集 -->
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- MySQL驅(qū)動依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
開發(fā)WriterTemplate類
public class WriterTemplate
{
Logger log = LoggerFactory.getLogger(this.getClass());
private final DataSource dataSource;
private Connection conn;
private final File dest;
private final Charset charset;
private RandomAccessFile raf;
public WriterTemplate(DataSource dataSource) throws SQLException
{
this.dataSource = dataSource;
this.dest = null;
this.charset = null;
if (Objects.nonNull(this.dataSource))
{
log.debug("==========獲取數(shù)據(jù)庫連接==========");
this.conn = dataSource.getConnection();
}
}
public WriterTemplate(File dest, Charset charset) throws FileNotFoundException
{
this.dest = dest;
this.charset = charset;
this.dataSource = null;
this.raf = new RandomAccessFile(this.dest, "rw");
}
public void write(String message) throws IOException, SQLException
{
if (Objects.nonNull(this.conn))
{
// 查詢當(dāng)前數(shù)據(jù)庫的funny_message表是否存在
ResultSet rs = conn.getMetaData().getTables(conn.getCatalog(), null,
"funny_message", null);
// 如果funny_message表不存在
if (!rs.next())
{
log.debug("~~~~~~創(chuàng)建funny_message表~~~~~~");
conn.createStatement().execute("create table funny_message " +
"(id int primary key auto_increment, message_text text)");
rs.close();
}
log.debug("~~~~~~輸出到數(shù)據(jù)表~~~~~~");
// 插入要輸出的字符串
conn.createStatement().executeUpdate("insert into " +
"funny_message values (null, '" + message + "')");
}
else
{
log.debug("~~~~~~輸出到文件~~~~~~");
// 輸出到文件
raf.seek(this.dest.length());
raf.write((message + "\n").getBytes(this.charset));
}
}
// 關(guān)閉資源
public void close() throws SQLException, IOException
{
if (this.conn != null)
{
this.conn.close();
}
if (this.raf != null)
{
this.raf.close();
}
}
}
然后使用 mvn install 命令打成jar包并安裝到本地資源庫。
在Starter的項(xiàng)目中引入上面的jar包。pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 指定繼承spring-boot-starter-parent POM文件 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/>
</parent>
<!-- 定義基本的項(xiàng)目信息 -->
<groupId>org.crazyit</groupId>
<artifactId>funny-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>funny-spring-boot-starter</name>
<properties>
<!-- 定義所使用的Java版本和源代碼所用的字符集 -->
<java.version>11</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Boot Starter依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 依賴自定義的funny框架 -->
<dependency>
<groupId>org.crazyit</groupId>
<artifactId>funny</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
然后在開發(fā)中編寫自定義配置類:
@Configuration
// 當(dāng)WriterTemplate類存在時(shí)配置生效
// WriterTemplate類是自己編寫的工具項(xiàng)目中的類
@ConditionalOnClass(WriterTemplate.class)
// 啟用FunnyProperties屬性處理類
@EnableConfigurationProperties(FunnyProperties.class)
// 讓該自動配置位于DataSourceAutoConfiguration自動配置之后處理
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class FunnyAutoConfiguration
{
// FunnyProperties類負(fù)責(zé)加載配置屬性
private final FunnyProperties properties;
public FunnyAutoConfiguration(FunnyProperties properties)
{
this.properties = properties;
}
@Bean(destroyMethod = "close")
// 當(dāng)單例的DataSource Bean存在時(shí)配置生效
@ConditionalOnSingleCandidate(DataSource.class)
// 只有當(dāng)容器中沒有WriterTemplate Bean時(shí),該配置才會生效
@ConditionalOnMissingBean
// 通過@AutoConfigureOrder注解指定該配置方法
// 比下一個(gè)配置WriterTemplate的方法的優(yōu)先級更高
// @AutoConfigureOrder 數(shù)值越小,優(yōu)先級越高
@AutoConfigureOrder(99)
public WriterTemplate writerTemplate(DataSource dataSource) throws SQLException
{
return new WriterTemplate(dataSource);
}
@Bean(destroyMethod = "close")
// 只有當(dāng)前面的WriterTemplate配置沒有生效時(shí),該方法的配置才會生效
@ConditionalOnMissingBean
@AutoConfigureOrder(199)
public WriterTemplate writerTemplate2() throws FileNotFoundException
{
File f = new File(this.properties.getDest());
Charset charset = Charset.forName(this.properties.getCharset());
return new WriterTemplate(f, charset);
}
}
上面代碼中的FunnyProperties類
// 定義屬性處理類
@ConfigurationProperties(prefix = FunnyProperties.FUNNY_PREFIX)
public class FunnyProperties
{
public static final String FUNNY_PREFIX = "org.crazyit.funny";
private String dest;
private String charset;
// 省略getter、setter
}
接下來在META-INF/spring.factories文件中注冊自動配置類
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.crazyit.funny.autoconfigure.FunnyAutoConfiguration
然后使用 mvn install 命令打成jar包,并安裝到maven本地資源庫中,就會在自己的本地資源庫中找到該jar包,這樣就完成了自定義配置的實(shí)現(xiàn)。
創(chuàng)建自定義的Starter
一個(gè)完整的SpringBoot Starter包含一下兩個(gè)組件:
- 自動配置模塊(auto-configure):包含自動配置類和spring.factories文件
- Starter模塊:負(fù)責(zé)管理自動配置模塊和第三方依賴。簡而言之,添加本Starter就能使用該自動配置。
由此看出,Starter不包含任何Class文件,只管理愿意來。如果查看官方提供的jar就會發(fā)現(xiàn),它所有自動配置類的Class都由spring-boot-autoconfigure.jar提供,而各個(gè)xxx-starter.jar并未提供任何Class文件,只是在這些jar下的相同路徑下提供了一個(gè)xxx-starter.pom文件,該文件指定Starter管理的自動依賴模塊和第三方依賴。
SpringBoot為自動配置包和Starter包提供推薦命名
- 自動配置包的推薦名:xxx-spring-boot
- Starter包的推薦名:xxx-spring-boot-starter
對于第三方Starter不要使用spring-boot-starter-xxx這種方式,這是官方使用的。
有了自定義的Starter后,使用起來和官方的沒有區(qū)別,比如
添加依賴:
<!-- 自定義的funny-spring-boot-starter依賴 --> <dependency> <groupId>org.crazyit</groupId> <artifactId>funny-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
在application.properties文件添加配置
org.crazyit.funny.dest=f:/abc-98765.txt org.crazyit.funny.charset=UTF-8 # 指定連接數(shù)據(jù)庫的信息 spring.datasource.url=jdbc:mysql://localhost:3306/funny?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=32147 # 配置funny框架的日志級別為debug logging.level.org.crazyit.funny = debug
主類的代碼:
@SpringBootApplication
public class App
{
public static void main(String[] args) throws IOException, SQLException
{
// 創(chuàng)建Spring容器、運(yùn)行Spring Boot應(yīng)用
var ctx = SpringApplication.run(App.class, args);
// 獲取自動配置的WriterTemplate
WriterTemplate writerTemplate = ctx.getBean(WriterTemplate.class);
writerTemplate.write("自動配置其實(shí)很簡單");
}
}
以上就是一文詳解SpringBoot如何創(chuàng)建自定義的自動配置的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot自動配置的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
spring cloud 阿波羅 apollo 本地開發(fā)環(huán)境搭建過程
Apollo(阿波羅)是攜程框架部門研發(fā)的配置管理平臺,能夠集中化管理應(yīng)用不同環(huán)境、不同集群的配置,配置修改后能夠?qū)崟r(shí)推送到應(yīng)用端,并且具備規(guī)范的權(quán)限、流程治理等特性2018-01-01
SpringBoot SSE服務(wù)端主動推送事件的實(shí)現(xiàn)
本文主要介紹了SpringBoot SSE服務(wù)端主動推送事件的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
Java利用FileUtils讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件
這篇文章主要介紹了Java利用FileUtils讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件,下面文章圍繞FileUtils的相關(guān)資料展開怎么讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件的內(nèi)容,具有一定的參考價(jià)值,徐婭奧德小伙伴可以參考一下2021-12-12
SpringBoot之groups應(yīng)對不同的Validation規(guī)則自定義方式
這篇文章主要介紹了SpringBoot之groups應(yīng)對不同的Validation規(guī)則自定義方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
你知道怎么從Python角度學(xué)習(xí)Java基礎(chǔ)
這篇文章主要為大家詳細(xì)介紹了Python角度學(xué)習(xí)Java基礎(chǔ)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02
spring boot整合mybatis使用c3p0數(shù)據(jù)源連接mysql
這篇文章主要為大家詳細(xì)介紹了spring boot整合mybatis使用c3p0數(shù)據(jù)源連接mysql,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03

