静态代码块读取主配文件
static {
String config = "mybatis-config.xml";
try {
//第一步加载主配文件
InputStream resourceAsStream = Resources.getResourceAsStream(config);
//第二步解析主配文件构建sqlSession工厂
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
将mybatis-config.xml主配文件加载到内存中。
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
mybatis中封装了一个工具类Resources中的getResourceAsStream方法用来获取主配文件的输入流,该方法默认使用系统类加载器加载指定文件。
classLoaderWrapper 字段和getResourceAsStream方法:
classLoaderWrapper字段是ClassLoaderWrapper类型 ,是mybatis对ClassLoader(类加载器)的封装
所以getResourceAsStream底层调用的是ClassLoader中的getResourceAsStream方法。
Resources类
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource); //传入的类加载器为null,调用重载方法
}
/**
* Returns a resource on the classpath as a Stream object
*
* @param loader The classloader used to fetch the resource
* @param resource The resource to find
* @return The resource
* @throws java.io.IOException If the resource cannot be found or read
*/
// getResourceAsStream重载方法
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
//调用包装类加载器中的方法
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
ClassLoaderWrapper类
可以看到该类中有两个ClassLoader类型的字段,所以ClassLoaderWapper本质上是对ClassLoder的封装,
该类中的getResourceAsStream方法:
//ClassLoaderWapper中的方法
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) {
// try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource);
// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
if (null == returnValue) {
//调用ClassLoader中的getResourceAsStream方法
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}
从源码中可以看出该方法本质上是调用的ClassLoader类中的getResourceAsStream方法返回指定文件的输入流。
第一步总结:mybatis通过Resources工具类一步步调用ClassLoader中方法来加载主配文件并非返回该文件的输入流。
解析主配文件然后将主配文件中的信息存放在Configuration类中并将该信息类作为参数来创建一个DefaultSqlSessionFactory对象(DefaultSqlSessionFactory是SqlSessionFactory接口的实现类(也可以说DefaultSqlSessionFactory对象就是SqlSessionFactory对象)),构建DefaultSqlSessionFactory需要创建SqlSessionFactoryBuilder对象并调用该对象中的build方法来创建,说白了想要得到DefaultSqlSessionFactory对象就必须借助SqlSessionFactoryBuilder中的build方法。这就是建造者模式 。
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
build方法内部是如何工作的,我们看源码。
build方法:
//依次向下调用重载方法
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//XMLConfigBuilder 故名意思xml配置构建者,用来构建Configuration类
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) {
// Intentionally ignore. Prefer previous error.
}
}
}
//真正产生对象的方法
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
在build方法中发现一个XMLConfigBuilder类,该类是用来构建Configuration配置信息类的。
Configuration类长这样
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected boolean shrinkWhitespacesInSql;
protected boolean nullableOnForEach;
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected Class<?> defaultSqlProviderType;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ResultSetType defaultResultSetType;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see Issue 300 (google code)
*/
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<>();
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<>();
public Configuration(Environment environment) {
this();
this.environment = environment;
}
该类中定义了很多属性,这些属性就是用来映射xml文件中的表签的,除此之外还有很多get、set方法。
对Configuration类有了大概的认识后,接着来看XMLConfigBuilder类
源码:
//XMLConfigBuilder类
private boolean parsed;
private final XPathParser parser;
private String environment;
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
//该类有很多构造方法 依次向下调用
public XMLConfigBuilder(Reader reader) {
this(reader, null, null);
}
public XMLConfigBuilder(Reader reader, String environment) {
this(reader, environment, null);
}
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XMLConfigBuilder(InputStream inputStream) {
this(inputStream, null, null);
}
public XMLConfigBuilder(InputStream inputStream, String environment) {
this(inputStream, environment, null);
}
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
//new XPathParser XPath解析器解析xml文件中各个节点信息的工具
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//调用父类BaseBuilder 的构造方法
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
XMLConfigBuilder是抽象类BaseBuilder的子类。在调用构造方法时最终调用了父类BaseBuilder的构造方法。
BaseBuilder类源码
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
该类中有3个protected修饰的属性:
private final Map<String, Class<?>> typeAliases = new HashMap<>();
//构造方法中初始化很多别名
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
让我们重新回到XMLConfigBuilder类中,上面说到该类继承了BaseBuilder中的3个属性。new 完XMLConfigBuiler对象后,这三个属性对应的实体对象还是没有数据的,所以下一步就是解析xml文件并将信息封装到对应的属性中。
XMLConfigBuilder中的方法:
可以看到XMLConfigBuilder中有很多私有方法这些方法基本都是用来向BaseBuilder中的那3个属性设置值的。
私有方法被调用只能是在公有方法中。
parse方法:
public Configuration parse() {
//判断是否解析过防止浪费资源
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//参数 parser 是 XpathPraser 的实体对象 也是XMLConfigBuilder中的属性 上文介绍过了
//它是正真干活的工具
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private final XPathParser parser;
XPathParser源码:
private final Document document; //文档树对象
private boolean validation;
private EntityResolver entityResolver;
private Properties variables;
private XPath xpath; //内置了一个XPath选择器
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
//其余方法省略
看XPathParser源码 ,可以看到它有两个重要的属性 Document 和 XPath 有js基础的同学应该知道这两个类对应的是什么,在前端的HTML中 ,我们把整个HTML文件抽象成一个文档树就是父标签包含子标签的那种,XPath则是一个在文档书中查找指定节点(标签)的方法。在XML文件中也是这么一个文档书。再看evalNode方法的返回值是XNode类型的,XNode就是mybatis对Node类的一个封装就类似于上文说的ClassLoaderWapper和ClassLoader的关系一样。所以XNode就代表一个节点(标签)通过找到节点来拿到节点对应的值。 那么evalNode方法就是根据给定节点名称来返回一个节点对象,对象里面包含着节点的信息。
parse方法中的代码 parseConfiguration(parser.evalNode(“/configuration”)); 参数中/configuration就是主配置文件的根节点。有了根节点就可以获取其子节点,从而解析全部的节点。
接着来看XMLConfigBuilder中的parseConfiguration方法
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
该方法就是对所有标签依次解析。最后将解析后的值封装到Configuration类中 并通过该类拿到SqlSessionFactory。
解析映射文件
mapperElement(root.evalNode("mappers"));
XMLConfigBuilder类中的mapperElement方法:
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 {
//单文件注册的 3种解析方式
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// resource方式解析
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
//url方式解析
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
//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.");
}
}
}
}
}
在主配文件中注册mapper分为单文件注册和多文件注册
单文件注册:
多文件注册:
源码中 使用 resource 和 url 的 方式中 可以看到 XMLMapperBuilder 类,打开可以看到 这个类和XMLConfigBuilder类很相似,只是前者多了一个用于存储SQL片段XNode节点的Map集合。
XMLMapperBuilder类:
public class XMLMapperBuilder extends BaseBuilder {
private final XPathParser parser;
private final MapperBuilderAssistant builderAssistant;
private final Map<String, XNode> sqlFragments;
private final String resource;
er>节点的resource属性直接映射XML文件。
使用节点url属性映射网络或者磁盘路径下的某个XML文件。
使用节点的class属性直接映射某个mapper接口。
多文件注册:
源码中 使用 resource 和 url 的 方式中 可以看到 XMLMapperBuilder 类,打开可以看到 这个类和XMLConfigBuilder类很相似,只是前者多了一个用于存储SQL片段XNode节点的Map集合。
XMLMapperBuilder类:
public class XMLMapperBuilder extends BaseBuilder {
private final XPathParser parser;
private final MapperBuilderAssistant builderAssistant;
private final Map<String, XNode> sqlFragments;
private final String resource;