記事ディレクトリ
この記事の概要
以上、プロジェクトの初期化プロジェクトの基本的な状況を紹介しましたが、この記事では ClassPathXmlApplicationContext の機能、初期化、パス文字列の解析について詳しく紹介します。
使用例
これは、通常のパスをパスのようなリソース名として解釈し、そこからコンテキスト定義ファイルを取得する別のアプリケーション コンテキストです。
下の図に示すように、xml ファイルで Bean を定義し、getBean() メソッドでオブジェクトを取得できます。
ディレクトリ構造:
xml ファイル:
1. spring-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="spring-config-test.xml"></import>
<bean id="user" class="com.learn.ioc.model.User">
<property name="name" value="小孙"/>
<property name="age" value="21"/>
</bean>
</beans>
2.spring-config-test.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user-test" class="com.learn.ioc.model.User" >
<property name="name" value="老王"/>
<property name="age" value="22"/>
</bean>
<alias name="user-test" alias="user-test-plus"/>
</beans>
呼び出し方法:
package com.learn.ioc.app;
import com.learn.ioc.model.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestIocApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
User user = applicationContext.getBean("user",User.class);
System.out.println(user.toString());
User userTest = applicationContext.getBean("user-test",User.class);
System.out.println(userTest.toString());
}
}
通話結果
主な機能
青の実線は継承を表し、緑の実線はインターフェイスの実装を表します。ClassPathXmlApplicationContext は、主に次のインターフェースを実装します。
ライフサイクル インターフェース: ライフサイクル制御メソッドの開始/停止を定義するパブリック インターフェースです。典型的なユース ケースは、非同期処理の制御です。
AutoCloseable インターフェイス: リソース (ファイル ハンドルやソケット ハンドルなど) を閉じるまで保持できるオブジェクト。リソース仕様ヘッダー ファイルで宣言されたオブジェクトの close() メソッドは、try-with-resourcesブロックを終了するときに自動的に呼び出されます。この構造により、タイムリーなリリースが保証され、リソースが枯渇する可能性のある例外やエラーが回避されます。
BeanFactory インターフェース: これは、Spring Bean コンテナーを実装するルート インターフェースであり、アプリケーション コンポーネントの中央レジストリであり、アプリケーション コンポーネントの構成を一元化します。
MessageSource インターフェイス: 戦略モードを使用し、メッセージを解析するためのインターフェイスを定義し、そのようなメッセージのパラメーター化と国際化をサポートします。
ApplicationEventPublisher インターフェース: アプリケーション イベント発行。
EnvironmentCapable インターフェイス: コンポーネントに Environment インターフェイスが含まれ、公開されていることを示します。
ResourceLoader インターフェイス: 戦略モードを使用して、リソースをロードするためのインターフェイスを定義します。
Aware インターフェース: マーカー インターフェース。この Bean は、フレームワーク オブジェクトの Spring コンテナーからコールバック メソッドを介して通知できることを示します。サブインターフェース BeanNameAware は、BeanFactory で独自の BeanName を知りたい Bean によって実装されます。
ソースコードの読み取り
まず、コンストラクターとは何かを理解します。
*/
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
@Nullable
private Resource[] configResources;
public ClassPathXmlApplicationContext() {
}
public ClassPathXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
/*
* 从XML文件中加载信息,创建新的上下文。
*/
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {
configLocation}, true, null);
}
/*
* 从多个XML文件中加载信息,创建新的上下文。
*/
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
/**
* 创建指定parent的上下文。
*/
public ClassPathXmlApplicationContext(String[] configLocations, @Nullable ApplicationContext parent)
throws BeansException {
this(configLocations, true, parent);
}
/**
* 从xml文件中加载上下文,refresh决定是否自动刷新上下文。
*/
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
/**
* 略
*/
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
/**
* 从xml文件中加载配置,自动刷新。加载制定类的相对路径下的资源。
*/
public ClassPathXmlApplicationContext(String path, Class<?> clazz) throws BeansException {
this(new String[] {
path}, clazz);
}
/**
* 同上,文件有多个
*/
public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz) throws BeansException {
this(paths, clazz, null);
}
/**
* 同上,多一个parent
*/
public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
Assert.notNull(paths, "Path array must not be null");
Assert.notNull(clazz, "Class argument must not be null");
this.configResources = new Resource[paths.length];
for (int i = 0; i < paths.length; i++) {
this.configResources[i] = new ClassPathResource(paths[i], clazz);
}
refresh();
}
@Override
@Nullable
protected Resource[] getConfigResources() {
return this.configResources;
}
}
その核心は次の段落であり、以下もその周りに展開します。
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
スーパー(親)
既存の ApplicationContext を介してコンテキストを作成し、コードにルートし、super(parent) がこの関数を呼び出します
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
これ()
this 関数は、新しいアプリケーション コンテキストを作成する次のとおりです。
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
setParent(親)
割り当て、親が空でない場合、活动配置文件、默认配置文件和属性源
親環境を子環境に追加します。
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
@Override
public void merge(ConfigurableEnvironment parent) {
for (PropertySource<?> ps : parent.getPropertySources()) {
if (!this.propertySources.contains(ps.getName())) {
// 属性源
this.propertySources.addLast(ps);
}
}
String[] parentActiveProfiles = parent.getActiveProfiles();
if (!ObjectUtils.isEmpty(parentActiveProfiles)) {
// 活动配置文件
synchronized (this.activeProfiles) {
Collections.addAll(this.activeProfiles, parentActiveProfiles);
}
}
String[] parentDefaultProfiles = parent.getDefaultProfiles();
if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {
// 默认配置文件
synchronized (this.defaultProfiles) {
this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);
Collections.addAll(this.defaultProfiles, parentDefaultProfiles);
}
}
}
setConfigLocations
アプリケーション コンテキストの構成の場所を設定します。
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++) {
//逐一解析路径,必要时用相应的环境属性值替换占位符,并且配置config locations
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
ここで getEnironment() には、環境変数の作成に関連する操作が含まれます。
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
getEnvironment()
環境変数を取得する
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
次に、環境変数を作成し、StandardEnvironment クラスを作成するためのコードを確認できます。
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
次に、次のメソッドが呼び出され、JVM システム プロパティ ソース名とシステム プロパティ ソース名がリソース リストに追加されます。
@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()));
}
resolveRequiredPlaceholders(パス)
このメソッドの機能は、${…} プレースホルダーを特定の値に置き換えることです。
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
それから
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
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 Set<String> visitedPlaceholders) {
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
StringBuilder result = new StringBuilder(value);
while (startIndex != -1) {
//findPlaceholderEndIndex方法是找最后一个结束符(“}”)
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
//new String placeholder = 第一个placeholderPrefix"${"和最后一个placeholderSuffix"}"
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.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// 递归调用,通过propertySource.getProperty(key)获取这个占位符的真实值,否则返回null
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
//形如 A:a,如果用“A:a”没从propertySource中获取到值,就拿A再去查一次
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;
}
}
}
//如果从propertySource中获取到值,就直接替换到"${}"
if (propVal != null) {
// 递归调用,解析先前解析的占位符值中包含的占位符
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) {
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();
}
この時点で、setConfigLocations フェーズは終了し、configLocations 配列には解析されたリソース パスが格納されています。
下
ClassPathXmlApplicationContext ソース コード (2) - BeanFactory の初期化、ロード、登録プロセス