Mybatis通过MappedStatement来描述XML中
XMLConfigBuilder对外提供了一个parse()方法用于解析构造Configuration对象,XMLConfigBuilder#parse方法首先判断当前XML是否正在被解析,如果没有被解析就将parsed属性设置为true,就确保一次调用;然后获取configuration标签下的所有信息;最后调用XMLConfigBuilder#mapperElement方法解析对应标签信息。
XMLConfigBuilder#parseConfiguration方法是XMLConfigBuilder中用于解析各属性信息的一个私有方法,下述代码仅截取了构造MappedStatement的部分代码,不难看出XMLConfigBuilder#parseConfiguration方法在获取到mappers属性标签信息后,直接调用了XMLConfigBuilder#mapperElement私有方法。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 标记为解析中,保证一次调用
parsed = true;
// 首先获取configuration标签下的所有信息
// 然后调用parseConfiguration方法解析对应标签信息
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 省略部分代码
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
XMLConfigBuilder#mapperElement方法相当于一个分流器,将不同类型标识mapper的标签信息指引到所属的处理逻辑上,如下(亦可访问org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement源代码了解更多信息):
虽然XMLConfigBuilder#mapperElement针对package、resource、url和class标签都有其专属的解析逻辑,但是归根结底就两个逻辑,一个是解析XML配置中的
当XML配置的是resource和url节点时会使用XMLMapperBuilder#parse来解析构造MappedStatement,但是在调用XMLMapperBuilder#parse之前,需要先使用XMLMapperBuilder的构造方法构造一个XMLMapperBuilder对象。
XMLMapperBuilder#parse处理逻辑大致分为,
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 首先获取mapper标签信息
// 然后解析mapper标签下所有信息
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// 从命名空间构造MappedStatement
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
XMLMapperBuilder#configurationElement分别解析了cache-ref、cache、/mapper/parameterMap、/mapper/resultMap、/mapper/sql和select|insert|update|delete标签信息,可详见org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement源代码。
当XML配置的是package和class节点时会使用XMLMapperBuilder#parse来解析构造MappedStatement,同样需要先使用MapperAnnotationBuilder构造方法构造MapperAnnotationBuilder对象,才能使用parse方法。
从源代码来看MapperAnnotationBuilder#parse,首先会调用MapperAnnotationBuilder#loadXmlResource私有方法来通过XMLMapperBuilder#parse方法解析构造当前Mapper接口对应XML文件中的信息,在完成XML解析之后才是正在解析Mapper接口中定义的注解,详细可见org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse。
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
无论是使用XMLMapperBuilder#parse还是MapperAnnotationBuilder#parse来解析,最终都会通过MapperBuilderAssistant#addMappedStatement方法来构造MappedStatement对象放入configuration中的mappedStatements属性。
了解了Mybatis是如何完成解析构造MappedStatement之后,我们在一起来看看Mybatis是如何根据Mapper接口来动态生成真正调用类。回到XMLMapperBuilder#parse和MapperAnnotationBuilder#parse方法中调用的MapperRegistry#addMapper方法,其源代码中通过MapperProxyFactory类来注册一个Mapper代理类工厂,然后在调用MapperRegistry#getMapper方法时通过MapperProxyFactory#newInstance方法来生成代理类。
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
// 省略部分代码
knownMappers.put(type, new MapperProxyFactory<>(type));
}
}
MapperProxyFactory#newInstance使用JDK动态代理Proxy类来完成生成mapper接口的代理类,这也正是Mybatis的mapper需要以接口形式存在的原因。
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(
sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}