mybatis源码解析之Configuration加载(五)

概述

前面几篇文章主要看了mybatis配置文件configuation.xml中<setting>,<environments>标签的加载,接下来看一下mapper标签的解析,先来看下标签的配置:

 1     <mappers>
 2         <!-- 方式一,mapper类和xml文件可以不再同一个目录下 -->
 3         <!-- <mapper resource="mapper/newsMapper.xml" /> -->
 4         
 5         <!-- 方式二,必须保证mapper类和xml文件在同一个目录下 -->
 6         <!--<package name="com.mybatis.read.dao" /> -->
 7         
 8         <!-- 方式三,必须保证mapper类和xml文件在同一个目录下 -->
 9         <mapper class="com.mybatis.read.dao.NewsMapper" />
10     </mappers>

常用的主要就有上面三种方式,指定mapper的xml文件,指定package,指定mapper文件,我们来具体解析下。

解析

我们看下具体开始解析的代码:

 1   private void mapperElement(XNode parent) throws Exception {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         if ("package".equals(child.getName())) {
 5           String mapperPackage = child.getStringAttribute("name");
 6           configuration.addMappers(mapperPackage);
 7         } else {
 8           String resource = child.getStringAttribute("resource");
 9           String url = child.getStringAttribute("url");
10           String mapperClass = child.getStringAttribute("class");
11           if (resource != null && url == null && mapperClass == null) {
12             ErrorContext.instance().resource(resource);
13             InputStream inputStream = Resources.getResourceAsStream(resource);
14             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
15             mapperParser.parse();
16           } else if (resource == null && url != null && mapperClass == null) {
17             ErrorContext.instance().resource(url);
18             InputStream inputStream = Resources.getUrlAsStream(url);
19             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
20             mapperParser.parse();
21           } else if (resource == null && url == null && mapperClass != null) {
22             Class<?> mapperInterface = Resources.classForName(mapperClass);
23             configuration.addMapper(mapperInterface);
24           } else {
25             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
26           }
27         }
28       }
29     }
30   }

这段代码就是针对上面不同的配置进行解析加载,这里面还有一种url的情况,是通过url加载外部的资源,不常用。

我们先来看配置了package的情形,

第5行代码,获取包名,第6行代码调用configuration的addMappers方法,具体看下:

1   public void addMappers(String packageName) {
2     mapperRegistry.addMappers(packageName);
3   }

configuation中又调用了mapperRegistry的addMappers方法:

1   public void addMappers(String packageName) {
2     addMappers(packageName, Object.class);
3   }

在看下addMappers方法:

1   public void addMappers(String packageName, Class<?> superType) {
2     ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
3     resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
4     Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
5     for (Class<?> mapperClass : mapperSet) {
6       addMapper(mapperClass);
7     }
8   }

 上面这段代码主要就是利用ResolverUtil遍历package下面的所有class文件,看下find方法:

 1   public ResolverUtil<T> find(Test test, String packageName) {
 2     String path = getPackagePath(packageName);
 3 
 4     try {
 5       List<String> children = VFS.getInstance().list(path);
 6       for (String child : children) {
 7         if (child.endsWith(".class")) {
 8           addIfMatching(test, child);
 9         }
10       }
11     } catch (IOException ioe) {
12       log.error("Could not read package: " + packageName, ioe);
13     }
14 
15     return this;
16   }

很明显,上面就是根据包名获取下面的class,然后调用addIfMatching方法:

 1   protected void addIfMatching(Test test, String fqn) {
 2     try {
 3       String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
 4       ClassLoader loader = getClassLoader();
 5       if (log.isDebugEnabled()) {
 6         log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
 7       }
 8 
 9       Class<?> type = loader.loadClass(externalName);
10       if (test.matches(type)) {
11         matches.add((Class<T>) type);
12       }
13     } catch (Throwable t) {
14       log.warn("Could not examine class '" + fqn + "'" + " due to a " +
15           t.getClass().getName() + " with message: " + t.getMessage());
16     }
17   }

这个方法主要就是根据前面传过来的class,去加载这个类,注意这边用的类加载器是线程上下文类加载器,然后判断这个类的类型是不是Object类,是的话,就放入matches这个set中,都遍历完成之后,符合要求的class就在这个set中,怎么获取呢?看下getClass方法:

1   public Set<Class<? extends T>> getClasses() {
2     return matches;
3   }

这边调用getClass方法时就返回了这个Set,我们接着看mapperRegistry的addMappers方法,

1   public void addMappers(String packageName, Class<?> superType) {
2     ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
3     resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
4     Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
5     for (Class<?> mapperClass : mapperSet) {
6       addMapper(mapperClass);
7     }
8   }

按照上面的分析,第4行代码,我们已经得到的复合条件的class,下面就是遍历这些class,调用addMapper方法:

 1   public <T> void addMapper(Class<T> type) {
 2     if (type.isInterface()) {
 3       if (hasMapper(type)) {
 4         throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
 5       }
 6       boolean loadCompleted = false;
 7       try {
 8         knownMappers.put(type, new MapperProxyFactory<T>(type));
 9         // It's important that the type is added before the parser is run
10         // otherwise the binding may automatically be attempted by the
11         // mapper parser. If the type is already known, it won't try.
12         MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
13         parser.parse();
14         loadCompleted = true;
15       } finally {
16         if (!loadCompleted) {
17           knownMappers.remove(type);
18         }
19       }
20     }
21   }

这段代码首先判断这个class的类型是不是接口,不是则直接跳过,是的话,判断这个class对应的mapper文件是不是已经加载了,是的话,则抛出异常,否则看下第8行代码,将当前class类作为key,new一个MapperProxyFactory作为value放入knownMappers这个map中,这边这个MapperProxyFactory我们后面会详细介绍,看第12行代码,这边new了一个MapperAnnotationBuilder,看下第13行代码,调用了MapperAnnotationBuilder的parse方法:

 1   public void parse() {
 2     String resource = type.toString();
 3     if (!configuration.isResourceLoaded(resource)) {
 4       loadXmlResource();
 5       configuration.addLoadedResource(resource);
 6       assistant.setCurrentNamespace(type.getName());
 7       parseCache();
 8       parseCacheRef();
 9       Method[] methods = type.getMethods();
10       for (Method method : methods) {
11         try {
12           // issue #237
13           if (!method.isBridge()) {
14             parseStatement(method);
15           }
16         } catch (IncompleteElementException e) {
17           configuration.addIncompleteMethod(new MethodResolver(this, method));
18         }
19       }
20     }
21     parsePendingMethods();
22   }

这边我们只要关注第4行代码,第5行代码之后都是针对注解做的处理,具体看下:

 1   private void loadXmlResource() {
 2     // Spring may not know the real resource name so we check a flag
 3     // to prevent loading again a resource twice
 4     // this flag is set at XMLMapperBuilder#bindMapperForNamespace
 5     if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
 6       String xmlResource = type.getName().replace('.', '/') + ".xml";
 7       InputStream inputStream = null;
 8       try {
 9         inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
10       } catch (IOException e) {
11         // ignore, resource is not required
12       }
13       if (inputStream != null) {
14         XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
15         xmlParser.parse();
16       }
17     }
18   }

这边首先再次确认xml文件有没有被加载过,没有的话继续加载,在当前目录下根据mapper的名字寻找xml文件,然后加载成流,之后交给XmlMapperBuilder处理,其实到这边,xml就已经加载完成了,后续都是处理一些使用了注解的东西,使用注解我们暂且不看。

我们再来看下方法三:

mapperClass不为空,走第三个if:

 1   private void mapperElement(XNode parent) throws Exception {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         if ("package".equals(child.getName())) {
 5           String mapperPackage = child.getStringAttribute("name");
 6           configuration.addMappers(mapperPackage);
 7         } else {
 8           String resource = child.getStringAttribute("resource");
 9           String url = child.getStringAttribute("url");
10           String mapperClass = child.getStringAttribute("class");
11           if (resource != null && url == null && mapperClass == null) {
12             ErrorContext.instance().resource(resource);
13             InputStream inputStream = Resources.getResourceAsStream(resource);
14             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
15             mapperParser.parse();
16           } else if (resource == null && url != null && mapperClass == null) {
17             ErrorContext.instance().resource(url);
18             InputStream inputStream = Resources.getUrlAsStream(url);
19             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
20             mapperParser.parse();
21           } else if (resource == null && url == null && mapperClass != null) {
22             Class<?> mapperInterface = Resources.classForName(mapperClass);
23             configuration.addMapper(mapperInterface);
24           } else {
25             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
26           }
27         }
28       }
29     }
30   }

第22行代码,根据名称加载mapper类,然后调用configuation的addMapper方法,这个我们上面已经分析过了,其实这种情况跟用package的很相似,可以说是一种特殊情况,package里面会有很多的mapper。

我们再看下方法一:

resource不为空: 

1           if (resource != null && url == null && mapperClass == null) {
2             ErrorContext.instance().resource(resource);
3             InputStream inputStream = Resources.getResourceAsStream(resource);
4             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
5             mapperParser.parse();
6           }

 

这边跟上面那个部分一样的,我们下一篇文章就主要看下这个解析过程。

转载于:https://www.cnblogs.com/xiaobaobei/p/10367988.html

MyBatis是一款开源的持久层框架,它支持自定义SQL、存储过程和高级映射。本文将深度解析MyBatis源码,包括其核心组件和实现原理。 1. MyBatis的核心组件 MyBatis的核心组件包括: - SqlSessionFactoryBuilder:用于创建SqlSessionFactory,它会读取配置文件并创建相应的对象。 - SqlSessionFactory:用于创建SqlSession,它是线程安全的,因此可以被共享。 - SqlSession:用于执行SQL语句,它是非线程安全的,因此每个线程都需要创建自己的SqlSession。 - Executor:用于执行SQL语句。 - StatementHandler:用于处理SQL语句的处理器。 - ParameterHandler:用于处理参数的处理器。 - ResultSetHandler:用于处理结果集的处理器。 - MappedStatement:映射配置信息,包括SQL语句、参数类型、返回值类型等。 - ConfigurationMyBatis的配置信息,包括数据源、类型转换器、映射配置等。 2. MyBatis的实现原理 MyBatis的实现原理主要包括以下几个方面: 2.1 映射配置 MyBatis使用XML或注解的方式进行映射配置,其配置信息包括: - 映射文件的命名空间。 - 映射文件中定义的SQL语句,包括select、insert、update、delete等。 - SQL语句的参数类型和返回值类型。 - SQL语句的参数映射关系,包括#{paramName}形式的占位符和${paramName}形式的变量。 - SQL语句的结果映射关系,包括<resultMap>标签和<result>标签。 2.2 解析配置文件 MyBatis会通过SqlSessionFactoryBuilder读取配置文件,并将其解析成一个Configuration对象。解析过程中会进行数据源的配置、类型转换器的配置、映射配置的加载等。 2.3 创建SqlSessionFactory MyBatis解析配置文件之后,会根据配置信息创建SqlSessionFactory对象。SqlSessionFactory是线程安全的,因此可以被共享。它主要负责创建SqlSession对象。 2.4 创建SqlSession 每个SqlSession都会绑定一个Connection对象,因此它是非线程安全的。MyBatis会为每个线程创建自己的SqlSession对象。SqlSession主要负责执行SQL语句。 2.5 执行SQL语句 MyBatis在执行SQL语句时,会先根据SQL语句的ID从MappedStatement中获取相应的映射信息,然后使用Executor执行SQL语句。在执行SQL语句时,会使用StatementHandler处理SQL语句,ParameterHandler处理参数,ResultSetHandler处理结果集。 3. 总结 本文深度解析MyBatis源码,包括其核心组件和实现原理。MyBatis是一个功能强大的持久层框架,可以帮助我们简化数据库操作。同时,MyBatis源码也值得我们深入学习和研究。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值