1. 程式人生 > 實用技巧 >tensorflow 三種模型:ckpt、pb、pb-savemodel

tensorflow 三種模型:ckpt、pb、pb-savemodel

配置檔案載入順序

SpringBoot也可以從以下位置載入配置; 優先順序從高到低;高優先順序的配置覆蓋低優先順序的配置,所有的配置會形成互補配置

  1. 命令列引數

    所有的配置都可以在命令列上進行指定

    java -jar xxx.jar --server.port=8087  --server.context-path=/abcCopy to clipboardErrorCopied
    

    多個配置用空格分開; --配置項=值

  2. 來自java:comp/env的JNDI屬性 ⤴️

  3. Java系統屬性(System.getProperties()) ⤴️

  4. 作業系統環境變數 ⤴️

  5. RandomValuePropertySource配置的random.*屬性值 ⤴️

由jar包外向jar包內進行尋找;

優先載入帶profile

  1. jar包外部的application-{profile}.propertiesapplication.yml(帶spring.profile)配置檔案 ⤴️

  2. jar包內部的application-{profile}.propertiesapplication.yml(帶spring.profile)配置檔案 ⤴️

再來載入不帶profile

  1. jar包外部的application.propertiesapplication.yml(不帶spring.profile)配置檔案 ⤴️

  2. jar包內部的application.properties

    application.yml(不帶spring.profile)配置檔案 ⤴️

  3. @Configuration註解類上的@PropertySource ⤴️

  4. 通過SpringApplication.setDefaultProperties指定的預設屬性 ⤴️

所有支援的配置載入來源:

參考官方文件

自動配置的執行流程

  • @SpringBootApplication
1. @SpringBootApplication // 由啟動類的@SpringBootApplication開啟自動配置
    
    
2. @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //標至該類是一個配置類,與@Configuration作用一致
@EnableAutoConfiguration //啟動
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
     

從Spring3.0,@Configuration用於定義配置類,可替換xml配置檔案,被註解的類內部包含有一個或多個被@Bean註解的方法,這些方法將會被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器。

注意:@Configuration註解的配置類有如下要求:

  1. @Configuration不可以是final型別;
  2. @Configuration不可以是匿名類;
  3. 巢狀的configuration必須是靜態類。

一、用@Configuration載入spring
1.1、@Configuration配置spring並啟動spring容器
1.2、@Configuration啟動容器+@Bean註冊Bean
1.3、@Configuration啟動容器+@Component註冊Bean
1.4、使用 AnnotationConfigApplicationContext 註冊 AppContext 類的兩種方法

1.5、配置Web應用程式(web.xml中配置AnnotationConfigApplicationContext)

二、組合多個配置類
2.1、在@configuration中引入spring的xml配置檔案
2.2、在@configuration中引入其它註解配置
2.3、@configuration巢狀(巢狀的Configuration必須是靜態類)
三、@EnableXXX註解
四、@Profile邏輯組配置
五、使用外部變數

  • EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //當SpringBoot應用啟動時預設會將啟動類所在的package作為自動配置的package。
@Import(AutoConfigurationImportSelector.class) //@EnableAutoConfiguration註解是Spring Boot中配置自動裝載的總開關。
public @interface EnableAutoConfiguration {
}

boot.autoconfigure.EnableAutoConfiguration註解

-> @Import了一個AutoConfigurationImportSelector例項

-> AutoConfigurationImportSelector類(implement ImportSelector),實現了selectImports() 方法,用來篩選被@Import的Configuration類(減去exclude等)

  • AutoConfigurationImportSelector.class
public class AutoConfigurationImportSelector implementsDeferredImportSelector, BeanClassLoaderAware,
    ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
  //......
  @Override
  publicString[] selectImports(AnnotationMetadata annotationMetadata) {
    // 如果AutoConfiguration沒開,返回{}
    if(!isEnabled(annotationMetadata)) {
      returnNO_IMPORTS;
    }
    // 將spring-autoconfigure-metadata.properties的鍵值對配置載入到PropertiesAutoConfigurationMetadata物件中並返回
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    // 基於各種配置計算需要import的configuration和exclusion
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
        annotationMetadata);
    returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
  }
      
  // 判斷AudoConfiguration是否開啟
  protectedbooleanisEnabled(AnnotationMetadata metadata) {
    if(getClass() == AutoConfigurationImportSelector.class) {
      // 如果配置檔案中有"spring.boot.enableautoconfiguration",返回該欄位的值;否則返回true
      returngetEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
    }
    returntrue;
  }
      
  protectedAutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
    if(!isEnabled(annotationMetadata)) {
      returnEMPTY_ENTRY;
    }
    // 獲取註解的屬性值
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 從META-INF/spring.factories檔案中獲取EnableAutoConfiguration所對應的configurations,但並不例項化,還要篩選
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 去重,List轉Set再轉List
    configurations = removeDuplicates(configurations);
    // 從註解的exclude/excludeName屬性中獲取排除項
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 對於不屬於AutoConfiguration的exclude報錯
    checkExcludedClasses(configurations, exclusions);
    // 從configurations去除exclusions
    configurations.removeAll(exclusions);
    // 所有AutoConfigurationImportFilter類例項化,並再進行一次篩選
    configurations = filter(configurations, autoConfigurationMetadata);
    // 例項化剩下configuration中的類,並把AutoConfigurationImportEvent繫結在所有AutoConfigurationImportListener子類例項上,當fireAutoConfigurationImportEvents事件被觸發時,打印出已經註冊到spring上下文中的@Configuration註解的類,打印出被阻止註冊到spring
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 返回(configurations, exclusions)組
    return newAutoConfigurationEntry(configurations, exclusions);
  }
  // ......
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    
   		// getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class;

		// getBeanClassLoader()這裡使用的是AppClassLoader。

		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;
	}

  • getCandidateConfigurations方法中,SpringFactoriesLoader.loadFactoryNames(),掃描所有jar包類路徑下 META-INF/spring.factories,並對相應的key值進行篩選,這裡使用的key值為org.springframework.boot.autoconfigure.EnableAutoConfiguration。
  • 把掃描到的這些檔案的內容包裝成properties物件,以 Properties 型別(即 key-value 形式)配置,就可以將相應的實現類注入 Spirng 容器中(key為factory型別)。從properties中獲取到EnableAutoConfiguration.class(類名)對應的值,然後把它們新增在容器中
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
  		// loadSpringFactories方法是獲取所有的springFactories
		// getOrDefault是通過key,獲取到對應的類的集合。因為value是通過逗號相隔的,可以有多個,所以是list
		// getOrDefault如果存在就返回,如果不存在那麼就返回給的預設值
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}


private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   
    MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   try {
       // 三目表示式,判斷引數classLoader是否為空,如果不為空,那麼直接使用傳入的classLoader獲取META-INF/spring.factories
			// 如果為空,那麼就使用系統的classLoader來獲取META-INF/spring.factories
			// 總之健壯性比較強,
      Enumeration<URL> urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      while (urls.hasMoreElements()) {
          // 通過迴圈遍歷所有的META-INF/spring.factories
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
    // 找到的每個 META-INF/spring.factories 檔案都是一個 Properties 檔案,將其內容載入到一個 Properties 物件然後處理其中的每個屬性
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {        
             // 獲取工廠類名稱(介面或者抽象類的全限定名)
             String factoryTypeName = ((String) entry.getKey()).trim();
               // 將逗號分割的屬性值逐個取出,然後放到 結果result 中去
            for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
               result.add(factoryTypeName, factoryImplementationName.trim());
            }
         }
      }
       
     // 篩選出的結果集Map放入記憶體中,
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
  • 在getConfigurationClassFilter與fireAutoConfigurationImportEvents方法中將其通過SpringFactoriesLoader 中的loadFactories反射對所有的配置進行篩選,例項化,並繫結到AutoConfigurationImportListener子類例項上
https://blog.csdn.net/qq_42154259/article/details/107600734
protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
	}

protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
	}

/**
	 * 通過classLoader從各個jar包的classpath下面的META-INF/spring.factories載入並解析其key-value值,然後建立其給定型別的工廠實現
	 *
	 * 返回的工廠通過AnnotationAwareOrderComparator進行排序過的。
	 * AnnotationAwareOrderComparator就是通過@Order註解上面的值進行排序的,值越高,則排的越靠後
	 *
	 * 如果需要自定義例項化策略,請使用loadFactoryNames方法獲取所有註冊工廠名稱。
	 *
	 * @param  factoryType 介面或者抽象類的Class物件
	 * @param  classLoader 用於載入的類載入器(可以是null,如果是null,則使用預設值)
	 * @throws IllegalArgumentException 如果無法載入任何工廠實現類,或者在例項化任何工廠時發生錯誤,則會丟擲IllegalArgumentException異常
	 */
	public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
		// 首先斷言,傳入的介面或者抽象類的Class物件不能為空
		Assert.notNull(factoryType, "'factoryType' must not be null");
		// 將傳入的classLoader賦值給classLoaderToUse
		// 判斷classLoaderToUse是否為空,如果為空,則使用預設的SpringFactoriesLoader的classLoader
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		// 載入所有的META-INF/spring.factories並解析,獲取其配置的factoryNames
		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
		if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
		}

		List<T> result = new ArrayList<>(factoryImplementationNames.size());
		// 通過反射對所有的配置進行例項化。
		for (String factoryImplementationName : factoryImplementationNames) {
			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
		}
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}

// 例項化工廠,根據
	@SuppressWarnings("unchecked")
	private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
		try {
			// 根據全限定類名通過反射獲取Class物件
			Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
			// 判斷獲取的Class物件是否從factoryType裡面來,
			// 說具體點就是判斷我們配置的spring.factories中的許可權定類名所對應的類是否是對應的子類或者實現類
			// 比如
			// org.springframework.context.ApplicationContextInitializer=\
			// org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
			// org.springframework.boot.context.ContextIdApplicationContextInitializer,
			// 那麼他就會驗證ConfigurationWarningsApplicationContextInitializer和ContextIdApplicationContextInitializer是否是ApplicationContextInitializer的子類
//			isAssignableFrom()方法與instanceof關鍵字的區別總結為以下兩個點:
//			isAssignableFrom()方法是從類繼承的角度去判斷,instanceof關鍵字是從例項繼承的角度去判斷。
//			isAssignableFrom()方法是判斷是否為某個類的父類,instanceof關鍵字是判斷是否某個類的子類。
			// 如果不是,則會儲存
			if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
				throw new IllegalArgumentException(
						"Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
			}
			// 通過反射的有參建構函式進行例項化:如果直接newInstance的話,那麼只能通過空參建構函式進行例項化。
			// 通過這種方式可以通過不同引數的建構函式進行建立例項,但是這裡並沒有傳入引數,所以呼叫的是預設空慘建構函式
			return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
		}
		catch (Throwable ex) {
			throw new IllegalArgumentException(
				"Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
				ex);
		}
	}
  • 可見selectImports()是AutoConfigurationImportSelector的核心函式,其核心功能就是獲取spring.factories中EnableAutoConfiguration所對應的Configuration類列表,由@EnableAutoConfiguration註解中的exclude/excludeName引數篩選一遍,再由AutoConfigurationImportFilter類所有例項篩選一遍,得到最終的用於Import的configuration和exclusion。

  • 該函式是被誰呼叫的呢?在org.springframework.context.annotation.ConfigurationClassParser類中被processImports()呼叫,而processImports()函式被doProcessConfigurationClass()呼叫。下面從doProcessConfigurationClass() 看起。

  • 上面為@Import()的Spring的註釋的底層實現

  • 接著看我們EnableAutoConfiguration.class(類名)對應的值:

  • 每一個這樣的 xxxAutoConfiguration類都是容器中的一個元件,都加入到容器中;用他們來做自動配置;

  • 每一個自動配置類進行自動配置功能;

  • eg :HttpEncodingAutoConfiguration

package org.springframework.boot.autoconfigure.web.servlet;

......

//表示這是一個配置類,以前編寫的配置檔案一樣,也可以給容器中新增元件
@Configuration(
    proxyBeanMethods = false
)

/**
 * 啟動指定類的ConfigurationProperties功能;
 * 將配置檔案中對應的值和HttpProperties繫結起來;
 * 並把HttpProperties加入到ioc容器中
 */
@EnableConfigurationProperties({HttpProperties.class})

/**
 * Spring底層@Conditional註解
 * 根據不同的條件,如果滿足指定的條件,整個配置類裡面的配置就會生效;
 * 判斷當前應用是否是web應用,如果是,當前配置類生效
 */
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

//判斷當前專案有沒有這個類
@ConditionalOnClass({CharacterEncodingFilter.class})

/**
 * 判斷配置檔案中是否存在某個配置  spring.http.encoding.enabled;如果不存在,判斷也是成立的
 * 即使我們配置檔案中不配置pring.http.encoding.enabled=true,也是預設生效的;
 */
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {

    //它已經和SpringBoot的配置檔案映射了
    private final Encoding properties;

    //只有一個有參構造器的情況下,引數的值就會從容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }

    @Bean     //給容器中新增一個元件,這個元件的某些值需要從properties中獲取
    @ConditionalOnMissingBean    //判斷容器有沒有這個元件?(容器中沒有才會新增這個元件)
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
        filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
        return filter;
    }

    ......
  1. 根據當前不同的條件判斷,決定這個配置類是否生效
  2. 一但這個配置類生效;這個配置類就會給容器中新增各種元件;這些元件的屬性是從對應的properties類中獲取的,這些類裡面的每一個屬性又是和配置檔案繫結的;
  3. 相應的配置類在@EnableConfigurationProperties中指定,其會在讀取我們寫的properties並將值注入類中,由xxxAutoConfiguration 讀取 例項化的xxxxProperties類,根據屬性生成相應的bean給ApplicationContext呼叫。

所有在配置檔案中能配置的屬性都是在xxxxProperties類中封裝著;配置檔案能配置什麼就可以參照某個功能對應的這個屬性類

@ConfigurationProperties(
    prefix = "spring.http"
)
public class HttpProperties {
    private boolean logRequestDetails;
    private final HttpProperties.Encoding encoding = new HttpProperties.Encoding();

我們配置時的流程:

  • SpringBoot啟動會載入大量的自動配置類

  • 我們看我們需要的功能有沒有SpringBoot預設寫好的自動配置類

  • 再來看這個自動配置類中到底配置了哪些元件;(只要我們要用的元件有,我們就不需要再來配置了)

  • 給容器中自動配置類新增元件的時候,會從properties類中獲取某些屬性。我們就可以在配置檔案中指定這些屬性的值

  • xxxxAutoConfigurartion:自動配置類;

    xxxxProperties:封裝配置檔案中相關屬性;

Apllication總體流程

SpringApplication的run方法的實現是我們本次旅程的主要線路,該方法的主要流程大體可以歸納如下:

1) 如果我們使用的是SpringApplication的靜態run方法,那麼,這個方法裡面首先要建立一個SpringApplication物件例項,然後呼叫這個建立好的SpringApplication的例項方法。在SpringApplication例項初始化的時候,它會提前做幾件事情:

  • 根據classpath裡面是否存在某個特徵類(org.springframework.web.context.ConfigurableWebApplicationContext)來決定是否應該建立一個為Web應用使用的ApplicationContext型別。
  • 使用SpringFactoriesLoader在應用的classpath中查詢並載入所有可用的ApplicationContextInitializer。
  • 使用SpringFactoriesLoader在應用的classpath中查詢並載入所有可用的ApplicationListener。
  • 推斷並設定main方法的定義類。

2) SpringApplication例項初始化完成並且完成設定後,就開始執行run方法的邏輯了,方法執行伊始,首先遍歷執行所有通過SpringFactoriesLoader可以查詢到並載入的SpringApplicationRunListener。呼叫它們的started()方法,告訴這些SpringApplicationRunListener,“嘿,SpringBoot應用要開始執行咯!”。

3) 建立並配置當前Spring Boot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。

4) 遍歷呼叫所有SpringApplicationRunListener的environmentPrepared()的方法,告訴他們:“當前SpringBoot應用使用的Environment準備好了咯!”。

5) 如果SpringApplication的showBanner屬性被設定為true,則列印banner。

6) 根據使用者是否明確設定了applicationContextClass型別以及初始化階段的推斷結果,決定該為當前SpringBoot應用建立什麼型別的ApplicationContext並建立完成,然後根據條件決定是否新增ShutdownHook,決定是否使用自定義的BeanNameGenerator,決定是否使用自定義的ResourceLoader,當然,最重要的,將之前準備好的Environment設定給建立好的ApplicationContext使用。

7) ApplicationContext建立好之後,SpringApplication會再次藉助Spring-FactoriesLoader,查詢並載入classpath中所有可用的ApplicationContext-Initializer,然後遍歷呼叫這些ApplicationContextInitializer的initialize(applicationContext)方法來對已經建立好的ApplicationContext進行進一步的處理。

8) 遍歷呼叫所有SpringApplicationRunListener的contextPrepared()方法。

9) 最核心的一步,將之前通過@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置載入到已經準備完畢的ApplicationContext。

10) 遍歷呼叫所有SpringApplicationRunListener的contextLoaded()方法。

11) 呼叫ApplicationContext的refresh()方法,完成IoC容器可用的最後一道工序。

12) 查詢當前ApplicationContext中是否註冊有CommandLineRunner,如果有,則遍歷執行它們。

13) 正常情況下,遍歷執行SpringApplicationRunListener的finished()方法、(如果整個過程出現異常,則依然呼叫所有SpringApplicationRunListener的finished()方法,只不過這種情況下會將異常資訊一併傳入處理)****