在之前我们已经对spring的生命周期、AOP、事物等源码有了大体的分析,其中在对源码分析中大家有看到过refresh方法,Spring容器创建之后,会调用它的refresh方法,refresh的时候会做很多事情:比如完成配置类的解析、各种BeanFactoryPostProcessor和BeanPostProcessor的注册、国际化配置的初始化、web内置容器的构造等等。
接下来我们以ClassPathXmlApplicationContext这容器入手看整个refresh方法到底做了哪些事情。以及分析Spring有哪些一些拓展点我们是可以进行拓展的。
二、refresh源码分析首先先看以下的这段代码:
public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("person.xml"); Person bean = ac.getBean(Person.class); Person bean2 = ac.getBean(Person.class); }
接下来我们就对这个ClassPathXmlApplicationContext的构造函数源码进行分析。
2.1 ClassPathXmlApplicationContext源码分析首先我们看到ClassPathXmlApplicationContext构造函数就为我们做了几件事情:
- 调用父类的构造函数完成一些资源的初始化;
- 解析配置文件的路径,例如:applicationContext-${user.name}.xml这种配置文件名称,并且保存到configLocations中,为下一步的解析bean定义做准备;
- 调用refresh方法;
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); // 2.解析配置文件的路径,例如:applicationContext-${user.name}.xml这种配置文件名称,并且保存到configLocations中,为下一步的解析bean定义做准备 setConfigLocations(configLocations); // 3.如果需要刷新容器 if (refresh) { // 执行刷新容器的 *** 作 refresh(); } }2.1.1父类构造方法-super(parent)源码分析
我们点击super(parent)一直往里面跟可以看到对应的构造方法实现:
// AbstractXmlApplicationContext public AbstractXmlApplicationContext(@Nullable ApplicationContext parent) { super(parent); } // AbstractRefreshableConfigApplicationContext public AbstractRefreshableConfigApplicationContext(@Nullable ApplicationContext parent) { super(parent); } // AbstractRefreshableApplicationContext public AbstractRefreshableApplicationContext(@Nullable ApplicationContext parent) { super(parent); } // AbstractApplicationContext public AbstractApplicationContext(@Nullable ApplicationContext parent) { this(); setParent(parent); } // AbstractApplicationContext public AbstractApplicationContext() { // 初始化一个默认的资源文件解析器对象,默认的类型为:PathMatchingResourcePatternResolver this.resourcePatternResolver = getResourcePatternResolver(); }
其实一直往里面跟,他调用父类的构造方法主要是完成一些调用父类构造函数完成一些资源的初始化,其中包括:初始化默认的资源文件解析器,默认为:PathMatchingResourcePatternResolver和初始化默认的类加载器。
2.2 setConfigLocations源码分析解析配置文件的路径,例如:applicationContext-${user.name}.xml这种配置文件名称,并且保存到configLocations中,为下一步的解析bean定义做准备;我们知道我们的配置文件路径可以是一些spel表达式,这一步主要是解析配置文件中的一些占位符;
public void setConfigLocations(@Nullable 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++) { // 解析给定的配置文件的路径,例如:spring-${user.name}.xml会被解析为spring-wangbin33.xml this.configLocations[i] = resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }
然后我们接着往下看resolvePath的源码,看他具体是怎么进行解析的。
protected String resolvePath(String path) { return getEnvironment().resolveRequiredPlaceholders(path); }
我们可以看到这进行解析之前他先调用getEnvironment去获取一个环境对象,我们去看他怎么获取一个环境对象;
@Override public ConfigurableEnvironment getEnvironment() { if (this.environment == null) { // StandardEnvironment this.environment = createEnvironment(); } return this.environment; } protected ConfigurableEnvironment createEnvironment() { return new StandardEnvironment(); }
看到这里我们就知道了我们的环境对象默认就是StandardEnvironment类型的,接下来我们看这个环境对象是做了哪些事情。
public class StandardEnvironment extends AbstractEnvironment { public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment"; public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties"; @Override protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast( new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast( new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); } public abstract class AbstractEnvironment implements ConfigurableEnvironment { public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore"; public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active"; public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default"; protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default"; protected final Log logger = LogFactory.getLog(getClass()); private final SetactiveProfiles = new linkedHashSet<>(); private final Set defaultProfiles = new linkedHashSet<>(getReservedDefaultProfiles()); private final MutablePropertySources propertySources = new MutablePropertySources(); private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources); public AbstractEnvironment() { customizePropertySources(this.propertySources); } /// ....忽略部门代码 }
我们看到StandardEnvironment没有默认的构造方法,然后我们具体看他父类的构造方法,父类的构造方法调用了customizePropertySources方法也就是StandardEnvironment中的customizePropertySources方法,我们可以看得到这个方法就是为我们去加载一些系统变量跟环境变量。到此我们继续分析resolveRequiredPlaceholders是如何解析配置文件名字的占位符。
@Override public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { // this.propertyResolver: PropertySourcesPropertyResolver // 根据传入的配置文件名称解析配置文件真正的路径,默认调用的是AbstractPropertyResolver抽象实现 return this.propertyResolver.resolveRequiredPlaceholders(text); } @Override public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { if (this.strictHelper == null) { // 创建PropertyPlaceholderHelper对象 this.strictHelper = createPlaceholderHelper(false); } // 执行解析占位符的 *** 作 return doResolvePlaceholders(text, this.strictHelper); } private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { return helper.replacePlaceholders(text, this::getPropertyAsRawString); } public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { Assert.notNull(value, "'value' must not be null"); return parseStringValue(value, placeholderResolver, null); } protected String parseStringValue( String value, PlaceholderResolver placeholderResolver, @Nullable SetvisitedPlaceholders) { // 如果没有占位符,直接返回.例如:$Value("wangbin33") 这种类型.直接返回wangbin33 int startIndex = value.indexOf(this.placeholderPrefix); if (startIndex == -1) { return value; } // 解析占位符对应的key: 如${user.name}, ${file-${name}} StringBuilder result = new StringBuilder(value); while (startIndex != -1) { int endIndex = findPlaceholderEndIndex(result, startIndex); if (endIndex != -1) { String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); String originalPlaceholder = placeholder; if (visitedPlaceholders == null) { visitedPlaceholders = new HashSet<>(4); } if (!visitedPlaceholders.add(originalPlaceholder)) { throw new IllegalArgumentException( "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); } // Recursive invocation, parsing placeholders contained in the placeholder key. 解析出占位符中的key placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // Now obtain the value for the fully resolved key... String propVal = placeholderResolver.resolvePlaceholder(placeholder); if (propVal == null && this.valueSeparator != null) { int separatorIndex = placeholder.indexOf(this.valueSeparator); if (separatorIndex != -1) { String actualPlaceholder = placeholder.substring(0, separatorIndex); String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); 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()); } 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 value "" + value + """); } visitedPlaceholders.remove(originalPlaceholder); } else { startIndex = -1; } } return result.toString(); }
我们可以看到在真正解析配置文件的代码中去递归调用解析方法,递归调用的原因是可能会出现类似于:spring-${user.name.${evn}}.xml这样的嵌套占位符.
好了,对于ClassPathXmlApplicationContext的构造方法的前两步我们就先分析到这里,在接下来的篇章中我们就可以分析refresh方法。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)