這兩篇 主要是在整合過程中 對相關知識的學習
1、瞭解springApplication
非spring boot 使用Spring cloud config (1) 瞭解springApplication
spring ApplicationContext 自定義
ApplicationContext是“事實上”的容器標準,它基於BeanFactory並對其做了一些功能上的擴充套件。例如:
通過MessageResource支援國際化
提供了容器內部的訊息釋出機制
自動新增BeanFactoryPostProcessor、BeanPostProcessor到容器中
複製程式碼
作用:
獲取xml 更改
生成bean 更改
![spring 整合 spring cloud config 的相關知識](https://i.iter01.com/images/4e9548a5fa52857a838db2db91fce25bfc4908b6924c4d27cc6c5e201cdbbbf4.jpg)
擴充套件點:
圖中表示出了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