1. 程式人生 > 資訊 >新品釋出季購物季臨近,晶片短缺問題有增無減

新品釋出季購物季臨近,晶片短缺問題有增無減

springboot的自動裝配是starter的基礎,簡單來說,就是將Bean裝配到Ioc,本文我們先學習redis的starter如何實現自動裝配,然後手寫一個redis的starter的,來學習spring如何通過starter實現自動裝配。

一、學習spring-boot-starter-data-redis如何實現自動裝配

首先,新建一個springboot專案,新增starter依賴

compile("org.springframework.boot:spring-boot-starter-data-redis")

在yml中新增redis資料來源:

  redis:
    database: 
8 host: 127.0.0.1 # password: port: 6379 timeout: 1000ms lettuce: pool: max-active: 20 min-idle: 1 max-idle: 8 max-wait: 10000

編寫一個controller,測試

/**
 * @author cgg
 * @version 1.0
 * @date 2021/4/1
 */
@RestController
@Slf4j
@Api(tags = "測試")
@RequestMapping(
"test") public class TestController { @Resource private RedisTemplate redisTemplate; @GetMapping("/") @ApiOperation("測試") public void helloWorld() { System.out.println("hello world"); } }

可以看到,在專案中,我們並沒有使用註解或者xml將redisTemplate注入到Ioc容器中就可以使用,說明容器中已經存在了,其實這就是springBoot的自動裝配。

其實springboot 通過一個starter依賴就能實現自動裝配,是starter遵守了約定規範,才實現了自動裝配,下面我們就學習一下原理,並學習starter的規範,為我們手寫自己的starter做準備。

springboot實現自動裝配是通過 @SpringBootApplication 註解中的@EnableAutoConfiguration實現的。

@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 {

.......
}

接下來,再看@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類,那我們我們繼續往下看AutoConfigurationImportSelector 實現了 DeferredImportSelector,而DeferredImportSelector實現了ImportSelector

其中的重寫了selectImports ,返回了一個String【】陣列,spring把返回的陣列中的類名全部裝配到容器中。繼續看AutoConfigurationImportSelector程式碼。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

    private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

    private static final String[] NO_IMPORTS = {};

    private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);

    private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";

    private ConfigurableListableBeanFactory beanFactory;

    private Environment environment;

    private ClassLoader beanClassLoader;

    private ResourceLoader resourceLoader;

    private ConfigurationClassFilter configurationClassFilter;

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); //可以看到 autoConfigurationEntry.getConfigurations()才是需要裝配的類名稱陣列。那麼就需要檢視 getAutoConfigurationEntry(annotationMetadata)
} ......

繼續跟到getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) 方法中

    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

由於呼叫棧過深,我說下原始碼的呼叫鏈:getCandidateConfigurations ->loadFactoryNames ->loadSpringFactories ->

在loadSpringFactories 可以發現下面的程式碼

            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

而 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 接下來我們再看一下專案啟動後,getCandidateConfigurations返回值。

這裡看到第一個返回值是MybatisPlusAutoConfiguration 那麼他是怎麼來的呢,再看下一張圖

相信看到這裡,大多數同學已經明白了,其實就是獲取了 每一個 starter 的 "META-INF/spring.factories" 中宣告類全名。

然後再說一下getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) 其他方法的含義:

二、手寫一個spring-boot-starter-cgg-redis