美文网首页
SpringBoot之手撸Starter

SpringBoot之手撸Starter

作者: 香芋牛奶面包 | 来源:发表于2019-09-26 16:02 被阅读0次

    引言

    前文说过,Spring Boot最大的优势在于自动配置,我们只需要引入一个个第三方提供的starter就可以快速集成第三方功能,既然starter这么方便,我们自己应该也可以写一个。

    快速写一个starter

    • 新建一个maven项目
    <groupId>com.walm</groupId>
        <artifactId>boot-test-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
    
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
                <version>2.0.6.RELEASE</version>
            </dependency>
        </dependencies>
    
    • 新建一个config类
    @Configuration
    public class HelloWorldConfig {
    
        @Bean
        public HelloWorld HelloWord() {
            return new HelloWorld();
        }
    }
    
    • 新建一个提供服务的service类
    public class HelloWorld {
    
        public void sayHello() {
            System.out.println("hello world !!!!");
        }
    }
    
    • 最重要的是一步,需要让spring能够扫描到我们的 config HelloWorldConfig 类
      • 在resource 文件夹下新建一个 META-INF 文件夹
      • 新建一个 spring.factories 文件

    在spring.factories文件中配置我们的写的HelloWorldConfig类

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.walm.boottest.HelloWorldConfig
    

    这样一个简单的 springboot starter 就完成了,最后本地安装 install我们的starter jar包就可以在其他项目中引用了

    • 引用starter,只需要在项目引用 该jar包即可
    <dependency>
                <groupId>com.walm</groupId>
                <artifactId>boot-test-starter</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
    

    下面我们简单分析一下 stater的原理

    starter 自动配置原理

    首先自动配置的原理关键一定是在 spring.factories 文件是如何被加载到的这个点上去探究

    需要从spring boot的启动过程一步一步的去看了

    首先,我们启动一个spring-boot项目,肯定会有一个配置注解 SpringBootApplication 那么这个注解到底起什么作用呢?

    • SpringBootApplication.class
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = {
            @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
            @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    
        /**
         * Exclude specific auto-configuration classes such that they will never be applied.
         * @return the classes to exclude
         */
        @AliasFor(annotation = EnableAutoConfiguration.class)
        Class<?>[] exclude() default {};
    
        /**
         * Exclude specific auto-configuration class names such that they will never be
         * applied.
         * @return the class names to exclude
         * @since 1.3.0
         */
        @AliasFor(annotation = EnableAutoConfiguration.class)
        String[] excludeName() default {};
    
        /**
         * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
         * for a type-safe alternative to String-based package names.
         * @return base packages to scan
         * @since 1.3.0
         */
        @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
        String[] scanBasePackages() default {};
    
        /**
         * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
         * scan for annotated components. The package of each class specified will be scanned.
         * <p>
         * Consider creating a special no-op marker class or interface in each package that
         * serves no purpose other than being referenced by this attribute.
         * @return base packages to scan
         * @since 1.3.0
         */
        @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
        Class<?>[] scanBasePackageClasses() default {};
    
    }
    

    这里着重看 EnableAutoConfiguration 这个注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        /**
         * Exclude specific auto-configuration classes such that they will never be applied.
         * @return the classes to exclude
         */
        Class<?>[] exclude() default {};
    
        /**
         * Exclude specific auto-configuration class names such that they will never be
         * applied.
         * @return the class names to exclude
         * @since 1.3.0
         */
        String[] excludeName() default {};
    
    }
    

    可以看到 @Import(AutoConfigurationImportSelector.class)这个注解,这里就是我们自动配置的入口
    注意,解析Import注解其实是通过 BeanFactoryPostProcessor 接口来扩展的

    下面我们来看下AutoConfigurationImportSelector.class 类的实现,AutoConfigurationImportSelector 实现了ImportSelector接口

    • ImportSelector
    public interface ImportSelector {
    
        /**
         * Select and return the names of which class(es) should be imported based on
         * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
         */
        String[] selectImports(AnnotationMetadata importingClassMetadata);
    
    }
    

    在解析import注解的时候会调用指定ImportSelector 接口实现 的 selectImports方法。下面我们看下AutoConfigurationImportSelector的 selectImports方法实现

    • selectImports
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            }
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 获取  META-INF/spring.factories  文件中的配置信息
            List<String> configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            configurations = removeDuplicates(configurations);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
        // 触发事件
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    

    总结

    写一个starter还是非常简单的,核心就是要让springboot自动扫描到我们的 配置类,核心逻辑其实就是通过扫描我们的自定义的META-INF/spring.factories文件来进行自动配置

    Spring 系列

    相关文章

      网友评论

          本文标题:SpringBoot之手撸Starter

          本文链接:https://www.haomeiwen.com/subject/dnwhyctx.html