spring 整合 spring cloud config 的相關知識

美式不加糖_發表於2017-12-14

這兩篇 主要是在整合過程中 對相關知識的學習

1、瞭解springApplication

非spring boot 使用Spring cloud config (1) 瞭解springApplication

spring ApplicationContext 自定義

ApplicationContext是“事實上”的容器標準,它基於BeanFactory並對其做了一些功能上的擴充套件。例如:

通過MessageResource支援國際化
提供了容器內部的訊息釋出機制
自動新增BeanFactoryPostProcessor、BeanPostProcessor到容器中

複製程式碼

作用:

獲取xml 更改

生成bean 更改

spring 整合 spring cloud config 的相關知識

擴充套件點:

圖中表示出了Spring容器中設計到的很多擴充套件點,主要可以分為以下幾類:

    BeanFactoryPostProcessor
    各種Aware
    BeanPostProcessor
    隱藏的一些特殊功能
複製程式碼

BeanFactoryPostProcessor

解析成BeanDefinition後,例項化之前。從名字可以看出來,BeanFactoryPostProcessor針對的應該是容器級別的擴充套件,名為“BeanFactory  PostProcessor”即對容器中所有的BeanDefinition都起普遍作用。BeanFactoryPostProcessor有幾個我們比較常用的子類PropertyPlaceholderConfigurer、CustomEditorConfigurer,前者用於配置檔案中的${var}變數替換,後者用於自定義編輯BeanDefinition中的屬性值,合理利用CustomEditorConfigurer會有一些意想不到的效果


複製程式碼
ApplicationContext在初始化過程中會呼叫invokeBeanFactoryPostProcessors(beanFactory),該函式會找出所有BeanFactoryPostProcessor型別的bean,呼叫postProcessBeanFactory方法。

複製程式碼

BeanPostProcessor

對於Bean這一級別,關注的主要是Bean例項化後,初始化前後的

BeanPostProcessor在BeanFactory的初始化bean的函式initializeBean中,主要程式碼為,基本就是取出所有的BeanPostProcessor,然後遍歷呼叫其postProcessBeforeInitialization或者postProcessAfterInitialization方法。

複製程式碼

參考:http://www.jianshu.com/p/2692bf784976

初始化:

ContextLoaderListener 的作用

該類可以作為Listener使用,在啟動Tomcat容器的時候,該類的作用就是自動裝載ApplicationContext的配置資訊

ContextLoaderListener會讀取這些XML檔案併產生 WebApplicationContext物件,然後將這個物件放置在ServletContext的屬性裡,這樣我們只要可以得到Servlet就可 以得到WebApplicationContext物件,並利用這個物件訪問spring 容器管理的bean。
複製程式碼

參考:http://blog.csdn.net/zjw10wei321/article/details/40145241


    	@Override
    	public void contextInitialized(ServletContextEvent event) {
    		initWebApplicationContext(event.getServletContext());
    	}

複製程式碼

ContextLoader中,會根據servlet 上下文,建立WebApplicationContext,也會列印log

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
複製程式碼

根據提供的servlet上下文去初始化Spring的web應用上下文,在構造時使用當前應用上下文或者在web.xml中配置引數contextClass和contextConfigLocation去建立新的上下文。

(1)先確定contextClass 讀配置引數,需要時ConfigurableWebApplicationContext的子類,如果不是丟擲異常,然後把contextClass 強制轉換為ConfigurableWebApplicationContext。

	protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        //這裡需要確定我們載入的根WebApplication的型別,  
        //由在web.xml中配置的contextClass中配置的引數, 如果沒有使用預設的 WebApplicationContext。   
Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

複製程式碼

獲取根據servlet上下文,獲取ContextClass,即 配置的Contextcalss 或者預設的(XmlWebApplicationContext)。必須是ConfigurableWebApplicationContext的實現

	/**
	 * Config param for the root WebApplicationContext implementation class to use: {@value}
	 * @see #determineContextClass(ServletContext)
	 */
	public static final String CONTEXT_CLASS_PARAM = "contextClass";
複製程式碼
	 * @return the WebApplicationContext implementation class to use
	 * @see #CONTEXT_CLASS_PARAM
	 * @see org.springframework.web.context.support.XmlWebApplicationContext
	 */
	protected Class<?> determineContextClass(ServletContext servletContext) {
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		if (contextClassName != null) {
			try {
				return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}
		}
		//預設
		else {
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			try {
				return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load default context class [" + contextClassName + "]", ex);
			}
		}
	}
複製程式碼

(2)讀loadParentContext

( 	web.xml配置的locatorFactorySelector和parentContextKey,設定父上下文 
BeanFactoryLocator locator )
複製程式碼

然後獲取parent Context,載入父上下文的主要原因 沒看懂。。。不過web應用,一般沒有,不用擔心

如何獲取ApplicationContext

1.WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();

當前應用的WebApplicationContext就儲存在 ContextLoader的currentContextPerThread屬性當中

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}
複製程式碼


2.基於ServletContext上下文獲取的方式

ServletContext sc = request.getSession().getServletContext();

ApplicationContext ac1 = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);

ApplicationContext ac2 = WebApplicationContextUtils.getWebApplicationContext(sc);

WebApplicationContext wac1 = (WebApplicationContext) sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);


3.還有一些更合適的,基於Spring提供的抽象類或者介面,在初始化Bean時注入ApplicationContext

3.1:繼承自抽象類ApplicationObjectSupport

說明:抽象類ApplicationObjectSupport提供getApplicationContext()方法,可以方便的獲取到ApplicationContext。

Spring初始化時,會通過該抽象類的setApplicationContext(ApplicationContext context)方法將ApplicationContext 物件注入。


3.2:繼承自抽象類WebApplicationObjectSupport

說明:類似上面方法,呼叫getWebApplicationContext()獲取WebApplicationContext


3.3:實現介面ApplicationContextAware

說明:實現該介面的setApplicationContext(ApplicationContext context)方法,並儲存ApplicationContext 物件。


總結:Context結構複雜,parentContext結構的作用,及如何的去載入bean工廠的邏輯原理。
複製程式碼

如果建立的是,ConfigurableWebApplicationContext, 會讀loadParentContext


	protected ApplicationContext loadParentContext(ServletContext servletContext) {
		ApplicationContext parentContext = null;
		String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
		String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);

複製程式碼

2、瞭解environment

PropertySource:屬性源,key-value屬性對抽象,比如用於配置資料

PropertyResolver:屬性解析器,用於解析相應key的value

Environment:環境,本身是一個PropertyResolver,但是提供了Profile特性,即可以根據環境得到相應資料(即啟用不同的Profile,可以得到不同的屬性資料,比如用於多環境場景的配置(正式機、測試機、開發機DataSource配置))


複製程式碼

2.Environment

   Environment介面是Spring對當前程式執行期間的環境的封裝(spring)。主要提供了兩大功能:profile和property(頂級介面PropertyResolver提供)。目前主要有StandardEnvironment、
   開發環境,比如JDK環境,系統環境;每個環境都有自己的配置資料,如System.getProperties()可以拿到JDK環境資料、System.getenv()可以拿到系統變數,ServletContext.getInitParameter()可以拿到Servlet環境配置資料。
Spring抽象了一個Environment來表示Spring應用程式環境配置,它整合了各種各樣的外部環境,並且提供統一訪問的方法。
複製程式碼
public interface Environment extends PropertyResolver {  
        //得到當前明確啟用的剖面  
    String[] getActiveProfiles();  

        //得到預設啟用的剖面,而不是明確設定啟用的  
    String[] getDefaultProfiles();  

        //是否接受某些剖面  
    boolean acceptsProfiles(String... profiles);  

}  
複製程式碼

http://img.blog.csdn.net/20160531142913985

StandardServletEnvironment和MockEnvironment3種實現,分別代表普通程式、Web程式以及測試程式的環境。通過上述的getOrCreateEnvironment方法處理邏輯也是可以總結出來的。
複製程式碼

會讀取配置檔案如servletConfigInitParams,servletContextInitParams,jdni,系統配置等

StubPropertySource

臨時作為一個PropertySource的佔位,後期會被真實的PropertySource取代。

2.環境的裝載 
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {

	@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		//servletConfigInitParams
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		//servletContextInitParams
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		super.customizePropertySources(propertySources);
	}
複製程式碼

配置檔案新增:

MutablePropertySources類中有一個list (PropertySource)如下,配置資訊 (PropertySource)add 到這個list當中

public class MutablePropertySources implements PropertySources {
    private final Log logger;
    private final List<PropertySource<?>> propertySourceList;
複製程式碼
 addFirst:在propertySourceList 頭部新增元素。

addLast:在propertySourceList 尾部新增元素。

addAtIndex:在propertySourceList 指定的位置新增元素。 
複製程式碼

配置資訊類:PropertySource 一個 name 和T source,即 key 和源頭

public abstract class PropertySource<T> {protected final   

    String name;//屬性源名稱
    protected final T source;//屬性源(比如來自Map,那就是一個Map物件)
    public String getName();  //獲取屬性源的名字  
    public T getSource();        //獲取屬性源  
    public boolean containsProperty(String name);  //是否包含某個屬性  
    public abstract Object getProperty(String name);   //得到屬性名對應的屬性值   
} 
複製程式碼
 RandomValuePropertySource:source是random。

ServletConfigPropertySource:source是ServletConfig。

ServletContextPropertySource:source是ServletContext。

JndiPropertySource:source是JndiLocatorDelegate。

StubPropertySource:source是Object。

MapPropertySource:source是Map<String, Object>。 
複製程式碼

配置資訊類 PropertySources

包含多個PropertySource,繼承了Iterable介面,所以它的子類還具有迭代的能力。

實現類 MutablePropertySources

它包含了一個CopyOnWriteArrayList集合,用來包含多個PropertySource
複製程式碼

profile :切面

profile 配置是一個被命名的,bean定義的邏輯組,這些bean只有在給定的profile配置啟用時才會註冊到容器。不管是XML還是註解,Beans都有可能指派給profile配置。Environment環境物件的作用,對於profiles配置來說,它能決定當前啟用的是哪個profile配置,和哪個profile是預設。就需要根據不同的環境選擇不同的配置;

profile有兩種: 預設的:通過環境中“spring.profiles.default”屬性獲取,如果沒有配置預設值是“default” 明確啟用的:通過環境中“spring.profiles.active”獲取 查詢順序是:先進性明確啟用的匹配,如果沒有指定明確啟用的(即集合為空)就找預設的;配置屬性值從Environment讀取。

@Profile()的使用
可以使用在類或方法上,表示這個bean或方法屬於哪個剖面 示例:

@Configuration
public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        System.setProperty("spring.profiles.active","dev");
        ApplicationContext context = new AnnotationConfigApplicationContext(Test.class);
        System.out.println(Arrays.asList(context.getBeanNamesForType(String.class)));
    }

    @Bean()
    @Profile("test")
    public String str1() {
        return "str1";
    }

    @Bean
    @Profile("dev")
    public String str2() {
        return "str2";
    }

    @Bean
    public String str3() {
        return "str3";
    }
}
複製程式碼

profile:http://www.jianshu.com/p/49e950b0b008

http://blog.csdn.net/u011179993/article/details/51511364

@propertySource

Java Config方式的註解,其屬性會自動註冊到相應的Environment

@Configuration  
@PropertySource(value = "classpath:resources.properties", ignoreResourceNotFound = false)  
public class AppConfig {  
} 
複製程式碼

綜上說明:

先新增servletConfigInitParams,然後新增servletContextInitParams,其次判斷是否是jndi環境,如果是則新增jndiProperties,最後呼叫父類的customizePropertySources(propertySources)。 
複製程式碼

PropertySourceLocator

PlaceHolder 是什麼

property的屬性${canal.instance.mysql.slaveId:1234} 取配置檔案key的時候帶了:後面跟了一個預設值

參考“:

http://www.jianshu.com/p/df57fefe0ab7

https://www.cnblogs.com/dragonfei/archive/2016/10/09/5906474.html

https://github.com/Eric-ly/spring-mvc-with-spring-cloud-config-client-without-springboot

http://www.jianshu.com/p/df57fefe0ab7

https://www.cnblogs.com/dragonfei/archive/2016/10/09/5906474.html

https://github.com/Eric-ly/spring-mvc-with-spring-cloud-config-client-without-springboot

相關文章