mybatis源码配置文件解析之五:解析mappers标签(解析class属性
在上篇文章中分析了mybatis解析mapper标签中的resource、url属性的过程,《mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)》。通过分析可以知道在解析这两个属性的时候首先解析的是对应的XML映射文件,然后解析XML映射文件中的namespace属性配置的接口,在上篇中说到该解析过程和mapper标签中的class属性的解析过程是一样的,因为class属性配置的即是一个接口的全限类名。 一、概述在mybatis的核心配置文件中配置mappers标签有以下方式, <mappers> mapper class="cn.com.mybatis.dao.UserMapper"/> </> 上面这种方式便是mapper标签的class属性配置方式,其解析部分过程如下, else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url,resource or class,but not more than one."); } 可以看到主要是调用了configuration.addMapper方法,和上篇文章中解析namespace调用的方法是一致的。看其具体实现 public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); } 下方分析mapperRegistry.addMapper方法。 二、详述mapperRegistry.addMapper方法的定义如下, type) { if (type.isInterface()) {//判断是否为接口 if (hasMapper(type)) {如果knownMappers中已经存在该type,则抛出异常 new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { 1、把type放入knownMappers中,其value为一个MapperProxyFactory对象 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. 2、对mapper文件及注解进行解析,初始化了sqlAnnotationTypessqlProviderAnnotationTypes两个变量 //具体的解析过程,1、先解析对应的XML映射文件,2、再解析接口方法中的注解信息 /**sqlAnnotationTypes.add(Select.class); sqlAnnotationTypes.add(Insert.class); sqlAnnotationTypes.add(Update.class); sqlAnnotationTypes.add(Delete.class); sqlProviderAnnotationTypes.add(SelectProvider.class); sqlProviderAnnotationTypes.add(InsertProvider.class); sqlProviderAnnotationTypes.add(UpdateProvider.class); sqlProviderAnnotationTypes.add(DeleteProvider.class); * */ MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config,type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) {//3、如果解析失败,则删除knowMapper中的信息 knownMappers.remove(type); } } } } 该方法主要分为下面几个步骤。 1、检查是否解析过接口首先会判断knowMappers中是否已经存在该接口,如果存在则会抛出异常 );
}
如果不存在则放入knownMappers中, new MapperProxyFactory<T>(type));
继续解析对应的映射文件及接口方法注解 2、解析接口对应的映射文件及接口方法注解上面把mapper接口放入了knownMappers中,接着需要解析映射文件及注解, 2、对mapper文件及注解进行解析,初始化了sqlAnnotationTypessqlProviderAnnotationTypes两个变量 具体的解析过程,1、先解析对应的XML映射文件,2、再解析接口方法中的注解信息 true; 上面的代码,生成了一个MapperAnnotationBuilder实例, public MapperAnnotationBuilder(Configuration configuration,Class<?> type) { String resource = type.getName().replace('.','/') + ".java (best guess)"; this.assistant = MapperBuilderAssistant(configuration,resource); this.configuration = configuration; this.type = type; sqlAnnotationTypes.add(Select.); sqlAnnotationTypes.add(Insert.); sqlAnnotationTypes.add(Update.); sqlAnnotationTypes.add(Delete.); sqlProviderAnnotationTypes.add(SelectProvider.); sqlProviderAnnotationTypes.add(InsertProvider.); sqlProviderAnnotationTypes.add(UpdateProvider.); sqlProviderAnnotationTypes.add(DeleteProvider.); } 给sqlAnnotationTypes和sqlProviderAnnotationTypes进行了赋值。 下面看具体的解析过程, parser.parse(); MapperAnnotationBuilder的parse方法如下, public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) {//判断是否加载过该Mapper接口 解析和接口同名的xml文件,前提是存在该文件,如果不存在该文件要怎么解析那?答案是解析接口中方法上的注解 * 1、解析和接口同名的xml配置文件,最终要做的是把xml文件中的标签,转化为mapperStatement, * 并放入mappedStatements中 * loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); 解析接口上的@CacheNamespace注解 parseCache(); parseCacheRef(); 2、获得接口中的所有方法,并解析方法上的注解 Method[] methods = type.getMethods(); for (Method method : methods) { issue #237 method.isBridge()) { 解析方法上的注解 parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this,method)); } } } parsePendingMethods(); } 首先判断是否加载过该资源, configuration.isResourceLoaded(resource)) {
}
只有未加载过,才会执行该方法的逻辑,否则该方法执行完毕。 boolean isResourceLoaded(String resource) { return loadedResources.contains(resource); } 从loadResources中进行判断,判断是否解析过该Mapper接口,答案是没有解析过,则会继续解析。 1.1、解析对应的XML文件首先会解析XML文件,调用下面的方法,
loadXmlResource();
看loadXmlResource方法 * 解析mapper配置文件 */ private 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())) { 解析对应的XML映射文件,其名称为接口类+"."+xml,即和接口类同名且在同一个包下。 String xmlResource = type.getName().replace('.','/') + ".xml"; InputStream inputStream = { inputStream = Resources.getResourceAsStream(type.getClassLoader(),xmlResource); } (IOException e) { ignore,resource is not required } if (inputStream != ) { XMLMapperBuilder xmlParser = XMLMapperBuilder(inputStream,assistant.getConfiguration(),xmlResource,configuration.getSqlFragments(),type.getName()); 解析xml映射文件 xmlParser.parse(); } } } 首先进行了判断,进入if判断,看判断上的注解 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 第一句注解没理解什么意思,第二句的意思是方式两次加载资源,第三句是说明了该标识是在XMLMapperBuilder类中的bindMapperForNamespace中进行的设置,如下 为什么这样设置,后面会总结mapper的加载流程详细说明该问题。 判断之后寻找相应的XML映射文件,映射文件的文件路径如下,
从上面可以看出Mapper接口文件和XML映射文件在同一个包下,且文件名称相同(扩展名不同)。接着便是解析XML映射文件的逻辑。 xmlParser.parse();
}
该逻辑和《mybatis源码配置文件解析之五:解析mappers标签(解析XML映射文件)》的过程是一样的,调用XMLMapperBuilder的parse方法进行解析,解析的结果为MapperStatement对象。 1.2、解析接口方法上的注解上面是解析接口对应的XML映射文件,解析完成之后,还要解析接口方法上的注解,因为mybatis的sql配置有两种方式,一种是通过XML映射文件,另一种便是注解(当SQL比较复杂建议使用映射文件的方式),下面看解析注解的过程,
注解的解析和解析XML映射文件的方式是一样的,解析的属性是一致的。需要注意下面的注解 @SelectProvider(type=BaseUserProvider.class,method="getUser")
该注解的意思是定义select语句的提供者,需要配置type和method,即提供类的Class对象和相应的方法(返回一个字符串) 3、解析失败回退如果在继续过程中失败或抛出异常,则进行回退,回退的意思是从knownMappers中删除该类型。 3、如果解析失败,则删除knowMapper中的信息 knownMappers.remove(type); } } 因为Mapper解析的过程有两个结果一个是放入到configuration.knownMappers中的MapperProxyFactory对象,一个是放入到configuration.mappedStatements中MappedStatement对象,由于生产MappedStatement对象失败,所以要回退生成MapperProxyFactory对象过程。 三、总结本文分析了mybatis解析<mapper class=""/>的过程,依旧是包含MapperProxyFactory和MappedStatement两个过程。 ? 有不当之处,欢迎指正,感谢! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |