Spring加载bean配置文件的schemas文件(懒加载)
最近在调试Spring源码时,看到加载Spring的xml文件-schema部分,
里面使用了懒加载的写法(PluggableSchemaResolver的getSchemaMappings方法)
为了能更好理解这个懒加载(里面获取到的Map
下面是类以及单元测试
import java.util.HashMap;
import java.util.Map;
/**
* Created by shucheng on 2019-5-31 上午 10:24
*/
public class MySchema {
public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
private final ClassLoader classLoader;
private String schemaMappingsLocation;
private Map<String, String> schemaMappings;
public MySchema(ClassLoader classLoader) {
this.classLoader = classLoader;
this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
}
public Map<String, String> getSchemaMappings() {
Map<String, String> schemaMappings = this.schemaMappings;
// 如果schemaMappings不为null,则直接返回数据,不需要重新创建对象
if (schemaMappings == null) {
// System.out.println("线程" + Thread.currentThread().getName() + "进入if判断");
synchronized (this) {
// System.out.println("线程" + Thread.currentThread().getName() + "进入同步锁");
// System.out.println("线程" + Thread.currentThread().getName() + "\n" +
"schemaMappings=====" + (schemaMappings != null ? schemaMappings.hashCode() : null) + "\n" +
"this.schemaMappings=====" + (this.schemaMappings != null ? this.schemaMappings.hashCode() : null));
schemaMappings = this.schemaMappings;
if (schemaMappings == null) {
System.out.println("开始加载schemaMappingsLocation");
Map<String, String> map = new HashMap();
map.put("1", "张三");
map.put("2", "李四");
map.put("3", "王五");
map.put("4", "赵六");
map.put("5", "钱七");
schemaMappings = new HashMap<>();
schemaMappings.putAll(map);
System.out.println("schemaMappingsLocation加载中");
this.schemaMappings = schemaMappings;
System.out.println("schemaMappingsLocation加载完成");
}
}
}
// System.out.println("线程" + Thread.currentThread().getName() + "======class为" + schemaMappings.getClass().hashCode() + "=====" + schemaMappings);
return schemaMappings;
}
@Override
public String toString() {
return "MySchema{" +
"schemaMappings=" + getSchemaMappings() +
'}';
}
}
import org.junit.Test;
import java.util.Map;
/**
* Created by shucheng on 2019-5-31 上午 10:23
*/
public class MyTest {
// 简单测试
@Test
public void test() {
MySchema m = new MySchema(getClass().getClassLoader());
System.out.println(m);
// System.out.println(m.toString());
}
// 模拟多线程(3个线程)测试
// 参考链接:https://www.jb51.net/article/130924.htm
// 另外还参考了MyBatis3.5.1源码里的单元测试org.apache.ibatis.io.VFSTest#getInstanceShouldNotBeNullInMultiThreadEnv
@Test
public void testMultiThread() {
final int threadCount = 20;
Thread[] threads = new Thread[threadCount];
InstanceGetterProcedure procedure = new InstanceGetterProcedure(new MySchema(getClass().getClassLoader()));
for (int i = 0; i < threads.length; i++) {
String threadName = "Thread##" + i;
threads[i] = new Thread(procedure, threadName);
}
for (Thread thread : threads) {
thread.start();
}
}
private class InstanceGetterProcedure implements Runnable {
private MySchema mySchema;
Map<String, String> schemaMappings;
public InstanceGetterProcedure() {
}
public InstanceGetterProcedure(MySchema mySchema) {
this.mySchema = mySchema;
}
@Override
public void run() {
// 获取schemaMappings
schemaMappings = mySchema.getSchemaMappings();
}
}
}
这里我重点说下多线程的情况:
这个属性被赋过两次值,这点我开始很疑惑,一直想能否把第33行去掉。答案当然是不能去掉(因为去掉的话,多线程情况下有可能返回的不是单例) 根源就是getSchemaMappings方法加锁只是对部分代码加锁,第23行就没有加锁,完全有可能出现多个线程进来后,每个线程的schemaMappings属性都为null
这一点可以通过调用MyTest的testMultiThread单元测试方法来证实,多调用几次就会碰到我所说的情况,下图是我碰到的一次
去看这个问题的起因:
我之所以会注意到这点,是因为我在用idea调试PluggableSchemaResolver中★★★位置(第93行)时,看到schemaMappings的值为null;但是当这个位置向下再执行一行代码后,鼠标放到schemaMappings上发现已经有结果了。
这种现象让我感到很奇怪,因为这行代码并没有直接操作schemaMappings属性,本身这个类也没有被代理,只是一个普通方法。
然后我在这个类当中继续搜索schemaMappings,发现有一个getSchemaMappings方法(里面直接操作了schemaMappings属性),而这个方法被两个地方(toString和resolveEntity,后面这个和我看到的现象无关,和实际解析xml时先加载schemas有关)调用了。这时我猜想debug时,idea会监控类以及其中变量,我想到现象是idea监控类的时候调用了toString方法(因为要在Variables显示类的有关信息)
如果我直接运行单元测试,去掉所有断点,发现在★★★位置后初始化完schemaMappingsLocation变量后,直接在加上System.out.println(“==========================” + schemaMappings);来运行,发现打印出来的是空的
这就证实了我前面猜想的debug过程中idea会调用监控类的toString是对的,所以我一开始看到的现象也就解释得通了
附PuggableSchemaResolver的部分源码:
public class PluggableSchemaResolver implements EntityResolver {
/**
* The location of the file that defines schema mappings.
* Can be present in multiple JAR files.
*
* 默认 {@link #schemaMappingsLocation} 地址
*/
public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
private static final Log logger = LogFactory.getLog(PluggableSchemaResolver.class);
@Nullable
private final ClassLoader classLoader;
/**
* Schema文件地址
*/
private final String schemaMappingsLocation;
/** Stores the mapping of schema URL -> local schema path. */
@Nullable
private volatile Map<String, String> schemaMappings; // namespaceURI与Schema文件地址的映射集合
/**
* Loads the schema URL -> schema file location mappings using the default
* mapping file pattern "META-INF/spring.schemas".
* @param classLoader the ClassLoader to use for loading
* (can be {@code null}) to use the default ClassLoader)
* @see PropertiesLoaderUtils#loadAllProperties(String, ClassLoader)
*/
public PluggableSchemaResolver(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
// ★★★
this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
// System.out.println("==========================" + schemaMappings);
}
/**
* Loads the schema URL -> schema file location mappings using the given
* mapping file pattern.
* @param classLoader the ClassLoader to use for loading
* (can be {@code null}) to use the default ClassLoader)
* @param schemaMappingsLocation the location of the file that defines schema mappings
* (must not be empty)
* @see PropertiesLoaderUtils#loadAllProperties(String, ClassLoader)
*/
public PluggableSchemaResolver(@Nullable ClassLoader classLoader, String schemaMappingsLocation) {
// 暂时用不到,这里省略具体内容
}
@Override
@Nullable
public InputSource resolveEntity(@Nullable String publicId, @Nullable String systemId) throws IOException {
// 暂时用不到,这里省略具体内容
}
/**
* Load the specified schema mappings lazily.
*/
private Map<String, String> getSchemaMappings() {
Map<String, String> schemaMappings = this.schemaMappings;
if (schemaMappings == null) {
synchronized (this) {
schemaMappings = this.schemaMappings;
if (schemaMappings == null) {
// System.out.println("开始加载schemaMappingsLocation");
if (logger.isTraceEnabled()) {
logger.trace("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
}
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
if (logger.isTraceEnabled()) {
logger.trace("Loaded schema mappings: " + mappings);
}
schemaMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
this.schemaMappings = schemaMappings;
// System.out.println("schemaMappingsLocation加载完成");
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
}
}
}
}
return schemaMappings;
}
@Override
public String toString() {
return "EntityResolver using schema mappings " + getSchemaMappings();
}
}
还没有评论,来说两句吧...