【Mybatis源码】解析MapperProxyFactory类中的methodCache缓存由ConcurrentHashMap构成的原因


前导

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方法打下断点:
【Mybatis源码】解析MapperProxyFactory类中的methodCache缓存由ConcurrentHashMap构成的原因_第1张图片
2、观擦调试器中的方法调用栈Frames
【Mybatis源码】解析MapperProxyFactory类中的methodCache缓存由ConcurrentHashMap构成的原因_第2张图片
根据栈结构,方法调用的顺序是从下往上,其方法调用的过程如下:(若读者不想了解该调用过程,可以直接跳过)

/**  调用过程(从上往下)   **/

//第一步(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。


如果本文出现错误或存在不严谨的表述,还请各位指正!



你可能感兴趣的:(笔记)