ConcurrentHashMap主要优势在于可以多线程并发使用,是线程安全的。而在Mybatis中,会话本身就是线程不安全的,那么为何要在与动态代理对象生成过程相关的 MapperProxyFactory 类中构造一个ConcurrentHashMap形式的methodCache呢?
首先我们要了解一下MapperProxyFactory是如何生成的:
//MapperRegistry.class
//knowMappers属性
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
//getMapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
从以上代码可以看出,MapperProxyFactory 是从 knowMappers 中根据 class类型 获取的。那么,knowMappers中的数据从何而来?
答案是 在构造MapperRegistry对象的过程后,通过addMappers方法添加而得。
1、在MapperRegistry类中的addMappers方法打下断点:
2、观擦调试器中的方法调用栈Frames
根据栈结构,方法调用的顺序是从下往上,其方法调用的过程如下:(若读者不想了解该调用过程,可以直接跳过)
/** 调用过程(从上往下) **/
//第一步(SecondCacheTest.class -- 自定义类)
factory = factoryBuilder.build(StatementHandlerTest.class.getResourceAsStream("/mybatis-config.xml"));
//第二步(SqlSessionFactoryBuilder.class)
public SqlSessionFactory build(InputStream inputStream) {
/** 继续执行本类中的重载build方法 **/
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
//第三步(SqlSessionFactoryBuilder.class)
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
/** 从parser方法中进入下一步 **/
var5 = this.build(parser.parse());
}
/** 省略部分代码... **/
return var5;
}
//第四步(XMLConfigBuilder.class)
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
/** 调用本类中的parseConfiguration方法解析配置信息 **/
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
//第五步(XMLConfigBuilder.class)
private void parseConfiguration(XNode root) {
try {
/** 省略部分代码... **/
/** 调用本类中的mapperElement方法进一步解析mappers **/
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
//第六步(XMLConfigBuilder.class)
private void mapperElement(XNode parent) throws Exception {
/** 省略其他代码... **/
/** 调用Configuration中的addMappers方法 **/
this.configuration.addMappers(resource);
}
//第七步(Configuration.class)
public void addMappers(String packageName) {
/** 调用MapperRegistry中的addMappers方法 **/
this.mapperRegistry.addMappers(packageName);
}
从以上代码可以看出,在解析全局配置信息的过程中,当找到mybatis配置文件中的mappers标签后,会将其package子标签中name所表示的目录中的所有的文件进行解析。
至此,我们已经进入MapperRegistry类中,继续执行addMappers方法,又因为在该类中包含两个addMappers重载方法,且这两个方法最终都需要执行addMapper方法,所以现在主要分析该方法。
public <T> void addMapper(Class<T> type) {
/* 如果type不是接口类即跳过 */
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
/**
* 向konwMappers中添加一个map数据
* key -> type
* value -> new MapperProxyFactory(type)
*/
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
只有当扫描到的类文件属于接口类才会加入到knowMappers中,而且只存在唯一一个名为type的键值对应一个MapperProxyFactory对象。
上述过程在创建会话工厂时进行,所以通过相同的mapper文件构建多个不同的会话时,所得到的MapperProxyFactory对象是一样的,因为它通过knowMappers获取时传入的参数type一样。
所以说明在MapperProxyFactory类中,methodCache可以在不同的会话中进行处理,而不同的会话间需要保证并发处理数据时的安全问题,于是使用ConcurrentHashMap来构造methodCache。
如果本文出现错误或存在不严谨的表述,还请各位指正!