找回密码
 立即注册
首页 业界区 业界 SpringBoot--如何创建自己的自动配置

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

皮仪芳 2025-7-21 10:18:52
在实际开发中,仅靠SpringBoot的自动配置是远远不够的,比如要访问多个数据源,自动配置就完全无能为力了。
自动配置的本质

本质就是在容器中预配置要整合的框架所需的基础Bean。
以MyBatis为例,spring整合MyBatis无非就是完成以下事情:

  • 配置SqlSessionFactory Bean,当然,该Bean需要注入一个DataSource
  • 配置SqlSessionTemplate Bean,将上面的SqlSessionFactory 注入该Bean
  • 注册Mapper组件的自动扫描,相当于添加元素
自动配置非常简单,无非就是有框架提供一个@Configuration修饰的配置类(相当于传统的xml配置文件),在该配置类中用@Bean预先配置默认的SqlSessionFactory、SqlSessionTemplate,并注册Mapper组件的自动扫描即可。
比如MybatisAutoConfiguration源代码:
  1. @Configuration // 被修饰的类变成配置类
  2. // 当SqlSessionFactory、SqlSessionFactoryBean类存在时,才会生效。
  3. // 条件注解之一
  4. @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
  5. // 当DataSource Bean存在时,才会生效
  6. // 条件注解之一
  7. @ConditionalOnSingleCandidate(DataSource.class)
  8. // 启用Mybatis的属性处理类
  9. // 启动属性处理类
  10. @EnableConfigurationProperties({MybatisProperties.class})
  11. //指定该配置类在DataSourceAutoConfiguration和MybatisLanguageDriverAutoConfiguration之后加载
  12. @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
  13. // 实现 InitializingBean 接口,该接口中的 afterPropertiesSet 方法会在该Bean初始化完成后被自动调用
  14. public class MybatisAutoConfiguration implements InitializingBean {
  15.     private static final Logger logger = LoggerFactory.getLogger(org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.class);
  16.     // Mybatis的配置属性
  17.     private final MybatisProperties properties;
  18.     // Mybatis的拦截器、类型处理器、语言驱动等
  19.     private final Interceptor[] interceptors;
  20.     private final TypeHandler[] typeHandlers;
  21.     private final LanguageDriver[] languageDrivers;
  22.     private final ResourceLoader resourceLoader;
  23.     private final DatabaseIdProvider databaseIdProvider;
  24.     private final List<ConfigurationCustomizer> configurationCustomizers;
  25.     private final List<SqlSessionFactoryBeanCustomizer> sqlSessionFactoryBeanCustomizers;
  26.     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) {
  27.         this.properties = properties;
  28.         this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
  29.         this.typeHandlers = (TypeHandler[])typeHandlersProvider.getIfAvailable();
  30.         this.languageDrivers = (LanguageDriver[])languageDriversProvider.getIfAvailable();
  31.         this.resourceLoader = resourceLoader;
  32.         this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable();
  33.         this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable();
  34.         this.sqlSessionFactoryBeanCustomizers = (List)sqlSessionFactoryBeanCustomizers.getIfAvailable();
  35.     }
  36.     // 在Bean初始化完成后调用该方法
  37.     public void afterPropertiesSet() {
  38.         this.checkConfigFileExists();
  39.     }
  40.     // 检查Mybatis配置文件是否存在
  41.     private void checkConfigFileExists() {
  42.         if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
  43.             // 获取配置文件的资源
  44.             Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
  45.             // 如果resource.exists()方法返回false,则抛出异常
  46.             Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
  47.         }
  48.     }
  49.     // 创建SqlSessionFactory Bean
  50.     @Bean
  51.     // 当没有SqlSessionFactory Bean时才会创建
  52.     @ConditionalOnMissingBean
  53.     public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  54.         // 创建SqlSessionFactoryBean实例
  55.         SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  56.         // 注入数据源
  57.         factory.setDataSource(dataSource);
  58.         factory.setVfs(SpringBootVFS.class);
  59.         // 如果配置文件路径不为空,则设置配置文件位置
  60.         if (StringUtils.hasText(this.properties.getConfigLocation())) {
  61.             factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
  62.         }
  63.         this.applyConfiguration(factory);
  64.         // 如果配置属性不为空,则设置配置属性
  65.         if (this.properties.getConfigurationProperties() != null) {
  66.             factory.setConfigurationProperties(this.properties.getConfigurationProperties());
  67.         }
  68.         // 应用所有的拦截器
  69.         if (!ObjectUtils.isEmpty(this.interceptors)) {
  70.             factory.setPlugins(this.interceptors);
  71.         }
  72.         // 应用所有databaseIdProvider
  73.         if (this.databaseIdProvider != null) {
  74.             factory.setDatabaseIdProvider(this.databaseIdProvider);
  75.         }
  76.         // 根据包名应用TypeAliases
  77.         if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
  78.             factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
  79.         }
  80.         // 根据父类型应用TypeAliases
  81.         if (this.properties.getTypeAliasesSuperType() != null) {
  82.             factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
  83.         }
  84.         // 根据包名应用TypeHandler
  85.         if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
  86.             factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
  87.         }
  88.         // 应用所有TypeHandler
  89.         if (!ObjectUtils.isEmpty(this.typeHandlers)) {
  90.             factory.setTypeHandlers(this.typeHandlers);
  91.         }
  92.         // 设置mapper的加载位置
  93.         if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
  94.             factory.setMapperLocations(this.properties.resolveMapperLocations());
  95.         }
  96.         Set<String> factoryPropertyNames = (Set) Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
  97.         Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
  98.         if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
  99.             factory.setScriptingLanguageDrivers(this.languageDrivers);
  100.             if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
  101.                 defaultLanguageDriver = this.languageDrivers[0].getClass();
  102.             }
  103.         }
  104.         if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
  105.             factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
  106.         }
  107.         this.applySqlSessionFactoryBeanCustomizers(factory);
  108.         // 返回SqlSessionFactory对象
  109.         return factory.getObject();
  110.     }
  111.     private void applyConfiguration(SqlSessionFactoryBean factory) {
  112.         org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
  113.         if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
  114.             configuration = new org.apache.ibatis.session.Configuration();
  115.         }
  116.         if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
  117.             Iterator var3 = this.configurationCustomizers.iterator();
  118.             while(var3.hasNext()) {
  119.                 ConfigurationCustomizer customizer = (ConfigurationCustomizer)var3.next();
  120.                 customizer.customize(configuration);
  121.             }
  122.         }
  123.         factory.setConfiguration(configuration);
  124.     }
  125.     private void applySqlSessionFactoryBeanCustomizers(SqlSessionFactoryBean factory) {
  126.         if (!CollectionUtils.isEmpty(this.sqlSessionFactoryBeanCustomizers)) {
  127.             Iterator var2 = this.sqlSessionFactoryBeanCustomizers.iterator();
  128.             while(var2.hasNext()) {
  129.                 SqlSessionFactoryBeanCustomizer customizer = (SqlSessionFactoryBeanCustomizer)var2.next();
  130.                 customizer.customize(factory);
  131.             }
  132.         }
  133.     }
  134.     // 创建SqlSessionTemplate Bean
  135.     @Bean
  136.     // 当没有SqlSessionTemplate Bean时才会创建
  137.     @ConditionalOnMissingBean
  138.     public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  139.         ExecutorType executorType = this.properties.getExecutorType();
  140.         // 如果executorType不为null,则创建SqlSessionTemplate时使用该executorType
  141.         return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
  142.     }
  143.     @Configuration
  144.     // 导入MapperScannerRegistrarNotFoundConfiguration注册类
  145.     @Import({org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
  146.     // 当MapperFactoryBean和MapperScannerConfigurer都不存在时,才会生效
  147.     @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
  148.     // 实现 InitializingBean 接口,该接口中的 afterPropertiesSet 方法会在该Bean初始化完成后被自动调用
  149.     public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
  150.         public MapperScannerRegistrarNotFoundConfiguration() {
  151.         }
  152.         // 重写afterPropertiesSet方法
  153.         public void afterPropertiesSet() {
  154.             org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
  155.         }
  156.     }
  157.     // 注册Mapper扫描器的自动配置类
  158.     // 实现 BeanFactoryAware接口可访问spring容器、
  159.     // 实现ImportBeanDefinitionRegistrar 接口可配置额外的bean
  160.     public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
  161.         // BeanFactory对象,用于保存Spring容器
  162.         private BeanFactory beanFactory;
  163.         private Environment environment;
  164.         public AutoConfiguredMapperScannerRegistrar() {
  165.         }
  166.         public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  167.             if (!AutoConfigurationPackages.has(this.beanFactory)) {
  168.                 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
  169.             } else {
  170.                 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
  171.                // 获取自动配置要处理的包
  172.                 List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
  173.                 if (org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.logger.isDebugEnabled()) {
  174.                     packages.forEach((pkg) -> {
  175.                         org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
  176.                     });
  177.                 }
  178.                 // 创建BeanDefinitionBuilder对象
  179.                 // 它帮助开发者以反射的方式创建任意类的实例
  180.                 // 此处就是帮助创建MapperScannerConfigurer类的实例
  181.                 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  182.                // 为要创建的对象设置属性
  183.                 builder.addPropertyValue("processPropertyPlaceHolders", true);
  184.                 builder.addPropertyValue("annotationClass", Mapper.class);
  185.                 builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
  186.                 BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
  187.                 Set<String> propertyNames = (Set)Stream.of(beanWrapper.getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
  188.                 if (propertyNames.contains("lazyInitialization")) {
  189.                     builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
  190.                 }
  191.                 if (propertyNames.contains("defaultScope")) {
  192.                     builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
  193.                 }
  194.                 boolean injectSqlSession = (Boolean)this.environment.getProperty("mybatis.inject-sql-session-on-mapper-scan", Boolean.class, Boolean.TRUE);
  195.                 if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) {
  196.                     ListableBeanFactory listableBeanFactory = (ListableBeanFactory)this.beanFactory;
  197.                     Optional<String> sqlSessionTemplateBeanName = Optional.ofNullable(this.getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory));
  198.                     Optional<String> sqlSessionFactoryBeanName = Optional.ofNullable(this.getBeanNameForType(SqlSessionFactory.class, listableBeanFactory));
  199.                     if (!sqlSessionTemplateBeanName.isPresent() && sqlSessionFactoryBeanName.isPresent()) {
  200.                         builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName.get());
  201.                     } else {
  202.                         builder.addPropertyValue("sqlSessionTemplateBeanName", sqlSessionTemplateBeanName.orElse("sqlSessionTemplate"));
  203.                     }
  204.                 }
  205.                 builder.setRole(2);
  206.                 // 在容器中注册BeanDefinitionBuilder创建的MapperScannerConfigurer对象
  207.                 registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
  208.             }
  209.         }
  210.         // 获取spring容器和环境对象
  211.         public void setBeanFactory(BeanFactory beanFactory) {
  212.             this.beanFactory = beanFactory;
  213.         }
  214.         public void setEnvironment(Environment environment) {
  215.             this.environment = environment;
  216.         }
  217.         private String getBeanNameForType(Class<?> type, ListableBeanFactory factory) {
  218.             String[] beanNames = factory.getBeanNamesForType(type);
  219.             return beanNames.length > 0 ? beanNames[0] : null;
  220.         }
  221.     }
  222. }
复制代码
开发WriterTemplate类
  1. # Auto Configure
  2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  3. org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
  4. org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
复制代码
然后使用 mvn install 命令打成jar包并安装到本地资源库。
在Starter的项目中引入上面的jar包。pom.xml如下:
  1. @Configuration(proxyBeanMethods = false)
  2. // 仅当com.mysql.cj.jdbc.Driver类存在时该配置类生效
  3. @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
  4. public class FkConfig
  5. {
  6.    @Bean
  7.    public MyBean myBean()
  8.    {
  9.       return new MyBean();
  10.    }
  11. }
复制代码
然后在开发中编写自定义配置类:
  1. // 仅当容器中不存在名为myService的Bean时,才创建该Bean   
  2. @ConditionalOnMissingBean
  3. @Bean
  4. public MyService myService()
  5. {
  6.    ...
  7. }
  8. // 当容器中不存在名为jdbcTemplate的Bean时,才创建该Bean
  9. @ConditionalOnMissingBean(name="jdbcTemplate")
  10. @Bean
  11. public JdbcTemplate JdbcTemplate()
  12. {
  13.    ...
  14. }
复制代码
上面代码中的FunnyProperties类
  1. @Configuration(proxyBeanMethods = false)
  2. public class FkConfig
  3. {
  4.    @Bean
  5.    // 只有当org.fkjava.test属性具有foo属性值时,下面配置方法才会生效
  6.    @ConditionalOnProperty(name = "test", havingValue = "foo",
  7.          prefix = "org.fkjava")
  8.    public DateFormat dateFormat()
  9.    {
  10.       return DateFormat.getDateInstance();
  11.    }
  12. }
复制代码
接下来在META-INF/spring.factories文件中注册自动配置类
  1. @SpringBootApplication
  2. public class App
  3. {
  4.    public static void main(String[] args)
  5.    {
  6.       // 创建Spring容器、运行Spring Boot应用
  7.       var ctx = SpringApplication.run(App.class, args);
  8.       System.out.println(ctx.getBean("dateFormat"));
  9.    }
  10. }
复制代码
然后使用 mvn install 命令打成jar包,并安装到maven本地资源库中,就会在自己的本地资源库中找到该jar包,这样就完成了自定义配置的实现。
创建自定义的Starter

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

  • 自动配置模块(auto-configure):包含自动配置类和spring.factories文件
  • Starter模块:负责管理自动配置模块和第三方依赖。简而言之,添加本Starter就能使用该自动配置。
由此看出,Starter不包含任何Class文件,只管理愿意来。如果查看官方提供的jar就会发现,它所有自动配置类的Class都由spring-boot-autoconfigure.jar提供,而各个xxx-starter.jar并未提供任何Class文件,只是在这些jar下的相同路径下提供了一个xxx-starter.pom文件,该文件指定Starter管理的自动依赖模块和第三方依赖。
SpringBoot为自动配置包和Starter包提供推荐命名

  • 自动配置包的推荐名:xxx-spring-boot
  • Starter包的推荐名:xxx-spring-boot-starter
对于第三方Starter不要使用spring-boot-starter-xxx这种方式,这是官方使用的。
有了自定义的Starter后,使用起来和官方的没有区别,比如
添加依赖:
  1. org.fkjava.test=foo
复制代码
在application.properties文件添加配置
  1. @Configuration(proxyBeanMethods = false)
  2. public class FkConfig
  3. {
  4.    @Bean
  5.    // 只有当前应用是反应式Web应用时,该配置才会生效
  6.    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
  7.    public DateFormat dateFormat()
  8.    {
  9.       return DateFormat.getDateInstance();
  10.    }
  11. }
复制代码
主类的代码:
  1. @SpringBootApplication
  2. public class App
  3. {
  4.    public static void main(String[] args)
  5.    {
  6.       var app = new SpringApplication(App.class);
  7.       // 设置Web应用的类型,如果不设置则使用默认的类型:
  8.       // 如果有Sping Web依赖,自动是基于Servlet的Web应用
  9.       // 如果有Sping WebFlux依赖,自动是反应式Web应用
  10.       app.setWebApplicationType(WebApplicationType.REACTIVE);  // ①
  11.       // 创建Spring容器、运行Spring Boot应用
  12.       var ctx = app.run(args);
  13.       System.out.println(ctx.getBean("dateFormat"));
  14.    }
  15. }
复制代码
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册