有时候我们明明只注册了UserMapper.xml资源,但是并没有注册UserMapper.java接口,但是却可以进行使用,原因是因为mybatis底层帮我们自动进行转换了,反过来也是一样的,以下就把两种情况都看下
xml自动注册接口
xml解析入口为:org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
//如果没解析过才会进行解析,其实就是用set来进行判断
if (!configuration.isResourceLoaded(resource)) {
//解析mapper标记,这也是最核心的一个位置
configurationElement(parser.evalNode("/mapper"));
//放到mapper里面去
configuration.addLoadedResource(resource);
//绑定到接口上,如果namespace配置的是接口的话,这也是一个规范
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
在解析完以后mybatis帮我们注册对应的接口了, bindMapperForNamespace
private void bindMapperForNamespace() {
//获取xml中配置的接口全类名
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//Class.forName获取Class对象
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
//反射成功并且配置中还没有这个接口对应的信息,那么就进行注册
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
可见,这个过程还是比较简单的,我们只需要把xml文件中的namespace配置为接口的全类名即可
接口自动注册xml
接口自己注册位置:org.apache.ibatis.binding.MapperRegistry#addMapper
//只有接口才会进来,因为要生成代理对象
if (type.isInterface()) {
//已经有了就直接抛异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//生成代理对象
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//解析,这里面又会把接口相关的再解析一遍,根据接口全类名去找关联的xml文件,
//格式为 String xmlResource = type.getName().replace('.', '/') + ".xml";
//所以要遵循它的规定
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
等接口注册完以后它又会去注册对应的xml文件 parse方法中的
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#loadXmlResource这个位置进行加载
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
//判断是不是加载过了就不重复加载
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
//拼接路径获取资源文件
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
/**
* xmlResource 格式为 com/zxc/study/test/mapper/UserMapper.xml 它会把接口替换为具体的xml位置
* 所以你的xml文件就必须遵循对应的规范了,否则mybatis就无法帮你自动解析xml
*
* 当然了,你也可以自己在这里定义mapper文件的地址去加载,但是原生的mybatis这里不提扩展,要改只能改源码,,一般也是不建议的
* mybatis-spring和mybatis-plus可能覆盖了这里的方法进行可以指定吧
*/
//或者对应的资源文件
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (Exception e) {
// ignore, resource is not required
}
if (inputStream != null) {
//不为空时又调用原来的解析
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
其实原理也不难,只不过这里稍微要遵循下规范,比xml自动注册接口麻烦了一点点
以上便是本篇的内容,其实就是要遵循一些规范,代码单独就这个逻辑的话也并不是很复杂