欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文詳解SpringBoot如何創(chuàng)建自定義的自動配置

 更新時(shí)間:2025年07月22日 08:38:32   作者:alineverstop  
在實(shí)際開發(fā)中,僅靠SpringBoot的自動配置是遠(yuǎn)遠(yuǎn)不夠的,所以這篇文章主要來和大家簡單講講如何在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的條件 注解可支持如下幾種條件:

  1. 類條件注解:@ConditionalOnClass(表示某些類存在時(shí),可通過Value或 name指定所要求存在的類,value屬性是 被檢查類的 Class對象;name屬性是被檢查類的全限定類名的字符串形式)、@ConditionalOnMissingClass(某些類不存在時(shí),只能通過value屬性指定不存在的類,value屬性值只能是被檢查類的全限定類名的字符串形式)
  2. Bean條件注解 :@ConditionalOnMissingBean、@ConditionalOnSingleCandidate、@ConditionalOnBean、@ConditionalOnMissingFilterBean
  3. 屬性條件注解:@ConditionalOnProperity
  4. 資源條件注解:@ConditionalOnResource
  5. Web應(yīng)用條件注解:@ConditionalOnWebApplication、@ConditionalOnNotWebApplication、@ConditionalOnWarDeployment
  6. SpEL表達(dá)式條件注解:@ConditionalOnExpression
  7. 特殊條件注解:@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可指定 如下屬性:

  1. Class<? extends Annotation>[] annotattion:指定 要檢查的 Bean必須用該屬性指定的注解修飾
  2. Class<?>[] ignored:指定要忽略哪些類型 的Bean。該屬性及ignoredType 僅對@ConditionalOnMissingBean注解有效
  3. String[] ignoredType :與ignored屬性的作用相同,只不過該屬性用字符串形式 的全限定類名
  4. String[] name:指定要檢查的Bean的ID
  5. search:指定搜索目標(biāo)Bean的 搜索策略、支持CURRENT(僅在容器中搜索)、ACESTORS(僅在祖先容器中搜索)、ALL(在所有容器中搜索)三個(gè)枚舉值
  6. Class<?> [] value:指定要檢查的Bean的類型
  7. 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注解 用于檢查特定屬性是否具有指定的屬性值。該注解支持如下屬性:

  1. String[] value:指定要檢查的屬性
  2. String[] name:指定value屬性的別名
  3. String havingValue:被檢查屬性必須具有的屬性值
  4. String prefix:自動為各屬性名添加該屬性指定的前綴
  5. 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è)枚舉值:

  1. ANY:任何Web應(yīng)用
  2. REACTIVE:當(dāng)應(yīng)用時(shí)反應(yīng)式Web應(yīng)用時(shí)
  3. 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屬性指定要求的云平臺,支持如下枚舉值:

  1. CLOUD_FOUNDRY
  2. HEROKU
  3. KUBERNETES
  4. SAP

@ConditionalOnJava對目標(biāo)平臺的java版本進(jìn)行檢測,既可以要求java版本是某個(gè)具體的版本,也可以要求高于或低于某個(gè)版本??芍付ㄈ缦聝蓚€(gè)屬性:

  1. JavaVersion value:指定要求的java版本
  2. 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ā)自定義的自動配置很簡單,分為兩步:

  1. 使用@Configuration和條件注解自定義配置類
  2. 在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)文章

  • 如何解決mybatis查詢結(jié)果接收不同的問題

    如何解決mybatis查詢結(jié)果接收不同的問題

    這篇文章主要介紹了如何解決mybatis查詢結(jié)果接收不同的問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • spring cloud 阿波羅 apollo 本地開發(fā)環(huá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)

    本文主要介紹了SpringBoot SSE服務(wù)端主動推送事件的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • java線程同步操作實(shí)例詳解

    java線程同步操作實(shí)例詳解

    這篇文章主要介紹了java線程同步操作,結(jié)合實(shí)例形式分析了Java線程同步與鎖機(jī)制相關(guān)原理、操作技巧與注意事項(xiàng),需要的朋友可以參考下
    2018-09-09
  • Java利用FileUtils讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件

    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ī)則自定義方式

    這篇文章主要介紹了SpringBoot之groups應(yīng)對不同的Validation規(guī)則自定義方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 你知道怎么從Python角度學(xué)習(xí)Java基礎(chǔ)

    你知道怎么從Python角度學(xué)習(xí)Java基礎(chǔ)

    這篇文章主要為大家詳細(xì)介紹了Python角度學(xué)習(xí)Java基礎(chǔ)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • Java線程狀態(tài)及同步鎖的操作方法

    Java線程狀態(tài)及同步鎖的操作方法

    Java中的thread類自帶有線程的一些方法,這些方法可以讓線程睡眠,插隊(duì),提高線程調(diào)度的優(yōu)先級等等,它們提供了改變線程狀態(tài)的操作手段,這篇文章主要介紹了Java線程狀態(tài)及同步鎖,需要的朋友可以參考下
    2021-11-11
  • spring boot整合mybatis使用c3p0數(shù)據(jù)源連接mysql

    spring boot整合mybatis使用c3p0數(shù)據(jù)源連接mysql

    這篇文章主要為大家詳細(xì)介紹了spring boot整合mybatis使用c3p0數(shù)據(jù)源連接mysql,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • java基礎(chǔ)知識I/O流使用詳解

    java基礎(chǔ)知識I/O流使用詳解

    編程語言的I/O類庫中常常使用流這個(gè)抽象的概念,它代表任何有能力產(chǎn)生數(shù)據(jù)的數(shù)據(jù)源對象或時(shí)有能力接收數(shù)據(jù)的接收端對象,本文為大家介紹Java中I/O系統(tǒng)基礎(chǔ)知識
    2014-01-01

最新評論