最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時(shí)間:2025年07月22日 08:38:32   作者:alineverstop  
在實(shí)際開(kāi)發(fā)中,僅靠SpringBoot的自動(dòng)配置是遠(yuǎn)遠(yuǎn)不夠的,所以這篇文章主要來(lái)和大家簡(jiǎn)單講講如何在SpringBoot創(chuàng)建自定義的自動(dòng)配置吧

在實(shí)際開(kāi)發(fā)中,僅靠SpringBoot的自動(dòng)配置是遠(yuǎn)遠(yuǎn)不夠的,比如要訪問(wèn)多個(gè)數(shù)據(jù)源,自動(dòng)配置就完全無(wú)能為力了。

自動(dòng)配置的本質(zhì)

本質(zhì)就是在容器中預(yù)配置要整合的框架所需的基礎(chǔ)Bean。

以MyBatis為例,spring整合MyBatis無(wú)非就是完成以下事情:

  • 配置SqlSessionFactory Bean,當(dāng)然,該Bean需要注入一個(gè)DataSource
  • 配置SqlSessionTemplate Bean,將上面的SqlSessionFactory 注入該Bean
  • 注冊(cè)Mapper組件的自動(dòng)掃描,相當(dāng)于添加<mybatis:scan.../>元素

自動(dòng)配置非常簡(jiǎn)單,無(wú)非就是有框架提供一個(gè)@Configuration修飾的配置類(相當(dāng)于傳統(tǒng)的xml配置文件),在該配置類中用@Bean預(yù)先配置默認(rèn)的SqlSessionFactory、SqlSessionTemplate,并注冊(cè)Mapper組件的自動(dòng)掃描即可。

比如MybatisAutoConfiguration源代碼:

@Configuration // 被修飾的類變成配置類
// 當(dāng)SqlSessionFactory、SqlSessionFactoryBean類存在時(shí),才會(huì)生效。
// 條件注解之一
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) 
// 當(dāng)DataSource Bean存在時(shí),才會(huì)生效
// 條件注解之一
@ConditionalOnSingleCandidate(DataSource.class)
// 啟用Mybatis的屬性處理類
// 啟動(dòng)屬性處理類
@EnableConfigurationProperties({MybatisProperties.class})
//指定該配置類在DataSourceAutoConfiguration和MybatisLanguageDriverAutoConfiguration之后加載
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
// 實(shí)現(xiàn) InitializingBean 接口,該接口中的 afterPropertiesSet 方法會(huì)在該Bean初始化完成后被自動(dòng)調(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的攔截器、類型處理器、語(yǔ)言驅(qū)動(dòng)等
    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)沒(méi)有SqlSessionFactory Bean時(shí)才會(huì)創(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對(duì)象
        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)沒(méi)有SqlSessionTemplate Bean時(shí)才會(huì)創(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注冊(cè)類
    @Import({org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
    // 當(dāng)MapperFactoryBean和MapperScannerConfigurer都不存在時(shí),才會(huì)生效
    @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
    // 實(shí)現(xiàn) InitializingBean 接口,該接口中的 afterPropertiesSet 方法會(huì)在該Bean初始化完成后被自動(dòng)調(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.");
        }
    }

    // 注冊(cè)Mapper掃描器的自動(dòng)配置類
    // 實(shí)現(xiàn) BeanFactoryAware接口可訪問(wèn)spring容器、
    // 實(shí)現(xiàn)ImportBeanDefinitionRegistrar 接口可配置額外的bean
    public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
        // BeanFactory對(duì)象,用于保存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");
               // 獲取自動(dòng)配置要處理的包
                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對(duì)象
                // 它幫助開(kāi)發(fā)者以反射的方式創(chuàng)建任意類的實(shí)例
                // 此處就是幫助創(chuàng)建MapperScannerConfigurer類的實(shí)例
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
               // 為要?jiǎng)?chuàng)建的對(duì)象設(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);
                // 在容器中注冊(cè)BeanDefinitionBuilder創(chuàng)建的MapperScannerConfigurer對(duì)象
                registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
            }
        }

        // 獲取spring容器和環(huán)境對(duì)象
        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;
        }
    }
}

開(kāi)開(kāi)發(fā)完自動(dòng)配置類后,還需要使用META-INF/spring.factories文件來(lái)定義自動(dòng)配置類,比如:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

自動(dòng)配置類只能通過(guò)META-INF/spring.factories來(lái)加載,并確保它們處于一個(gè)特殊的包空間內(nèi),尤其不能讓他們變成普通@ComponentScan的目標(biāo)。此外,自動(dòng)配置類不應(yīng)該使用@ComponentScan來(lái)掃描其他組件,如果需要加載其他配置文件,應(yīng)使用@Import來(lái)加載。

如果要為自動(dòng)配置類指定加載順序,可使用以下的注解:

  • @AutoConfigureAfter:指定被修飾的類必須在一個(gè)或多個(gè)自動(dòng)配置類之后加載
  • @AutoConfigureBefore:指定被修飾的類必須在一個(gè)或多個(gè)自動(dòng)配置類之前加載

如果自動(dòng)配置包中包含多個(gè)自動(dòng)配置類,且以特定的順序來(lái)加載,可使用@AutoConfigureOrder來(lái)修飾它們,@AutoConfigureOrder類似于@Order注解,只不過(guò)專門修飾自動(dòng)配置類。

條件注解

條件注解用于修飾@Configuration類 或@Bean方法等,表示只有條件有效時(shí),被修飾的 配置類或配置方法才生效。SpringBoot的條件 注解可支持如下幾種條件:

  1. 類條件注解:@ConditionalOnClass(表示某些類存在時(shí),可通過(guò)Value或 name指定所要求存在的類,value屬性是 被檢查類的 Class對(duì)象;name屬性是被檢查類的全限定類名的字符串形式)、@ConditionalOnMissingClass(某些類不存在時(shí),只能通過(guò)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 僅對(duì)@ConditionalOnMissingBean注解有效
  3. String[] ignoredType :與ignored屬性的作用相同,只不過(guò)該屬性用字符串形式 的全限定類名
  4. String[] name:指定要檢查的Bean的ID
  5. search:指定搜索目標(biāo)Bean的 搜索策略、支持CURRENT(僅在容器中搜索)、ACESTORS(僅在祖先容器中搜索)、ALL(在所有容器中搜索)三個(gè)枚舉值
  6. Class<?> [] value:指定要檢查的Bean的類型
  7. String[] type:與value屬性作用相同,只不過(guò)該屬性用字符串形式 的全限定類名

@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,因此只能通過(guò)value指定要檢查的Filter的類型。

@ConditionalOnProperity注解 用于檢查特定屬性是否具有指定的屬性值。該注解支持如下屬性:

  1. String[] value:指定要檢查的屬性
  2. String[] name:指定value屬性的別名
  3. String havingValue:被檢查屬性必須具有的屬性值
  4. String prefix:自動(dòng)為各屬性名添加該屬性指定的前綴
  5. boolean matchMissing:指定當(dāng)屬性未設(shè)置屬性值時(shí),是否通過(guò)檢查

代碼示例:

@Configuration(proxyBeanMethods = false)
public class FkConfig
{
   @Bean
   // 只有當(dāng)org.fkjava.test屬性具有foo屬性值時(shí),下面配置方法才會(huì)生效
   @ConditionalOnProperty(name = "test", havingValue = "foo",
         prefix = "org.fkjava")
   public DateFormat dateFormat()
   {
      return DateFormat.getDateInstance();
   }
}

啟動(dòng)類代碼:

@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)行程序會(huì)有異常。

在application.properties文件添加如下配置:

org.fkjava.test=foo

運(yùn)行結(jié)果如下

@ConditionalOnResource的作用很簡(jiǎn)單,它要求指定的資源必須存在,修飾的配置類才會(huì)生效。使用該注解只需指定resource屬性,該屬性指定必須存在的資源。

@ConditionalOnWebApplication要求當(dāng)前應(yīng)用必須是Web應(yīng)用時(shí),修飾 的配置類才會(huì)生效??赏ㄟ^(guò)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í),該配置才會(huì)生效
   @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
   public DateFormat dateFormat()
   {
      return DateFormat.getDateInstance();
   }
}

啟動(dòng)類:

@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依賴,自動(dòng)是基于Servlet的Web應(yīng)用
      // 如果有Sping WebFlux依賴,自動(dòng)是反應(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è)注解使用簡(jiǎn)單,不需要指定任何屬性

@ConditionalOnExpression要求指定SpEL表達(dá)式的值為true,所修飾的配置類或方法才會(huì)生效。代碼示例:

@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)用被部署在特定云平臺(tái),修飾的配置類或方法才生效。可通過(guò)value屬性指定要求的云平臺(tái),支持如下枚舉值:

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

@ConditionalOnJava對(duì)目標(biāo)平臺(tái)的java版本進(jìn)行檢測(cè),既可以要求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)平臺(tái)的Java版本是11或更新的平臺(tái)時(shí),該方法才生效
   @ConditionalOnJava(value = JavaVersion.ELEVEN,range = ConditionalOnJava.Range.EQUAL_OR_NEWER)
   public DateFormat dateFormat()
   {
      return DateFormat.getDateInstance();
   }
}

@ConditionalOnJndi要求指定JNDI必須存在,通過(guò)value屬性指定要檢查的JNDI。

@ConditionalOnRepositoryType要求特定的Spring Data Repository被啟用時(shí),修飾的配置類或方法才會(huì)生效。

自定義條件注解

自定義條件注解的關(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è)屬性值對(duì)應(yīng)的配置屬性不存在,返回false
         if (env.getProperty(val.toString()) == null)
         {
            return false;
         }
      }
      return true;
   }
}

此處邏輯是要求value屬性所指定的所有配置屬性必須存在,至于屬性值是什么無(wú)所謂,這些屬性是否有值也無(wú)所謂。

自定義條件注解的代碼:

@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();
   }
}

自定義自動(dòng)配置

開(kāi)發(fā)自定義的自動(dòng)配置很簡(jiǎn)單,分為兩步:

  1. 使用@Configuration和條件注解自定義配置類
  2. 在META-INF/spring.factories文件中注冊(cè)自動(dòng)配置類

為了演示,先自行開(kāi)發(fā)一個(gè)funny框架,功能是用文件或數(shù)據(jù)庫(kù)保存程序輸出信息。

先新建一個(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ū)動(dòng)依賴 -->
      <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>

開(kāi)發(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ù)庫(kù)連接==========");
         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ù)庫(kù)的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包并安裝到本地資源庫(kù)。

在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>

然后在開(kāi)發(fā)中編寫自定義配置類:

@Configuration
// 當(dāng)WriterTemplate類存在時(shí)配置生效
// WriterTemplate類是自己編寫的工具項(xiàng)目中的類
@ConditionalOnClass(WriterTemplate.class)
// 啟用FunnyProperties屬性處理類
@EnableConfigurationProperties(FunnyProperties.class)
// 讓該自動(dòng)配置位于DataSourceAutoConfiguration自動(dòng)配置之后處理
@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)容器中沒(méi)有WriterTemplate Bean時(shí),該配置才會(huì)生效
   @ConditionalOnMissingBean
   // 通過(guò)@AutoConfigureOrder注解指定該配置方法
   // 比下一個(gè)配置WriterTemplate的方法的優(yōu)先級(jí)更高
    // @AutoConfigureOrder 數(shù)值越小,優(yōu)先級(jí)越高
   @AutoConfigureOrder(99)
   public WriterTemplate writerTemplate(DataSource dataSource) throws SQLException
   {
      return new WriterTemplate(dataSource);
   }

   @Bean(destroyMethod = "close")
   // 只有當(dāng)前面的WriterTemplate配置沒(méi)有生效時(shí),該方法的配置才會(huì)生效
   @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
}

接下來(lái)在META-INF/spring.factories文件中注冊(cè)自動(dòng)配置類

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.crazyit.funny.autoconfigure.FunnyAutoConfiguration

然后使用 mvn install 命令打成jar包,并安裝到maven本地資源庫(kù)中,就會(huì)在自己的本地資源庫(kù)中找到該jar包,這樣就完成了自定義配置的實(shí)現(xiàn)。

創(chuàng)建自定義的Starter

一個(gè)完整的SpringBoot Starter包含一下兩個(gè)組件:

  • 自動(dòng)配置模塊(auto-configure):包含自動(dòng)配置類和spring.factories文件
  • Starter模塊:負(fù)責(zé)管理自動(dòng)配置模塊和第三方依賴。簡(jiǎn)而言之,添加本Starter就能使用該自動(dòng)配置。

由此看出,Starter不包含任何Class文件,只管理愿意來(lái)。如果查看官方提供的jar就會(huì)發(fā)現(xiàn),它所有自動(dòng)配置類的Class都由spring-boot-autoconfigure.jar提供,而各個(gè)xxx-starter.jar并未提供任何Class文件,只是在這些jar下的相同路徑下提供了一個(gè)xxx-starter.pom文件,該文件指定Starter管理的自動(dòng)依賴模塊和第三方依賴。

SpringBoot為自動(dòng)配置包和Starter包提供推薦命名

  • 自動(dòng)配置包的推薦名:xxx-spring-boot
  • Starter包的推薦名:xxx-spring-boot-starter

對(duì)于第三方Starter不要使用spring-boot-starter-xxx這種方式,這是官方使用的。

有了自定義的Starter后,使用起來(lái)和官方的沒(méi)有區(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ù)庫(kù)的信息
spring.datasource.url=jdbc:mysql://localhost:3306/funny?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=32147
# 配置funny框架的日志級(jí)別為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);
      // 獲取自動(dòng)配置的WriterTemplate
      WriterTemplate writerTemplate = ctx.getBean(WriterTemplate.class);
      writerTemplate.write("自動(dòng)配置其實(shí)很簡(jiǎn)單");
   }
}

以上就是一文詳解SpringBoot如何創(chuàng)建自定義的自動(dòng)配置的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot自動(dòng)配置的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java如何使用自己的maven本地倉(cāng)庫(kù)詳解

    java如何使用自己的maven本地倉(cāng)庫(kù)詳解

    這篇文章主要給大家介紹了關(guān)于java如何使用自己的maven本地倉(cāng)庫(kù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • 詳解Netty編碼器和解碼器

    詳解Netty編碼器和解碼器

    很多小伙伴對(duì)Netty編解碼器這方面不是很了解,今天這篇文章給大家詳細(xì)介紹了Netty編碼器和解碼器的相關(guān)知識(shí),需要的朋友可以參考下
    2021-06-06
  • SpringBoot調(diào)用WebService接口的實(shí)現(xiàn)示例

    SpringBoot調(diào)用WebService接口的實(shí)現(xiàn)示例

    本文主要介紹了SpringBoot調(diào)用WebService接口的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-03-03
  • SpringBoot Kafka 整合使用及安裝教程

    SpringBoot Kafka 整合使用及安裝教程

    這篇文章主要介紹了SpringBoot Kafka 整合使用及安裝教程,本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下
    2019-07-07
  • SpringBoot 整合jdbc和mybatis的方法

    SpringBoot 整合jdbc和mybatis的方法

    該文章主要為記錄如何在SpringBoot項(xiàng)目中整合JDBC和MyBatis,在整合中我會(huì)使用簡(jiǎn)單的用法和測(cè)試用例,感興趣的朋友跟隨小編一起看看吧
    2019-11-11
  • springboot實(shí)現(xiàn)SSE(Server?Sent?Event)的示例代碼

    springboot實(shí)現(xiàn)SSE(Server?Sent?Event)的示例代碼

    SSE?全稱Server?Sent?Event,直譯一下就是服務(wù)器發(fā)送事件,本文主要為大家詳細(xì)介紹了springboot實(shí)現(xiàn)SSE的相關(guān)知識(shí),需要的可以參考一下
    2024-04-04
  • 新版SpringBoot無(wú)法主動(dòng)讀取bootstrap.yml的原因和解決方法

    新版SpringBoot無(wú)法主動(dòng)讀取bootstrap.yml的原因和解決方法

    在使用新版 Springboot 搭建微服務(wù)時(shí) 發(fā)現(xiàn)配置數(shù)據(jù)源失敗,依賴、配置、注解等所示所有均為正確,所以本文給大家介紹了新版SpringBoot無(wú)法主動(dòng)讀取bootstrap.yml的原因和解決方案,需要的朋友可以參考下
    2025-01-01
  • 被遺忘的Java關(guān)鍵字transient的使用詳解

    被遺忘的Java關(guān)鍵字transient的使用詳解

    在 Java 中,transient 是一個(gè)關(guān)鍵字,用于指定一個(gè)類的字段(成員變量)在序列化時(shí)應(yīng)該被忽略。本文將通過(guò)示例為大家簡(jiǎn)單講講transient的使用,需要的可以參考一下
    2023-04-04
  • Java基礎(chǔ)開(kāi)發(fā)之JDBC操作數(shù)據(jù)庫(kù)增刪改查,分頁(yè)查詢實(shí)例詳解

    Java基礎(chǔ)開(kāi)發(fā)之JDBC操作數(shù)據(jù)庫(kù)增刪改查,分頁(yè)查詢實(shí)例詳解

    這篇文章主要介紹了Java基礎(chǔ)開(kāi)發(fā)之JDBC操作數(shù)據(jù)庫(kù)增刪改查,分頁(yè)查詢實(shí)例詳解,需要的朋友可以參考下
    2020-02-02
  • Java 如何判斷Integer類型的值是否相等

    Java 如何判斷Integer類型的值是否相等

    這篇文章主要介紹了Java 如何判斷Integer類型的值是否相等操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09

最新評(píng)論

昂仁县| 察哈| 白山市| 辽阳市| 南阳市| 社旗县| 樟树市| 额济纳旗| 建平县| 屏东县| 泽州县| 志丹县| 屏边| 浦北县| 广元市| 新龙县| 胶州市| 西藏| 封开县| 阜新市| 旺苍县| 东乌珠穆沁旗| 台湾省| 汕头市| 尉氏县| 新民市| 东莞市| 定南县| 清镇市| 东城区| 隆安县| 旬阳县| 景洪市| 丰台区| 瑞安市| 张家界市| 阿克苏市| 鹤山市| 城口县| 太白县| 温宿县|