Mapper 代理方式初始化
- 首先修改一下 SqlSession 获取代理对象方式,即通过 getMapper() 来拿到动态代理对象
public class MybatisTest {
/**
* 问题1:<package name="com.itheima.mapper"/> 是如何进行解析的?
* 解答:解析得到 name 属性的值(包名)-->根据包名加载该包下所有 的mapper 接口--->将 mapper 接口及代理工厂对象存到 knownMappers map 集合中
* 根据 mapper 接口的路径替换. / ---根据替换后路径定位到对应的映射配置文件--->XMLMapperBuilder.parse() 注解方式解析
**/
@Test
public void test2() throws IOException {
// 1. 通过类加载器对配置文件进行加载,加载成了字节输入流,存到内存中 注意:配置文件并没有被解析
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2. (1) 解析了配置文件,封装 configuration 对象 (2)创建了 DefaultSqlSessionFactory 工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3.问题:openSession()执行逻辑是什么?
// 3. (1) 创建事务对象 (2)创建了执行器对象cachingExecutor (3) 创建了DefaultSqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. JDK动态代理生成代理对象,就是这里
UserMapper mapperProxy = sqlSession.getMapper(UserMapper.class);
...
}
}
- 修改 sqlMapConfig.xml 引入配置文件的方式
<configuration>
...
<!--第二部分:引入映射配置文件-->
<mappers>
<package name="com.itheima.mapper"/>
</mappers>
</configuration>
- 把 UserMapper.xml 放到和 com.itheima.mapper.UserMapper 同一个目录,同时修改一下命名空间,然后就可以学习 MyBatis 的代理方式
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 即修改这里 -->
<mapper namespace="com.itheima.mapper.UserMapper">
<cache></cache>
<select id="findByCondition" resultType="com.itheima.pojo.User" useCache="true">
SELECT id, name FROM user WHERE id = #{id}
</select>
</mapper>
- 问题
- <package name=“com.itheima.mapper”/> 是如何进行解析的?
- 首先解析配置文件还是从 SqlSessionFactoryBuilder 的 build() 开始,方法内会先创建 XMLConfigBuilder,然后开始解析 sqlMapConfig.xml 配置文件
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder:用来解析XML配置文件
// 使用构建者模式(至少 4 个以上成员变量):好处:降低耦合、分离复杂对象的创建
// 1. 创建 XPathParser 解析器对象,根据 inputStream 解析成了 document 对象 2. 创建全局配置对象 Configuration 对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 2. parser.parse():使用 XPATH 解析XML配置文件,将配置文件封装到 Configuration 对象
// 返回 DefaultSqlSessionFactory 对象,该对象拥有 Configuration 对象(封装配置文件信息)
// 3. parse():配置文件就解析完成了
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
// 4. 返回对应封装的 DefaultSqlSessionFactory`
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
- parse() 会继续先解析 /configuration 节点,然后从根节点开始找到每个节点进行解析
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private final XPathParser parser;
...
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// parser.evalNode("/configuration"):通过 XPATH 解析器,解析 configuration 根节点
// 1. 从 configuration 根节点开始解析,最终将解析出的内容封装到 Configuration 对象中
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
}
- 这次我们更关注解析 /mappers 这个基点
public class XMLConfigBuilder extends BaseBuilder {
...
private void parseConfiguration(XNode root) {
try {
...
// 1. 解析 </mappers> 标签 加载映射文件流程主入口
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
- 获取 /mappers 标签,然后遍历 /mappers 标签的子标签,也就是现在我们配置的 /package 标签,从标签中拿到 name 属性,即 com.itheima.mapper,然后开始将包下所有的 mapper 接口以及它的代理工厂对象存储到 Configuration 的一个 Map 集合中,key 为 mapper 接口类型,value 为代理对象工厂
public abstract class BaseBuilder {
protected final Configuration configuration;
...
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 1. 获取 <mappers> 标签的子标签
for (XNode child : parent.getChildren()) {
// 2. <package> 子标签
if ("package".equals(child.getName())) {
// 3. 获取 mapper 接口和 mapper 映射文件对应的 package 包名。即 com.itheima.mapper
String mapperPackage = child.getStringAttribute("name");
// 4. 将包下所有的 mapper 接口以及它的代理工厂对象存储到一个 Map 集合中,key 为 mapper 接口类型,value 为代理对象工厂
configuration.addMappers(mapperPackage);
} else {// <mapper>子标签
...
}
}
}
}
- Configuration 对象会交由 MapperRegistry 来添加到 Map 集合
public class Configuration {
...
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
...
public void addMappers(String packageName) {
// 1. 交由 MapperRegistry 来添加到 Map 集合。其中 packageName 为 com.itheima.mapper
mapperRegistry.addMappers(packageName);
}
}
- MapperRegistry 先创建解析工具类 ResolverUtil,根据 packageName 找到该包下所有的 Mapper 接口文件,然后将 Mapper 接口添加到 MapperRegistry 中
public class MapperRegistry {
...
public void addMappers(String packageName, Class<?> superType) {
// 其中 packageName 为 com.itheima.mapper,而 superType 为 Object.class
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 1. 根据 package 名称,加载该包下 Mapper 接口文件(不是映射文件)
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 2. 获取加载的 Mapper 接口,其实就是从 matches 拿到类集合
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
// 3. 将 Mapper 接口添加到 MapperRegistry 中
addMapper(mapperClass);
}
}
}
- ResolverUtil 找接口时,先把 packageName 改成 com/itheima/mapper,然后查找所有的 .class 文件,然后加载这个文件存入 matches 中
public class ResolverUtil<T> {
private Set<Class<? extends T>> matches = new HashSet<>();
...
protected String getPackagePath(String packageName) {
// 1. 修改报名
return packageName == null ? null : packageName.replace('.', '/');
}
public ResolverUtil<T> find(Test test, String packageName) {
// 2. packageName 为 com.itheima.mapper,改成 com/itheima/mapper
String path = getPackagePath(packageName);
try {
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
if (child.endsWith(".class")) {
// 3. 添加到 matches 属性中
addIfMatching(test, child);
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
protected void addIfMatching(Test test, String fqn) {
try {
// 4. 把 fqn 的 com/itheima/mapper/UserMapper.class 改全类名,即 com.itheima.mapper.UserMapper
String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
ClassLoader loader = getClassLoader();
if (log.isDebugEnabled()) {
log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
}
// 5. 加载类
Class<?> type = loader.loadClass(externalName);
if (test.matches(type)) {
// 6. 将类装入 matches 中
matches.add((Class<T>) type);
}
} catch (Throwable t) {
log.warn("Could not examine class '" + fqn + "'" + " due to a "
+ t.getClass().getName() + " with message: " + t.getMessage());
}
}
}
- 得到类集合之后,先判断对应的类是否已经加入过 knownMappers,如果是,则抛出异常,否则就加入 knownMappers 中,其中 key 为 type,value 为 MapperProxyFactory 代理工厂,如果接口中有使用 @Select 或者配置了 Mapper.xml,就需要使用 MapperAnnotationBuilder 进一步解析
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
...
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
// 1. 如果 Map 集合中已经有该 mapper 接口的映射,就不需要再存储了
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 2. 将 mapper 接口以及它的代理对象存储到一个 Map 集合中,key 为 mapper 接口类型,value 为代理对象工厂
knownMappers.put(type, new MapperProxyFactory<>(type));
// 3. 用来解析注解方式的 mapper 接口
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 4. 解析注解方式的 mapper 接口
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
- 解析 Mapper.xml 时,先获取 mapper 接口的全路径(即 interface com.itheima.mapper.UserMapper)。如果 Configuration 对象从来没加载过,就创建 XMLMapperBuilder 来解析。然后遍历 type 的每个方法,都解析构成 MappedStatement 对象,存入 Configuration 对象中
public class MapperAnnotationBuilder {
private final Configuration configuration;
private final MapperBuilderAssistant assistant;
private final Class<?> type;
...
public void parse() {
// 1. 获取 mapper 接口的全路径,即 interface com.itheima.mapper.UserMapper
String resource = type.toString();
// 2. 是否解析过该 mapper 接口
if (!configuration.isResourceLoaded(resource)) {
// 3. 先解析 mapper 映射文件
loadXmlResource();
// 设置解析标识
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// 解析CacheNamespace注解
parseCache();
// 解析CacheNamespaceRef注解
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 {
// 4. 每个 mapper 接口中的方法,都解析成 MappedStatement 对象
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
void parseStatement(Method method) {
// 1. 获取 Mapper 接口的形参类型
final Class<?> parameterTypeClass = getParameterType(method);
...
// 2. 添加到 Configuration 对象
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
statementAnnotation.getDatabaseId(),
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
});
}
}
-
总结
文章来源地址https://www.toymoban.com/news/detail-491769.html
文章来源:https://www.toymoban.com/news/detail-491769.html
到了这里,关于『手撕 Mybatis 源码』06 - Mapper 代理方式初始化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!