Mapper 代理方式初始化
- 首先修改一下 SqlSession 获取代理对象方式,即通过 getMapper() 来拿到动态代理对象
public class MybatisTest {
@Test
public void test2() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
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>
- 问题
- 首先解析配置文件还是从 SqlSessionFactoryBuilder 的 build() 开始,方法内会先创建 XMLConfigBuilder,然后开始解析 sqlMapConfig.xml 配置文件
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
}
}
}
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;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
}
- 这次我们更关注解析 /mappers 这个基点
public class XMLConfigBuilder extends BaseBuilder {
...
private void parseConfiguration(XNode root) {
try {
...
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) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
...
}
}
}
}
- Configuration 对象会交由 MapperRegistry 来添加到 Map 集合
public class Configuration {
...
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
...
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
}
- MapperRegistry 先创建解析工具类 ResolverUtil,根据 packageName 找到该包下所有的 Mapper 接口文件,然后将 Mapper 接口添加到 MapperRegistry 中
public class MapperRegistry {
...
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
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) {
return packageName == null ? null : packageName.replace('.', '/');
}
public ResolverUtil<T> find(Test test, String packageName) {
String path = getPackagePath(packageName);
try {
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
if (child.endsWith(".class")) {
addIfMatching(test, child);
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
protected void addIfMatching(Test test, String fqn) {
try {
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 + "]");
}
Class<?> type = loader.loadClass(externalName);
if (test.matches(type)) {
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()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
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() {
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();
}
void parseStatement(Method method) {
final Class<?> parameterTypeClass = getParameterType(method);
...
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
false,
keyGenerator,
keyProperty,
keyColumn,
statementAnnotation.getDatabaseId(),
languageDriver,
options != null ? nullOrEmpty(options.resultSets()) : null);
});
}
}
- 总结