Springboot淺析(一)——自動配置

爲何學習Springboot勒,一方面主要實在是Springboot應用得太普遍了,加深對其的理解對本身的開發工做頗有必要,由於若是遇到或業務場景須要進行一些稍微高級點的基於Springboot的擴展以及應用,可能就會不明白不理解。另外一方面就是做爲一個編碼愛好者自己對springboot這麼一個優秀的框架的好奇。文章基於SpringBoot2.1.9。spring

1、SpringBoot的手動裝配

(一) 手動裝配的方式

主要包含如下幾種方式:數組

  • 使用模式註解 @Component 等(Spring2.5+),經常使用,但沒法裝配jar包目錄的組件,爲此可使用 @Configuration 與 @Bean,手動裝配組件
  • 使用配置類 @Configuration 與 @Bean (Spring3.0+),註冊過多,會致使編碼成本高,維護不靈活等問題。
  • 使用模塊裝配 @EnableXXX 與 @Import (Spring3.1+),@Import用於裝配指定的類,@EnableXXX用於啓用指定的@Import

前面一兩種都是在項目中常常會用到的,就不過多介紹了,這裏主要介紹下使用模塊裝配,這也是Spring各類Starter得以被裝配的主要方式。緩存

(二)@EnableXXX與@Import的使用

這裏分四種場景,導入普通類、導入配置類、導入ImportSelector,導入ImportBeanDefinitionRegistrar。接下來用4個小demo來演示怎麼使用。springboot

1.導入普通類

咱們的目標是導入一個TestService類,TestService是一個普通的類。框架

步驟

(1)新建一個註解EnableTest
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(TestService.class)
public @interface EnableTest {
}
(2)在能被掃描的配置類或啓動類標註 @EnableTest
@SpringBootApplication
@EnableTest
public class SpringbootexampleApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringbootexampleApplication.class, args);
  }
}

2.導入配置類

沒有位與啓動類下的@Configuration註解默認狀況是不會被掃描的,這個時候要裝配@Configuration標註的類以及其下面註冊的bean,能夠經過這種方式手動裝配。ide

步驟

(1)新建一個配置類
@Configuration
public class TestRegistrarConfiguration {
    @Bean
    public Test2Service yellow() {
        return new Test2Service();
    }
}
(2)修改EnableTest,添加該配置類的引入
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({TestService.class,TestRegistrarConfiguration.class})
public @interface EnableTest {
}

這樣啓動時也間接裝配了TestRegistrarConfiguration配置類下面的全部bean。函數

3.導入ImportSelector

ImportSelector接口規定了String[] selectImports(AnnotationMetadata importingClassMetadata)方法,這個方法的返回值String數組,表示要裝配的bean的className數組。SpringBoot的自動裝配,實際上就是用的這種方式。spring-boot

步驟

(1) 新建一個ImportSelector
public class TestImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 返回類.getName()。
        return new String[]{Test3Service.class.getName()};
    }
}

注:參數importingClassMetadata,表明的是被 @Import 標記的類的信息。(不算註解),這裏就是@EnableTest修飾的配置類即SpringbootexampleApplication。學習

(2)修改EnableTest,添加該導入選擇器的引入

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({TestService.class, TestRegistrarConfiguration.class, TestImportSelector.class})
public @interface EnableTest {
}

4. 導入ImportBeanDefinitionRegistrar

ImportBeanDefinitionRegistrar接口規定了 void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)方法, 這裏有兩個參數AnnotationMetadata跟前面ImportSelector接口方法參數同樣,registry是用於註冊BeanDefinition的註冊者,BeanDefinition是整個Bean信息的封裝,容器刷新時能夠根據容器類的BeanDefinition建立實例。this

步驟

(1)新建一個ImportBeanDefinitionRegistrar
public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        registry.registerBeanDefinition("Test4Service", new RootBeanDefinition(Test4Service.class));
    }
}

(2)修改EnableTest,添加該導入bean定義的註冊器的引入

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({TestService.class, TestRegistrarConfiguration.class, TestImportSelector.class, TestImportBeanDefinitionRegistrar.class})
public @interface EnableTest {
}

2、SpringBoot的自動配置

自動配置其實就是Spring框架應用了手動裝配的原理,裝配了本身的默認組件,也提供了一些擴展點,可讓應用擴展咱們本身的組件。固然,SpringBoot對於應用basePackage下面擴展的組件經過組件掃描@ComponentScan也能完成配置類的裝配。

(一)簡單看下@ComponentScan

咱們知道@SpringBootApplication是一個組合註解,包含@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。以下:

@SpringBootConfiguration  
@EnableAutoConfiguration  
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication

這裏@ComponentScan中默認定義了兩個過濾器,TypeExcludeFilter與AutoConfigurationExcludeFilter,其中TypeExcludeFilter的核心代碼以下:

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
        throws IOException {
    if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {
        //獲取容器內實現了TypeExcludeFilter的全部bean
        Collection<TypeExcludeFilter> delegates = ((ListableBeanFactory) this.beanFactory)
                .getBeansOfType(TypeExcludeFilter.class).values();
        for (TypeExcludeFilter delegate : delegates) {
 		//遍歷獲取的TypeExcludeFilter的bean,若是知足,則經過
            if (delegate.match(metadataReader, metadataReaderFactory)) {
                return true;
            }
        }
    }
    return false;
}

主要提供一個擴展點,獲取容器內的全部TypeExcludeFilter類型的bean,執行一下match方法,返回true的話,則進行過濾。其中兩個參數metadataReader用於讀取掃描獲得的類型信息。metadataReaderFactory用於獲取其餘類型的metadataReader。好比若是不想掃描某個類,則註冊一個TypeExcludeFilter的子類bean,其match方法的邏輯是從metadataReader獲取ClassName,而後判斷若和xxxClass相等則返回true進行過濾就好了。

AutoConfigurationExcludeFilter的做用則是排除是不是自動裝配的Configuration,若是是則進行掃描過濾,其核心代碼以下:

@Override  
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)  
  throws IOException {  
  return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);  
}  
  
private boolean isConfiguration(MetadataReader metadataReader) {  
  return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());  
}  
  
private boolean isAutoConfiguration(MetadataReader metadataReader) {  
  return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());  
}  
  
protected List<String\> getAutoConfigurations() {  
  if (this.autoConfigurations == null) {  
  this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,  
 this.beanClassLoader);  
  }  
  return this.autoConfigurations;  
}

isConfiguration(metadataReader) 判斷是否帶有@Configuration註解, isAutoConfiguration(metadataReader)判斷是不是自動裝配類(後面仔細介紹這裏的自動裝配方式),因此經過這咱們知道@Component組件掃描和自動裝配機制並不衝突。

2、自動配置原理

核心註解是@EnableAutoConfiguration。其內容以下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

(一)AutoConfigurationPackage的做用

其中,@AutoConfigurationPackage的內容以下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage

這裏引入了一個AutoConfigurationPackages的內部類:Registrar,來看看內容:

/**
	 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
	 * configuration.
	* (用於保存導入的配置類所在的根包。)
	 */
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }

}

register(registry, new PackageImport(metadata).getPackageName())掉用了外部類AutoConfigurationPackages的register方法,由於metadata這裏實際上就是啓動類的元數據,因此new PackageImport(metadata).getPackageName()返回的就是啓動類的包路徑。再看看AutoConfigurationPackages的register方法:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {  
 private static final String BEAN = AutoConfigurationPackages.class.getName();

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 判斷 BeanFactory 中是否包含名爲AutoConfigurationPackages.class.getName()的beanDefinition
    if (registry.containsBeanDefinition(BEAN)) {
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
        // addBasePackages:merge當前的packageNames參數(根包)到原來的構造器參數裏面
        constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
    }
    else {
	   //構造一個BasePackages的BD,設置構造器參數爲當前的packageNames參數
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}
}

綜上,AutoConfigurationPackage的做用就是註冊一個BasePackage的BeanDefinition,其構造函數包含EnableAutoConfiguration標識的類的包路徑。

(二)@Import(AutoConfigurationImportSelector.class)的做用

看一下AutoConfigurationImportSelector:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
    // 加載自動配置類
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, 
            annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

返回值是StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()),重點看看autoConfigurationEntry裏面的Configurations怎麼生成的,進入getAutoConfigurationEntry方法:

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

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

這裏的關鍵是 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());前面一個參數是EnableAutoConfiguration.class,後面是一個類加載器。 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());這個方法在Springboot源碼中是很是很是常見的,好比容器啓動獲取初始化器ApplicationContextInitializer實例等等,接下來看看這裏作了什麼:

blic static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
	//經過factoryClassName的className從一個map裏面取相應的className集合
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   //若是有,這從緩存裏面拿,不然去生成
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }
	//使用 classLoader 去加載了指定常量路徑下的資源: FACTORIES_RESOURCE_LOCATION ,而這個常量指定的路徑實際是:META-INF/spring.factories 。
    try {
        Enumeration<URL> urls = (classLoader != null ?
                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
			//以 Properties 的形式加載
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					//鍵爲Properties文件的鍵,值爲properties文件的值用逗號分隔
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                       FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

spring.factories能夠在任意工程的meta-inf目錄,例如咱們能夠在 spring-boot-autoconfiguration 包下找到spring.factories文件如圖:

綜上@Import(AutoConfigurationImportSelector.class)的做用就是加載spring.factories文件下標識的以EnableAutoConfiguration的className爲鍵的classNames集合,而後把這些對應的class的BeanDefinition註冊到容器。若是有對JDK的SPI機制有了解的朋友會感受很熟悉,這裏的這個機制有點相似JDK原生的SPI機制, 都是從配置文件獲取相應的className並實例化,jdk的SPI指的是擴展者針對某接口本身定義實現類,作法也是在meta-inf/service目錄下對對應接口的配置文件內容進行編輯,標識實現類的className。可是我的認爲仍是與SpringFactoryLoader這裏有些區別。JDK的SPI是將加載的類抽象爲策略(策略模式),經過抽象類加載配置文件配置的實現。Spring更像是將加載的類抽象爲產品(工廠模式),經過給定工廠的類全限定名加載配置文件配置的類全限定名,從而能夠以此加載配置的類。

(三)小結

自動配置的核心註解@EnableAutoConfiguration主要作了兩件事:

  1. 裝配AutoConfigurationPackages.Registrar,註冊者註冊了一個類爲BasePackages的BeanDefinition,而且在這個BD的構造器參數上添加了啓動類路徑。
  2. 裝配AutoConfigurationImportSelector,選擇器掃描了META-INF目錄下spring.factories文件中鍵爲EnableAutoConfiguration.class.getName的全部類。將其BeanDefinition註冊進入了容器。

3、總結

今天主要介紹了手動裝配與自動配置,瞭解了Springboot自動配置的原理。經過此咱們在平時也能夠開發一些本身針對其餘框架的封裝,或者本身開發一套組件,而後其餘項目引入jar包自動進行配置,不用再本身進行一次配置,是很方便的。

相關文章
相關標籤/搜索