写点什么

SpringBoot-- 如何创建自己的自动配置

  • 2025-07-22
    福建
  • 本文字数:16249 字

    阅读完需:约 53 分钟

在实际开发中,仅靠 SpringBoot 的自动配置是远远不够的,比如要访问多个数据源,自动配置就完全无能为力了。


自动配置的本质


本质就是在容器中预配置要整合的框架所需的基础 Bean。

以 MyBatis 为例,spring 整合 MyBatis 无非就是完成以下事情:

  1. 配置 SqlSessionFactory Bean,当然,该 Bean 需要注入一个 DataSource

  2. 配置 SqlSessionTemplate Bean,将上面的 SqlSessionFactory 注入该 Bean

  3. 注册 Mapper 组件的自动扫描,相当于添加<mybatis:scan.../>元素

自动配置非常简单,无非就是有框架提供一个 @Configuration 修饰的配置类(相当于传统的 xml 配置文件),在该配置类中用 @Bean 预先配置默认的 SqlSessionFactory、SqlSessionTemplate,并注册 Mapper 组件的自动扫描即可。

比如 MybatisAutoConfiguration 源代码:

@Configuration // 被修饰的类变成配置类// 当SqlSessionFactory、SqlSessionFactoryBean类存在时,才会生效。// 条件注解之一@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) // 当DataSource Bean存在时,才会生效// 条件注解之一@ConditionalOnSingleCandidate(DataSource.class)// 启用Mybatis的属性处理类// 启动属性处理类@EnableConfigurationProperties({MybatisProperties.class})//指定该配置类在DataSourceAutoConfiguration和MybatisLanguageDriverAutoConfiguration之后加载@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})// 实现 InitializingBean 接口,该接口中的 afterPropertiesSet 方法会在该Bean初始化完成后被自动调用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的拦截器、类型处理器、语言驱动等    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初始化完成后调用该方法 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)"); }
}
// 创建SqlSessionFactory Bean @Bean // 当没有SqlSessionFactory Bean时才会创建 @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { // 创建SqlSessionFactoryBean实例 SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); // 注入数据源 factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); // 如果配置文件路径不为空,则设置配置文件位置 if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); }
this.applyConfiguration(factory); // 如果配置属性不为空,则设置配置属性 if (this.properties.getConfigurationProperties() != null) { factory.setConfigurationProperties(this.properties.getConfigurationProperties()); } // 应用所有的拦截器 if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); } // 应用所有databaseIdProvider if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); } // 根据包名应用TypeAliases if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); } // 根据父类型应用TypeAliases if (this.properties.getTypeAliasesSuperType() != null) { factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); } // 根据包名应用TypeHandler if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } // 应用所有TypeHandler if (!ObjectUtils.isEmpty(this.typeHandlers)) { factory.setTypeHandlers(this.typeHandlers); } // 设置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); } }
}
// 创建SqlSessionTemplate Bean @Bean // 当没有SqlSessionTemplate Bean时才会创建 @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); // 如果executorType不为null,则创建SqlSessionTemplate时使用该executorType return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory); }
@Configuration // 导入MapperScannerRegistrarNotFoundConfiguration注册类 @Import({org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class}) // 当MapperFactoryBean和MapperScannerConfigurer都不存在时,才会生效 @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class}) // 实现 InitializingBean 接口,该接口中的 afterPropertiesSet 方法会在该Bean初始化完成后被自动调用 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扫描器的自动配置类 // 实现 BeanFactoryAware接口可访问spring容器、 // 实现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); }); } // 创建BeanDefinitionBuilder对象 // 它帮助开发者以反射的方式创建任意类的实例 // 此处就是帮助创建MapperScannerConfigurer类的实例 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); // 为要创建的对象设置属性 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创建的MapperScannerConfigurer对象 registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } }
// 获取spring容器和环境对象 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; } }}
复制代码


开开发完自动配置类后,还需要使用 META-INF/spring.factories 文件来定义自动配置类,比如:

# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
复制代码


自动配置类只能通过 META-INF/spring.factories 来加载,并确保它们处于一个特殊的包空间内,尤其不能让他们变成普通 @ComponentScan 的目标。此外,自动配置类不应该使用 @ComponentScan 来扫描其他组件,如果需要加载其他配置文件,应使用 @Import 来加载。

如果要为自动配置类指定加载顺序,可使用以下注解:

  1. @AutoConfigureAfter:指定被修饰的类必须在一个或多个自动配置类之后加载

  2. @AutoConfigureBefore:指定被修饰的类必须在一个或多个自动配置类之前加载

如果自动配置包中包含多个自动配置类,且以特定的顺序来加载,可使用 @AutoConfigureOrder 来修饰它们,@AutoConfigureOrder 类似于 @Order 注解,只不过专门修饰自动配置类。


条件注解


条件注解用于修饰 @Configuration 类 或 @Bean 方法等,表示只有条件有效时,被修饰的 配置类或配置方法才生效。SpringBoot 的条件 注解可支持如下几种条件:

  1. 类条件注解:@ConditionalOnClass(表示某些类存在时,可通过 Value 或 name 指定所要求存在的类,value 属性是 被检查类的 Class 对象;name 属性是被检查类的全限定类名的字符串形式)、@ConditionalOnMissingClass(某些类不存在时,只能通过 value 属性指定不存在的类,value 属性值只能是被检查类的全限定类名的字符串形式)

  2. Bean 条件注解 :@ConditionalOnMissingBean、@ConditionalOnSingleCandidate、@ConditionalOnBean、@ConditionalOnMissingFilterBean

  3. 属性条件注解:@ConditionalOnProperity

  4. 资源条件注解:@ConditionalOnResource

  5. Web 应用条件注解:@ConditionalOnWebApplication、@ConditionalOnNotWebApplication、@ConditionalOnWarDeployment

  6. SpEL 表达式条件注解:@ConditionalOnExpression

  7. 特殊条件注解:@ConditionalOnCloudPlatform、@ConditionalOnJava、@ConditionalOnJndi、@ConditionalOnRepositoryType

代码示例:

@Configuration(proxyBeanMethods = false)// 仅当com.mysql.cj.jdbc.Driver类存在时该配置类生效@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:指定搜索目标 Bean 的 搜索策略、支持 CURRENT(仅在容器中搜索)、ACESTORS(仅在祖先容器中搜索)、ALL(在所有容器中搜索)三个枚举值

  6. Class<?> [] value:指定要检查的 Bean 的类型

  7. String[] type:与 value 属性作用相同,只不过该属性用字符串形式 的全限定类名

@ConditionalOnSingleCandidate 注解相当于 @ConditionalOnMissingBean 的增强版,不仅要求被检查的 Bean 必须存在,而且只能有一个“候选者”--能满足 byType 依赖注入条件。

如果 @ConditionalOnMissingBean、@ConditionalOnBean 注解不指定任何属性,默认根据目标 Bean 的类型进行检查,默认检查被修饰的方法返回的 Bean 类型,代码示例:

// 仅当容器中不存在名为myService的Bean时,才创建该Bean   @ConditionalOnMissingBean@Beanpublic MyService myService(){   ...}
// 当容器中不存在名为jdbcTemplate的Bean时,才创建该Bean@ConditionalOnMissingBean(name="jdbcTemplate")@Beanpublic JdbcTemplate JdbcTemplate(){ ...}
复制代码


@ConditionalOnMissingFilterBean 相当于 @ConditionalOnMissingBean 的特殊版本,专门检查容器中是否有指定类型的 javax.servlet.Filter,因此只能通过 value 指定要检查的 Filter 的类型。

@ConditionalOnProperity 注解 用于检查特定属性是否具有指定的属性值。该注解支持如下属性:

  1. String[] value:指定要检查的属性

  2. String[] name:指定 value 属性的别名

  3. String havingValue:被检查属性必须具有的属性值

  4. String prefix:自动为各属性名添加该属性指定的前缀

  5. boolean matchMissing:指定当属性未设置属性值时,是否通过检查

代码示例:

@Configuration(proxyBeanMethods = false)public class FkConfig{   @Bean   // 只有当org.fkjava.test属性具有foo属性值时,下面配置方法才会生效   @ConditionalOnProperty(name = "test", havingValue = "foo",         prefix = "org.fkjava")   public DateFormat dateFormat()   {      return DateFormat.getDateInstance();   }}
复制代码


启动类代码:

@SpringBootApplicationpublic class App{   public static void main(String[] args)   {      // 创建Spring容器、运行Spring Boot应用      var ctx = SpringApplication.run(App.class, args);      System.out.println(ctx.getBean("dateFormat"));   }}
复制代码


此时直接运行程序会有异常。



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

org.fkjava.test=foo
复制代码


运行结果如下



@ConditionalOnResource 的作用很简单,它要求指定的资源必须存在,修饰的配置类才会生效。使用该注解只需指定 resource 属性,该属性指定必须存在的资源。

@ConditionalOnWebApplication 要求当前应用必须是 Web 应用时,修饰 的配置类才会生效。可通过 type 属性指定 Web 应用类型。该属性支持如下三个枚举值:

  1. ANY:任何 Web 应用

  2. REACTIVE:当应用时反应式 Web 应用时

  3. SERVLET:基于 servlet 的 Web 应用

代码示例:

@Configuration(proxyBeanMethods = false)public class FkConfig{   @Bean   // 只有当前应用是反应式Web应用时,该配置才会生效   @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)   public DateFormat dateFormat()   {      return DateFormat.getDateInstance();   }}
复制代码


启动类:

@SpringBootApplicationpublic class App{   public static void main(String[] args)   {      var app = new SpringApplication(App.class);      // 设置Web应用的类型,如果不设置则使用默认的类型:      // 如果有Sping Web依赖,自动是基于Servlet的Web应用      // 如果有Sping WebFlux依赖,自动是反应式Web应用      app.setWebApplicationType(WebApplicationType.REACTIVE);  // ①      // 创建Spring容器、运行Spring Boot应用      var ctx = app.run(args);      System.out.println(ctx.getBean("dateFormat"));   }}
复制代码


@ConditionalOnNotWebApplication 要求当前应用不是 Web 应用时,修饰的配置类或方法才生效

@ConditionalOnWarDeployment 要求当前应用以 War 包部署 到 Web 服务器或应用服务器中时(不以独立的 java 程序的方式运行),才生效。

@ConditionalOnNotWebApplication、@ConditionalOnWarDeployment 这 2 个注解使用简单,不需要指定任何属性

@ConditionalOnExpression 要求指定 SpEL 表达式的值为 true,所修饰的配置类或方法才会生效。代码示例:

@Configuration(proxyBeanMethods = false)public class FkConfig{   @Bean   public User user()   {      return new User("fkjava", true);   }   @Bean   // 只有当user.active表达式为true时,该方法才生效。也就是容器中User Bean的active属性为true时,该方法才生效   @ConditionalOnExpression("user.active")   public DateFormat dateFormat()   {      return DateFormat.getDateInstance();   }}
复制代码


@ConditionalOnCloudPlatform 要求应用被部署在特定云平台,修饰的配置类或方法才生效。可通过 value 属性指定要求的云平台,支持如下枚举值:

  1. CLOUD_FOUNDRY

  2. HEROKU

  3. KUBERNETES

  4. SAP

@ConditionalOnJava 对目标平台的 java 版本进行检测,既可以要求 java 版本是某个具体的版本,也可以要求高于或低于某个版本。可指定如下两个属性:

  1. JavaVersion value:指定要求的 java 版本

  2. ConditionalOnJava.Range range:该属性支持 EQUAL_OR_NEWER(大于或等于某版本)和 OLDER_THAN(小于某版本)两个枚举值。如果不指定该属性,则要求 java 版本必须是 value 属性所指定的版本。

代码示例:

@Configuration(proxyBeanMethods = false)public class FkConfig{   @Bean   // 只有当目标平台的Java版本是11或更新的平台时,该方法才生效   @ConditionalOnJava(value = JavaVersion.ELEVEN,range = ConditionalOnJava.Range.EQUAL_OR_NEWER)   public DateFormat dateFormat()   {      return DateFormat.getDateInstance();   }}
复制代码


@ConditionalOnJndi 要求指定 JNDI 必须存在,通过 value 属性指定要检查的 JNDI。

@ConditionalOnRepositoryType 要求特定的 Spring Data Repository 被启用时,修饰的配置类或方法才会生效。


自定义条件注解


自定义条件注解的关键就是要有一个 Condition 实现类,该类负责条件注解的处理逻辑--它所实现的 matches()方法决定了条件注解的要求是否得到满足。

代码示例:Condition 实现类

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[]数组)      String[] vals = (String[]) map.get("value");      Environment env = context.getEnvironment();      // 遍历每个属性值      for (Object val : vals)      {         // 如果某个属性值对应的配置属性不存在,返回false         if (env.getProperty(val.toString()) == null)         {            return false;         }      }      return true;   }}
复制代码


此处逻辑是要求 value 属性所指定的所有配置属性必须存在,至于属性值是什么无所谓,这些属性是否有值也无所谓。

自定义条件注解的代码:

@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented// 指定Conditional的实现类@Conditional(MyCondition.class)public @interface ConditionalCustom{   String[] value() default {};}
复制代码


使用自定义条件注解:

@Configuration(proxyBeanMethods = false)public class FkConfig{   @Bean   // 只有当org.fkjava.test和org.crazyit.abc两个配置属性存在时该方法才生效   @ConditionalCustom({"org.fkjava.test", "org.crazyit.abc"})   public DateFormat dateFormat()   {      return DateFormat.getDateInstance();   }}
复制代码


自定义自动配置


开发自定义的自动配置很简单,分为两步:

1、使用 @Configuration 和条件注解自定义配置类

2、在 META-INF/spring.factories 文件中注册自动配置类

为了演示,先自行开发一个 funny 框架,功能是用文件或数据库保存程序输出信息。

1、先新建一个 maven 项目,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驱动依赖 --> <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>
复制代码


开发 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("==========获取数据库连接=========="); 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)) { // 查询当前数据库的funny_message表是否存在 ResultSet rs = conn.getMetaData().getTables(conn.getCatalog(), null, "funny_message", null); // 如果funny_message表不存在 if (!rs.next()) { log.debug("~~~~~~创建funny_message表~~~~~~"); conn.createStatement().execute("create table funny_message " + "(id int primary key auto_increment, message_text text)"); rs.close(); } log.debug("~~~~~~输出到数据表~~~~~~"); // 插入要输出的字符串 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)); } } // 关闭资源 public void close() throws SQLException, IOException { if (this.conn != null) { this.conn.close(); } if (this.raf != null) { this.raf.close(); } }}
复制代码


然后使用 mvn install 命令打成 jar 包并安装到本地资源库。

在 Starter 的项目中引入上面的 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>
<!-- 定义基本的项目信息 --> <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>
复制代码


然后在开发中编写自定义配置类:

@Configuration// 当WriterTemplate类存在时配置生效// WriterTemplate类是自己编写的工具项目中的类@ConditionalOnClass(WriterTemplate.class)// 启用FunnyProperties属性处理类@EnableConfigurationProperties(FunnyProperties.class)// 让该自动配置位于DataSourceAutoConfiguration自动配置之后处理@AutoConfigureAfter(DataSourceAutoConfiguration.class)public class FunnyAutoConfiguration{   // FunnyProperties类负责加载配置属性   private final FunnyProperties properties;
public FunnyAutoConfiguration(FunnyProperties properties) { this.properties = properties; }
@Bean(destroyMethod = "close") // 当单例的DataSource Bean存在时配置生效 @ConditionalOnSingleCandidate(DataSource.class) // 只有当容器中没有WriterTemplate Bean时,该配置才会生效 @ConditionalOnMissingBean // 通过@AutoConfigureOrder注解指定该配置方法 // 比下一个配置WriterTemplate的方法的优先级更高 // @AutoConfigureOrder 数值越小,优先级越高 @AutoConfigureOrder(99) public WriterTemplate writerTemplate(DataSource dataSource) throws SQLException { return new WriterTemplate(dataSource); }
@Bean(destroyMethod = "close") // 只有当前面的WriterTemplate配置没有生效时,该方法的配置才会生效 @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 包,这样就完成了自定义配置的实现。


创建自定义的 Starter


一个完整的 SpringBoot Starter 包含一下两个组件:

  1. 自动配置模块(auto-configure):包含自动配置类和 spring.factories 文件

  2. Starter 模块:负责管理自动配置模块和第三方依赖。简而言之,添加本 Starter 就能使用该自动配置。

由此看出,Starter 不包含任何 Class 文件,只管理愿意来。如果查看官方提供的 jar 就会发现,它所有自动配置类的 Class 都由 spring-boot-autoconfigure.jar 提供,而各个 xxx-starter.jar 并未提供任何 Class 文件,只是在这些 jar 下的相同路径下提供了一个 xxx-starter.pom 文件,该文件指定 Starter 管理的自动依赖模块和第三方依赖。


SpringBoot 为自动配置包和 Starter 包提供推荐命名

  1. 自动配置包的推荐名:xxx-spring-boot

  2. Starter 包的推荐名:xxx-spring-boot-starter

对于第三方 Starter 不要使用 spring-boot-starter-xxx 这种方式,这是官方使用的。

有了自定义的 Starter 后,使用起来和官方的没有区别,比如

添加依赖:

<!-- 自定义的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.txtorg.crazyit.funny.charset=UTF-8# 指定连接数据库的信息spring.datasource.url=jdbc:mysql://localhost:3306/funny?serverTimezone=UTCspring.datasource.username=rootspring.datasource.password=32147# 配置funny框架的日志级别为debuglogging.level.org.crazyit.funny = debug
复制代码


主类的代码:

@SpringBootApplicationpublic class App{   public static void main(String[] args) throws IOException, SQLException   {      // 创建Spring容器、运行Spring Boot应用      var ctx = SpringApplication.run(App.class, args);      // 获取自动配置的WriterTemplate      WriterTemplate writerTemplate = ctx.getBean(WriterTemplate.class);      writerTemplate.write("自动配置其实很简单");   }}
复制代码


文章转载自:NE_STOP

原文链接:https://www.cnblogs.com/alineverstop/p/18995435

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2025-04-01 加入

还未添加个人简介

评论

发布
暂无评论
SpringBoot--如何创建自己的自动配置_spring_量贩潮汐·WholesaleTide_InfoQ写作社区