一、直接写个测试例子
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; } }
总结