我们知道的是mybatis的主配置文件是mybatis-config.xml,而在mybatis-config.xml 里关联了许多具体的sqlMapper.xml文件.
2.什么是我们想知道的:
我们想知道的是这些配置文件是怎么在启动的时候被读取并被工厂类消化的。
3. 怎么办?debug!
接下来我通过一些错误信息找到了一些有用的类信息,并且打上了断点,查找到了方法堆栈,打印如下
XMLMapperBuilder.parse() line: 87
XMLConfigBuilder.mapperElement(XNode) line: 317
XMLConfigBuilder.parseConfiguration(XNode) line: 104
XMLConfigBuilder.parse() line: 89
SqlSessionFactoryBean.buildSqlSessionFactory() line: 424
SqlSessionFactoryBean.afterPropertiesSet() line: 336
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).invokeInitMethods(String, Object, RootBeanDefinition) line: 1541
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).initializeBean(String, Object, RootBeanDefinition) line: 1479
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 521
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 458
AbstractBeanFactory$1.getObject() line: 295
DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory) line: 223
DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class<T>, Object[], boolean) line: 292
DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 194
DefaultListableBeanFactory.preInstantiateSingletons() line: 608
ClassPathXmlApplicationContext(AbstractApplicationContext).finishBeanFactoryInitialization(ConfigurableListableBeanFactory) line: 932
ClassPathXmlApplicationContext(AbstractApplicationContext).refresh() line: 479
ClassPathXmlApplicationContext.<init>(String[], boolean, ApplicationContext) line: 139
ClassPathXmlApplicationContext.<init>(String) line: 83
ActivityTest.init() line: 20
SqlSessionFactoryBean.java 是一个神奇的类 在mybatis启动中扮演了重要的角色
下面我将对这段代码进行单补调试,并在我能力范围之内给出一些说明,如有错误和不足望指教:
/**
* Build a {@code SqlSessionFactory} instance.
*
* The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
* {@code SqlSessionFactory} instance based on an Reader.
*
* @return SqlSessionFactory
* @throws IOException if loading the config file failed
*/
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Registered plugin: '" + plugin + "'");
}
}
}
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
if (this.databaseIdProvider != null) {
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
①代码16行处
mybatis-config.xml经过xmlConfigBuilder生成了configuration.(里面用到了parser模块,好像有xml配置文件的开源代码都有这么一个类似的模块)
具体过程:将文件流转成xml的document放入到一个新建的XPathParser对象中 ,再贮存在XMLConfigBuilder 对象中
流程如下
SqlSessionFactoryBean->XMLConfigBuilder->XPathParser->@return Document builder.parse(inputSource){
com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl }
SqlSessionFactoryBean的xmlConfigBuilder对应此xmlConfigBuilder
经过这一波configuration的parser变量会指向XPathParser ,XPathParser 的document就贮存了上面返回的document。
XPathParser.createDocument(InputSource) line: 251
XPathParser.<init>(InputStream, boolean, Properties, EntityResolver) line: 122
XMLConfigBuilder.<init>(InputStream, String, Properties) line: 72
SqlSessionFactoryBean.buildSqlSessionFactory() line: 354
SqlSessionFactoryBean.afterPropertiesSet() line: 336
具体代码如下
XPathParser.java
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
②代码17行处
SqlSessionFactoryBean的configuration对应此xmlConfigBuilder的configuration
③代码34行(可以略过)
处默认的${mybatis.alias.basepackage}基础package
④86行
xmlConfigBuilder.parse();
-> parseConfiguration(parser.evalNode("/configuration")); in XMLConfigBuilder.java @89
XMLConfigBuilder.parseConfiguration(XNode) line: 104 解析了configuration节点下
mappers底下的各个mapper
config/mapper/UserMapper.xml
这里会使用javax.xml.xpath.XPath;来解析各个节点 类似于 /foo/bar/@id 对应于 <foo><bar id="barId"/></foo>的id属性
这里对我来说是一个新的知识点,通过javax.xml.xpath.XPath 来找到对应的xml dom 的节点/foo/bar/@id指的就是
parseConfiguration 对<configuration>xml节点进行解析
<configuration>
<typeAliases>
<typeAlias alias="User" type="cola.machine.calendar.user.bean.User"/>
<typeAlias alias="Activity" type="cola.machine.calendar.activity.bean.Activity"/>
</typeAliases>
<mappers>
<mapper resource="config/mapper/UserMapper.xml"/>
<mapper resource="config/mapper/ActivityMapper.xml"/>
</mappers>
</configuration>
下面是XmlConfigBuilder里的一段代码
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
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);
}
}
⑤120行
针对<mapper>元素会调用XmlConfigBuilder 的mapperElement方法
这一处代码是解析resource->inputStream->InputSource
此之前 configuration中会有含有由mybatis-config.xml解析成的document
⑥86行 xmlConfigBuilder.parse();会对configuration里的document进行解析
parseConfiguration(parser.evalNode("/configuration"));
这里涉及的configuration是mybatis的一大核心。
从mybatis主页上的flow图来看configration对应了mybatis主要的xml配置文件。 configuration的loadResources是一个hashset 里面存储了所有的xml配置文件地址。
XMLMapperBuilder 是解析sqlMapper的主要类.MapperBuilderAssistant
代码1处会去判断改资源是否加载过 resource 是String 类型的uri资源地址, 比如“config/mapper/UserMapper.xml”。
SqlSessionFactoryBean是第一个觉得是处理mybatis-config.xml的地方
SqlSessionFactoryBean.afterPropertiesSet() line: 336
this.sqlSessionFactory = buildSqlSessionFactory();
SqlSessionFactoryBean包含了
private Resource configLocation变量 实际值为 class path resource [config/xml/mybatis-config.xml]
.buildSqlSessionFactory() 是第一次加载
他会对mybatis-config.xml进行解析 对配置的sqlMapper.xml
昨天因为几个错误去debug了下mybatis的代码,有些心得。
首先,因为mybatis和spring 进行了整合,用到了spring mybatis的整合jar包,让spring管理了mybatis的类的生命周期。
ClassPathXmlApplicationContext 首先会对applicationContext进行解析 事先装载所需要的类
DefaultListableBeanFactory
public void parse() {
if (!configuration.isResourceLoaded(resource)) {//1
configurationElement(parser.evalNode("/mapper"));//2
configuration.addLoadedResource(resource);//3
bindMapperForNamespace();//4
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}