设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间20 现在时间20 缓存数据 和聪明人交流,和靠谱的人恋爱,和进取的人共事,和幽默的人随行。晚安!

和聪明人交流,和靠谱的人恋爱,和进取的人共事,和幽默的人随行。晚安!

查看: 1110|回复: 0

一文详解SpringBoot如何创建自定义的自动配置

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:203
  • 打卡月天数:0
  • 打卡总奖励:2961
  • 最近打卡:2023-08-27 09:22:16
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
402
主题
367
精华
0
金钱
4123
积分
788
注册时间
2022-12-21
最后登录
2025-9-2

发表于 2025-8-28 03:13:03 | 显示全部楼层 |阅读模式

在实际开发中,仅靠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. }
复制代码

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

  1. # Auto Configure
  2. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  3. org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
  4. org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
复制代码

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

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

  • @AutoConfigureAfter:指定被修饰的类必须在一个或多个自动配置类之后加载
  • @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

代码示例:

  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. }
复制代码

@ConditionalOnMissingBean、@ConditionalOnSingleCandidate、@ConditionalOnBean可指定 如下属性:

  1. Class[] 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类型,代码示例:

  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. }
复制代码

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

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

  1. String[] value:指定要检查的属性
  2. String[] name:指定value属性的别名
  3. String havingValue:被检查属性必须具有的属性值
  4. String prefix:自动为各属性名添加该属性指定的前缀
  5. boolean matchMissing:指定当属性未设置属性值时,是否通过检查

代码示例:

  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. }
复制代码

启动类代码:

  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. }
复制代码

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

1.png

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

  1. org.fkjava.test=foo
复制代码

运行结果如下

2.png

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

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

  1. ANY:任何Web应用
  2. REACTIVE:当应用时反应式Web应用时
  3. SERVLET:基于servlet的Web应用

代码示例:

  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. }
复制代码

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

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

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

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

  1. @Configuration(proxyBeanMethods = false)
  2. public class FkConfig
  3. {
  4. @Bean
  5. public User user()
  6. {
  7. return new User("fkjava", true);
  8. }
  9. @Bean
  10. // 只有当user.active表达式为true时,该方法才生效。也就是容器中User Bean的active属性为true时,该方法才生效
  11. @ConditionalOnExpression("user.active")
  12. public DateFormat dateFormat()
  13. {
  14. return DateFormat.getDateInstance();
  15. }
  16. }
复制代码

@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属性所指定的版本。

代码示例:

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

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

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

自定义条件注解

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

代码示例:Condition实现类

  1. public class MyCondition implements Condition
  2. {
  3. @Override
  4. public boolean matches(ConditionContext context,
  5. AnnotatedTypeMetadata metadata)
  6. {
  7. // 获取@ConditionalCustom注解的全部属性
  8. Map<String, Object> map = metadata.getAnnotationAttributes(
  9. ConditionalCustom.class.getName());
  10. // 获取注解的value属性值(String[]数组)
  11. String[] vals = (String[]) map.get("value");
  12. Environment env = context.getEnvironment();
  13. // 遍历每个属性值
  14. for (Object val : vals)
  15. {
  16. // 如果某个属性值对应的配置属性不存在,返回false
  17. if (env.getProperty(val.toString()) == null)
  18. {
  19. return false;
  20. }
  21. }
  22. return true;
  23. }
  24. }
复制代码

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

自定义条件注解的代码:

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

使用自定义条件注解:

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

自定义自动配置

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

  1. 使用@Configuration和条件注解自定义配置类
  2. 在META-INF/spring.factories文件中注册自动配置类

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

先新建一个maven项目,pom.xml如下

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>org.crazyit</groupId>
  7. <artifactId>funny</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <name>funny</name>
  10. <properties>
  11. <!-- 定义所使用的Java版本和源代码所用的字符集 -->
  12. <maven.compiler.source>11</maven.compiler.source>
  13. <maven.compiler.target>11</maven.compiler.target>
  14. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  15. </properties>
  16. <dependencies>
  17. <!-- MySQL驱动依赖 -->
  18. <dependency>
  19. <groupId>mysql</groupId>
  20. <artifactId>mysql-connector-java</artifactId>
  21. <version>8.0.22</version>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.slf4j</groupId>
  25. <artifactId>slf4j-api</artifactId>
  26. <version>1.7.30</version>
  27. <optional>true</optional>
  28. </dependency>
  29. </dependencies>
  30. </project>
复制代码

开发WriterTemplate类

  1. public class WriterTemplate
  2. {
  3. Logger log = LoggerFactory.getLogger(this.getClass());
  4. private final DataSource dataSource;
  5. private Connection conn;
  6. private final File dest;
  7. private final Charset charset;
  8. private RandomAccessFile raf;
  9. public WriterTemplate(DataSource dataSource) throws SQLException
  10. {
  11. this.dataSource = dataSource;
  12. this.dest = null;
  13. this.charset = null;
  14. if (Objects.nonNull(this.dataSource))
  15. {
  16. log.debug("==========获取数据库连接==========");
  17. this.conn = dataSource.getConnection();
  18. }
  19. }
  20. public WriterTemplate(File dest, Charset charset) throws FileNotFoundException
  21. {
  22. this.dest = dest;
  23. this.charset = charset;
  24. this.dataSource = null;
  25. this.raf = new RandomAccessFile(this.dest, "rw");
  26. }
  27. public void write(String message) throws IOException, SQLException
  28. {
  29. if (Objects.nonNull(this.conn))
  30. {
  31. // 查询当前数据库的funny_message表是否存在
  32. ResultSet rs = conn.getMetaData().getTables(conn.getCatalog(), null,
  33. "funny_message", null);
  34. // 如果funny_message表不存在
  35. if (!rs.next())
  36. {
  37. log.debug("~~~~~~创建funny_message表~~~~~~");
  38. conn.createStatement().execute("create table funny_message " +
  39. "(id int primary key auto_increment, message_text text)");
  40. rs.close();
  41. }
  42. log.debug("~~~~~~输出到数据表~~~~~~");
  43. // 插入要输出的字符串
  44. conn.createStatement().executeUpdate("insert into " +
  45. "funny_message values (null, '" + message + "')");
  46. }
  47. else
  48. {
  49. log.debug("~~~~~~输出到文件~~~~~~");
  50. // 输出到文件
  51. raf.seek(this.dest.length());
  52. raf.write((message + "\n").getBytes(this.charset));
  53. }
  54. }
  55. // 关闭资源
  56. public void close() throws SQLException, IOException
  57. {
  58. if (this.conn != null)
  59. {
  60. this.conn.close();
  61. }
  62. if (this.raf != null)
  63. {
  64. this.raf.close();
  65. }
  66. }
  67. }
复制代码

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

在Starter的项目中引入上面的jar包。pom.xml如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <!-- 指定继承spring-boot-starter-parent POM文件 -->
  7. <parent>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-parent</artifactId>
  10. <version>2.4.2</version>
  11. <relativePath/>
  12. </parent>
  13. <!-- 定义基本的项目信息 -->
  14. <groupId>org.crazyit</groupId>
  15. <artifactId>funny-spring-boot-starter</artifactId>
  16. <version>0.0.1-SNAPSHOT</version>
  17. <name>funny-spring-boot-starter</name>
  18. <properties>
  19. <!-- 定义所使用的Java版本和源代码所用的字符集 -->
  20. <java.version>11</java.version>
  21. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  22. </properties>
  23. <dependencies>
  24. <!-- Spring Boot Starter依赖 -->
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter</artifactId>
  28. </dependency>
  29. <!-- 依赖自定义的funny框架 -->
  30. <dependency>
  31. <groupId>org.crazyit</groupId>
  32. <artifactId>funny</artifactId>
  33. <version>1.0-SNAPSHOT</version>
  34. </dependency>
  35. <dependency>
  36. <groupId>org.springframework.boot</groupId>
  37. <artifactId>spring-boot-configuration-processor</artifactId>
  38. <optional>true</optional>
  39. </dependency>
  40. </dependencies>
  41. </project>
复制代码

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

  1. @Configuration
  2. // 当WriterTemplate类存在时配置生效
  3. // WriterTemplate类是自己编写的工具项目中的类
  4. @ConditionalOnClass(WriterTemplate.class)
  5. // 启用FunnyProperties属性处理类
  6. @EnableConfigurationProperties(FunnyProperties.class)
  7. // 让该自动配置位于DataSourceAutoConfiguration自动配置之后处理
  8. @AutoConfigureAfter(DataSourceAutoConfiguration.class)
  9. public class FunnyAutoConfiguration
  10. {
  11. // FunnyProperties类负责加载配置属性
  12. private final FunnyProperties properties;
  13. public FunnyAutoConfiguration(FunnyProperties properties)
  14. {
  15. this.properties = properties;
  16. }
  17. @Bean(destroyMethod = "close")
  18. // 当单例的DataSource Bean存在时配置生效
  19. @ConditionalOnSingleCandidate(DataSource.class)
  20. // 只有当容器中没有WriterTemplate Bean时,该配置才会生效
  21. @ConditionalOnMissingBean
  22. // 通过@AutoConfigureOrder注解指定该配置方法
  23. // 比下一个配置WriterTemplate的方法的优先级更高
  24. // @AutoConfigureOrder 数值越小,优先级越高
  25. @AutoConfigureOrder(99)
  26. public WriterTemplate writerTemplate(DataSource dataSource) throws SQLException
  27. {
  28. return new WriterTemplate(dataSource);
  29. }
  30. @Bean(destroyMethod = "close")
  31. // 只有当前面的WriterTemplate配置没有生效时,该方法的配置才会生效
  32. @ConditionalOnMissingBean
  33. @AutoConfigureOrder(199)
  34. public WriterTemplate writerTemplate2() throws FileNotFoundException
  35. {
  36. File f = new File(this.properties.getDest());
  37. Charset charset = Charset.forName(this.properties.getCharset());
  38. return new WriterTemplate(f, charset);
  39. }
  40. }
复制代码

上面代码中的FunnyProperties类

  1. // 定义属性处理类
  2. @ConfigurationProperties(prefix = FunnyProperties.FUNNY_PREFIX)
  3. public class FunnyProperties
  4. {
  5. public static final String FUNNY_PREFIX = "org.crazyit.funny";
  6. private String dest;
  7. private String charset;
  8. // 省略getter、setter
  9. }
复制代码

接下来在META-INF/spring.factories文件中注册自动配置类

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2. org.crazyit.funny.autoconfigure.FunnyAutoConfiguration
复制代码

然后使用 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. <!-- 自定义的funny-spring-boot-starter依赖 -->
  2. <dependency>
  3. <groupId>org.crazyit</groupId>
  4. <artifactId>funny-spring-boot-starter</artifactId>
  5. <version>0.0.1-SNAPSHOT</version>
  6. </dependency>
复制代码

在application.properties文件添加配置

  1. org.crazyit.funny.dest=f:/abc-98765.txt
  2. org.crazyit.funny.charset=UTF-8
  3. # 指定连接数据库的信息
  4. spring.datasource.url=jdbc:mysql://localhost:3306/funny?serverTimezone=UTC
  5. spring.datasource.username=root
  6. spring.datasource.password=32147
  7. # 配置funny框架的日志级别为debug
  8. logging.level.org.crazyit.funny = debug
复制代码

主类的代码:

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

以上就是一文详解SpringBoot如何创建自定义的自动配置的详细内容,更多关于SpringBoot自动配置的资料请关注晓枫资讯其它相关文章!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表