MyBatis 3.5.4源码之旅一之初始化流程一
前言
mybatis
是什么,跟传统的JDBC
,HIBERNATE
之间有什么优点这种我就不说了,我还是分享点比较实在的东西,因为源码比较多,也比较复杂,一行行走下去意义不大,这个也不是读源码的好方式,我们读源码不是为了看源码而读,而是要知道他的原理,知道他的思想,一些实现的细节,顺便学习下他的一些风格,一些技巧,一些设计模式,自己项目也可以参考参考等等。所以看源码我打算整理一个大致的结构,然后具体的一些细节就用提问回答的方式去看源码,只要有问题,我们就去看源码,到底是怎么实现的,是怎么个过程。同时我也不会说太多的导包,配置上的细节,主要还是在mybatis
的3.5.4-SNAPSHOT
版本的源码上讲。
初始化流程
我们都知道,mybaits
的基本用法,比如初始化的这段代码:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
上面的代码其实就是做了初始化的工作,把resources
目录下的mybatis-config.xml
配置文件读进来,然后进行构造,构造出SqlSessionFactory
。看起来好像很简单,但是其实里面做了很多的事情,接下来我们就来看看吧。
初始化的问题
1.如何读入配置文件
我们可以看到Resources.getResourceAsStream(resource);
这句,其实Resources
是mybatis
自定义的类,首先先说一下这个classLoaderWrapper
是在Resources
初始化的时候创建的:
private static ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper();
看他的构造函数,是取获得系统类加载器,也就是我们常说的应用类加载器:
ClassLoaderWrapper() {
try {
systemClassLoader = ClassLoader.getSystemClassLoader();
} catch (SecurityException ignored) {
// AccessControlException on Google App Engine
}
}
然后我们再看看Resources
是怎么做的:
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource);
}
跟进去,我们发现里面是通过classLoaderWrapper
来获取输入流的:
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.getResourceAsStream
:
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return getResourceAsStream(resource, getClassLoaders(classLoader));
}
会先获取类加载器:
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}
你会发现,他要获取那么多类加载器,其实只有最后3
个是有的,前面两个是null,而且都是同一个系统类加载器:
我们继续getResourceAsStream
,你会发现,源码他拿那么多类加载器是为了读取输入流,我们知道加载器是可以从类路径里(比如target/classes/xxx
)加载文件的,字节码文件,资源文件,所以不管哪个加载器,只要读进来了就可以了,而且有些加载器要求有/
开头的,具体可以看JDK
源码,就不深入讲了:
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) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}
2.SqlSessionFactoryBuilder的build构造方法做了什么
接下去我们就要研究这么简单的一句代码了,其实里面做了好多东西:
new SqlSessionFactoryBuilder().build(reader);
里面有一些build
重载,有InputStream
的最后都是调用了,如果是Reader
那就是另外一个,只是输入流类型不一样,处理是一样的,都是创建XMLConfigBuilder
后处理:
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) {
// Intentionally ignore. Prefer previous error.
}
}
}
创建XMLConfigBuilder
看看XMLConfigBuilder
构造方法:
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
首先是创建解析xml
的对象XPathParser
:
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
然后创建文档对象,这个属于JDK
里的功能,具体有兴趣可以自己跟进去看看:
this.document = createDocument(new InputSource(inputStream));
继续回到XMLConfigBuilder
创建this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props)
:
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
创建Configuration
首先得创建Configuration
,一看就知道是配置的意思,对的,配置文件里的信息都会保存在这里面,看看他的构造函数,里面注册了很多内部要用的类型和他的名字,名字是小写的,比如事务,池化,缓存,日志等等:
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
我们看一个注册方法registerAlias
,其他基本类似的,他会把你的别名小写和类型联系起来:
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
String key = alias.toLowerCase(Locale.ENGLISH);
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
typeAliases.put(key, value);
}
然后回来看super(new Configuration());
父类的构造方法,保存了别名和类型处理注册中心:
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
剩下的就是设置一些属性了:
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。