一、直接寫個測試例子
package com.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.test.controller.User; public class UserTest { @Test public void test() { @SuppressWarnings("resource") ApplicationContext beanFactory = new ClassPathXmlApplicationContext("classpath:springContext.xml"); User user = beanFactory.getBean("user", User.class); String username = user.getName(); System.out.println(username); } }
二、直接debug運行
在進入代碼之前,先了解一下這個ClassPathXmlApplicationContext類的繼承關系
1、首先進入
//這里的configLocation的值就是classpath:springContext.xml public ClassPathXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); }
2、繼續進入ClassPathXmlApplicationContext的構造器
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent);//parent為null值 setConfigLocations(configLocations); if (refresh) { refresh(); } }
3、進入setConfigLocation這個方法定義於父類AbstractRefreshableConfigApplicationContext
public void setConfigLocations(String... locations) { if (locations != null) { Assert.noNullElements(locations, "Config locations must not be null"); //new一個String數組,用來裝解析后的配置文件地址 this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { this.configLocations[i] = resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }
4、繼續進入resolvePath方法,它這里創建了一個StandEnvironment實例,這個實例包含着一些系統參數,環境變量參數,不過它現在還沒有做任何事情,僅僅是創建它的一個實例。后面用到再說。
protected String resolvePath(String path) { return getEnvironment().resolveRequiredPlaceholders(path); }
5.進入getEnvironment方法,這個方法定義於父類AbstractApplicationContext。
@Override public ConfigurableEnvironment getEnvironment() { if (this.environment == null) { this.environment = createEnvironment(); } return this.environment; }
6、接下來調用StandEnvironment的resolveRequiredPlaceholders(path)方法,先看看StrandEnvironment結構。
這里的resolveRequiredPlaceholders方法定義在父類AbstractEnvironment中,這個方法的代碼如下:
//注意這個類的實例被注入到了PropertySourcesPropertyResolver中了,等下會用到 private final MutablePropertySources propertySources = new MutablePropertySources(this.logger); private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources); public AbstractEnvironment() { customizePropertySources(this.propertySources); if (this.logger.isDebugEnabled()) { this.logger.debug(format( "Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources)); } } @Override//這個方法是實現於StandardEnvironment類中,我把它放到了一起 protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); //這個getSystemProperties調用的是System.getProperties() propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));//獲得系統變量 } @Override public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { // 這個propertyResolver在new出StandardEnvironment的時候就被創建了 return this.propertyResolver.resolveRequiredPlaceholders(text); }
7、進入PropertySourcesPropertyResolver 類的resolveRequiredPlaceholders方法,這個方法存在於PropertySourcesPropertyResolver的父類AbstractPropertyResolver里面
@Override public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { if (this.strictHelper == null) { //這里創建了一個占位符助手實例,這個false參數表示忽略不能解析的占位符 this.strictHelper = createPlaceholderHelper(false); } //這里才是真正解析的開始 return doResolvePlaceholders(text, this.strictHelper); }
8、這個PlaceholderHelper在創建時有一段靜態塊的初始化
private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4); static { wellKnownSimplePrefixes.put("}", "{"); wellKnownSimplePrefixes.put("]", "["); wellKnownSimplePrefixes.put(")", "("); }
9、進入doResolvePlaceholders方法
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { //調用了屬性占位符助手的替換占位符的方法 return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() { @Override public String resolvePlaceholder(String placeholderName) { return getPropertyAsRawString(placeholderName); } }); }
10、進到PropertyPlaceholderHelper類的replacePlaceholders方法,strVal就是我們從前面傳進來的配置文件的名稱:classpath:springContext.xml,placeholderResolver可以通過占位符找到對應的值,怎么找的,后面再說。這段代碼非常有用,也許有一天會對你有用。
protected String parseStringValue( String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { StringBuilder result = new StringBuilder(strVal); //檢查這個字符串時候有 ${ 前綴 int startIndex = strVal.indexOf(this.placeholderPrefix); while (startIndex != -1) { //如果有 ${ 前綴,再檢查是否有 } 后綴 int endIndex = findPlaceholderEndIndex(result, startIndex); if (endIndex != -1) { //拿到占位符,如classpath:spring${key}.xml,這個占位符是key String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); String originalPlaceholder = placeholder; //將當前的占位符存到set集合中,如果set集合有了,就會添加失敗 //就會報錯,循環引用錯誤,比如${a},這個a的值依然是${a} //這樣就陷入了無限解析了,根本停不下來 if (!visitedPlaceholders.add(originalPlaceholder)) { throw new IllegalArgumentException( "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); } // Recursive invocation, parsing placeholders contained in the placeholder key. //對占位符進行解析,如:${${a}},所以要繼續解析 placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // Now obtain the value for the fully resolved key... //調用這個解析器查找占位符對應的值,這個方法的代碼在下面11步給出 String propVal = placeholderResolver.resolvePlaceholder(placeholder); if (propVal == null && this.valueSeparator != null) { //如果為null,那么查找這個propVal是否為:分割的字符串 int separatorIndex = placeholder.indexOf(this.valueSeparator); if (separatorIndex != -1) { //如果propVal為key:Context,那么這個值應為key String actualPlaceholder = placeholder.substring(0, separatorIndex); //如果propVal為key:Context,那么就是Context String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); //跟上面的一樣去系統屬性中查找 propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); //如果為空,那么就設置為defaultValue,如key:Context //defaultValue = Context; if (propVal == null) { propVal = defaultValue; } } } if (propVal != null) { // Recursive invocation, parsing placeholders contained in the // previously resolved placeholder value. //這個值可能也有站位符,繼續遞歸解析 propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); //得到了占位符對應的值后替換掉占位符 result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); if (logger.isTraceEnabled()) { logger.trace("Resolved placeholder '" + placeholder + "'"); } //繼續查找是否還有后續的占位符 startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); } //如果propValue為null,那么就說明這個占位符沒有值,如果設置為忽略 //不能解析的占位符,那么繼續后續的占位符,否則報錯 else if (this.ignoreUnresolvablePlaceholders) { // Proceed with unprocessed value. startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); } else { throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'" + " in string value \"" + strVal + "\""); } //解析成功就刪除set集合中對應的占位符 visitedPlaceholders.remove(originalPlaceholder); } else { startIndex = -1; } } return result.toString(); }
11、resolvePlaceholder方法調用了getPropertyAsRawString方法,這個方法又調用了PropertySourcesPropertyResolver類的getProperty方法
//參數說明,key是傳進來的占位符,targetValueType指的是目標類型,這里肯定是String.class, resolveNestedPlaceholders表示是否要對嵌套的占位符進行解析,這里傳的是false,不需要。 protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) { boolean debugEnabled = logger.isDebugEnabled(); if (logger.isTraceEnabled()) { logger.trace(String.format("getProperty(\"%s\", %s)", key, targetValueType.getSimpleName())); } //這個類就是在AstractEnvironment中傳進來的MutablePropertySource 實例,上面第6點已經說它是怎么進來的,它存有系統屬性和系統環境變量 if (this.propertySources != null) { for (PropertySource<?> propertySource : this.propertySources) { if (debugEnabled) { logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName())); } //從系統屬性中尋找值 Object value = propertySource.getProperty(key); if (value != null) { Class<?> valueType = value.getClass(); //如果允許解析嵌入的${},並且是String類型的就繼續解析 if (resolveNestedPlaceholders && value instanceof String) { value = resolveNestedPlaceholders((String) value); } if (debugEnabled) { logger.debug(String.format("Found key '%s' in [%s] with type [%s] and value '%s'", key, propertySource.getName(), valueType.getSimpleName(), value)); } //判斷當前值得類型能夠轉換成目標類型,如果不能,直接報錯 if (!this.conversionService.canConvert(valueType, targetValueType)) { throw new IllegalArgumentException(String.format( "Cannot convert value [%s] from source type [%s] to target type [%s]", value, valueType.getSimpleName(), targetValueType.getSimpleName())); } //如果可以轉換直接轉換返回這個值 return this.conversionService.convert(value, targetValueType); } } } if (debugEnabled) { logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key)); } //如果在系統屬性中沒有得到值,那么返回null值。 return null; }
12、最后返回到AbstractRefreshableConfigApplicationContext類,將解析后的配置文件路徑設置到configLocations屬性中
public void setConfigLocations(String... locations) { if (locations != null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { this.configLocations[i] = resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }
總結