经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring Boot » 查看文章
Spring Boot自动配置原理懂后轻松写一个自己的starter
来源:cnblogs  作者:程序员xiaozhang  时间:2023/2/22 15:23:32  对本文有异议

目前很多Spring项目的开发都会直接用到Spring Boot。因为Spring原生开发需要加太多的配置,而使用Spring Boot开发很容易上手,只需遵循Spring Boot开发的约定就行了,也就是约定大于配置,无需觉得它神奇,它的底层都是使用的Spring。聊完这个原理带着大家轻松写一个自己的starter。

要学习它的自动配置原理首先自己要创建一个Spring Boot项目,创建项目过程很简单就不介绍了。学习它的自动配置原理,首先要看的就是如下这个注解(SpringBootApplication)。这个注解大家都是很熟悉,这个注解是由如下三个注解组成如下:

  1. //第一个注解
  2. @SpringBootConfiguration
  3. //第二个注解
  4. @EnableAutoConfiguration
  5. //第三个注解
  6. @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  7. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  8. public @interface SpringBootApplication {

上面三个注解都是太太重要了,本文由于聊自动配置所以就只讲EnableAutoConfiguration这个注解,Spring Boot的自动配置原理精髓都在这个注解里面。好了那就先看这个注解的代码如下:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @AutoConfigurationPackage
  6. @Import(AutoConfigurationImportSelector.class)
  7. public @interface EnableAutoConfiguration {

看到这个注解一眼就能瞧到它帮助我们导入了一个AutoConfigurationImportSelector 。由于很多地方遇到了这个Import注解,所以先简单说一下这个注解的作用。

1:给Spring容器自动注入导入的类。如下使用,就是帮助Spring容器自动导入了一个TableEntity对象,在项目中你不需要new 对象,也不需要给这个对象加任何注解了,就可以直接使用TableEntity对象了。

  1. @Configuration
  2. @Import(TableEntity.class)
  3. public class TestConfig {
  4. }

2:给容器导入一个ImportSelector,比如上文讲的那个AutoConfigurationImportSelector  。通过字符串数组的方式返回配置类的全限定名,然后把这些类注入到Spring容器中,我们也就可以直接在Spring容器中使用这些类了。

好了讲了上面那2段作用我们主要分析的也就是下面这段代码了。

  1. public class AutoConfigurationImportSelector {
  2. @Override
  3. //作用就是Spring会把这个方法返回的数组中所有全限定名的类注入到Spring容器中
  4. //供使用者直接去使用这些类。
  5. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  6. if (!isEnabled(annotationMetadata)) {
  7. return NO_IMPORTS;
  8. }
  9. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
  10. .loadMetadata(this.beanClassLoader);
  11. //这个方法是Spring Boot 自动配置要说的
  12. AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
  13. annotationMetadata);
  14. return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  15. }

然后我们后面主要分析的也就是getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata),这个方法。

  1. //这个方法中的每个方法都很重要,一个一个说
  2. protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
  3. AnnotationMetadata annotationMetadata) {
  4. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  5. //1:见名知意,获取候选配置类
  6. List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  7. // 2:去除重复的配置类,这个方法太好了。
  8. configurations = removeDuplicates(configurations);
  9. //3 :去除使用者排除的配置类。
  10. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  11. checkExcludedClasses(configurations, exclusions);
  12. configurations.removeAll(exclusions);
  13. configurations = filter(configurations, autoConfigurationMetadata);
  14. fireAutoConfigurationImportEvents(configurations, exclusions);
  15. return new AutoConfigurationEntry(configurations, exclusions);
  16. }

getCandidateConfigurations这个方法的意思就是获取候选的配置类(也就是Spring Boot已经自动配置的那些类),如下:(PS我们一看那个报错信息就能猜出来Spring从这个【META-INF/spring.factories】下找配置类)

  1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  2. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
  3. getBeanClassLoader());
  4. Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
  5. + "are using a custom packaging, make sure that file is correct.");
  6. return configurations;
  7. }

主要找配置类信息的就是如下代码了。

  1. public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
  2. String factoryClassName = factoryClass.getName();
  3. return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
  4. }
  5. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  6. // 1:第一步先从缓存中找,找不到在循环遍历找,
  7. // 由于Spring代码逻辑太复杂,Spring很多地方都采用这种缓存的设计
  8. MultiValueMap<String, String> result = cache.get(classLoader);
  9. if (result != null) {
  10. return result;
  11. }
  12. // public static final String 下面代码用到的常量值如下
  13. // FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  14. try {
  15. // 扫描 代码中的所有META-INF/spring.factories"文件
  16. Enumeration<URL> urls = (classLoader != null ?
  17. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  18. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  19. result = new LinkedMultiValueMap<>();
  20. //循环遍历加载上面所说的文件下的文件,并把它们放入到
  21. // LinkedMultiValueMap中
  22. while (urls.hasMoreElements()) {
  23. URL url = urls.nextElement();
  24. UrlResource resource = new UrlResource(url);
  25. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  26. for (Map.Entry<?, ?> entry : properties.entrySet()) {
  27. String factoryClassName = ((String) entry.getKey()).trim();
  28. for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
  29. result.add(factoryClassName, factoryName.trim());
  30. }
  31. }
  32. }
  33. //放缓存中一份,后面要加载从这个缓存中直接取,
  34. // 如果看全代码可知Spring Boot 缓存的不止有配置类,还有其他类。
  35. cache.put(classLoader, result);
  36. return result;
  37. }
  38. catch (IOException ex) {
  39. throw new IllegalArgumentException("Unable to load factories from location [" +
  40. FACTORIES_RESOURCE_LOCATION + "]", ex);
  41. }
  42. }

从上面代码可知Spring主要是从META-INF/spring.factories文件中加载配置类,那么就带大家看一看Spring Boot自己已经配置的类有哪些。

后面就回到这个(removeDuplicates)去重方法,如下:

  1. protected final <T> List<T> removeDuplicates(List<T> list) {
  2. return new ArrayList<>(new LinkedHashSet<>(list));
  3. }

为什么要把这单独一行代码列出来呢?是因为我感觉这段去重复代码用的太好了,自从看了这段代码,后面博主自己写去重逻辑的时候也就参照Spring大佬这一行代码写去重逻辑(PS:如果自己业务去重逻辑没有其他逻辑的时候参考使用),简单,效率应该也不低毕竟大佬们这样用了。

后面代码逻辑就是一些去除用户自己要排除,要过滤掉的配置类。然后就会使用Spring的ImportSelector这个特性(PS具体Spring是怎么把这些返回权限定名的类加载的容器中的,是Spring加载类方面的知识,本文不做具体介绍)

好了,然后带着大家创建一个自己的starter(PS:命名规范我是参考了mybatis-spring,毕竟是大神们的命名规范,记好约定大于配置,哈哈哈)的starter 。

  1. 1: 创建一个工程,信息如下:
  2. <groupId>scott-spring-boot-starter</groupId>
  3. <artifactId>scottspringbootstarter</artifactId>
  4. <version>0.0.1-SNAPSHOT</version>
  5. 2:再创建一个工程 也就是autoconfigure项目。
  6. 如下:
  7. <groupId>com.spring.starter</groupId>
  8. <artifactId>scott-spring-boot-starter-autoconfigure</artifactId>
  9. <version>0.0.1-SNAPSHOT</version>
  10. pom文件中引入如下(一般下面的是必须引入的):
  11. <parent>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-parent</artifactId>
  14. <version>2.1.8.RELEASE</version>
  15. <relativePath></relativePath>
  16. </parent>
  17. <dependencies>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter</artifactId>
  21. </dependency>
  22. </dependencies>
  23. 3:创建HelloService
  24. public class HelloService {
  25. HelloProperties helloProperties ;
  26. public String sayHello(String name){
  27. return helloProperties.getPrefix()+"-"+name+helloProperties.getSuffix() ;
  28. }
  29. public HelloProperties getHelloProperties() {
  30. return helloProperties;
  31. }
  32. public void setHelloProperties(HelloProperties helloProperties) {
  33. this.helloProperties = helloProperties;
  34. }
  35. }
  36. 4: 创建相应的properies文件。
  37. @ConfigurationProperties(prefix="scott.hello") //以 scott.hello开头的。
  38. public class HelloProperties {
  39. private String prefix ;
  40. private String suffix ;
  41. public String getPrefix() {
  42. return prefix;
  43. }
  44. public void setPrefix(String prefix) {
  45. this.prefix = prefix;
  46. }
  47. public String getSuffix() {
  48. return suffix;
  49. }
  50. public void setSuffix(String suffix) {
  51. this.suffix = suffix;
  52. }
  53. 5:创建自定义的配置文件如下:
  54. @Configuration
  55. @ConditionalOnWebApplication // 在web环境下才生效
  56. @EnableConfigurationProperties(HelloProperties.class) // 属性文件生效
  57. public class HelloServiceAutoConfiguration {
  58. @Autowired
  59. HelloProperties helloProperties;
  60. @Bean
  61. public HelloService helloService() {
  62. HelloService service = new HelloService();
  63. service.setHelloProperties(helloProperties);
  64. return service;
  65. };
  66. }

6:在META-INF 文件夹下创建 spring.factories 文件,写入如下自己的配置类 。Spring Boot自动配置规约,约定大于规范,如下图的配置所示:

7:在scottspringbootstarter项目的pom文件中引入自定义的 autoconfigure如下:

  1. <groupId>scott-spring-boot-starter</groupId>
  2. <artifactId>scottspringbootstarter</artifactId>
  3. <version>1.0-SNAPSHOT</version>
  4. <dependencies>
  5. <dependency>
  6. <groupId>com.spring.starter</groupId>
  7. <artifactId>scott-spring-boot-starter-autoconfigure</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. </dependency>
  10.  
  11. </dependencies>

8:自定义starter就好了,然后就可以在我们自定义的工程中引入scottspringbootstarter就可以使用了。

如下使用方法,配置yml文件,然后使用对应的服务,So Easy:

 

原文链接:https://www.cnblogs.com/scott1102/p/17140461.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号