一. 流程
二. SqlSessionFactoryBuilder中的build方法是如何创建出SqlsessionSactory
传入一个配置文件的文件输入流
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
创建SqlSessionFactory 的过程重点在于XMLConfigBuilder的创建与parser.parse()的执行。new XMLConfigBuilder的过程解析出一个document对象放入XPathParser然后XPathParser会被放入XMLConfigBuilder对象。
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对象的创建
调用此构造器
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
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;
}
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();
}
重点来看document这个对象的创建
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
这是调用抽象类的静态方法实例出DocumentBuilderFactory对象
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() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
得到这个DocumentBuilder调用这个parse创建一个document对象
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
以上是创建XMLConfigBuilder对象的过程。下面是执行
return build(parser.parse());的过程。parser.parse()执行完会生成一个configuration对象。执行完后会生成一个SqlSessionFactory对象。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
这个方法便是对xml文件中内容的详细解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
以下下这些方法是创建SqlsessionFacory对象的关键。我们来一个一个进行阅读
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
此标签 原来将一些被其他地方引用的变量存储在这。别的地方使用时用${}
例如:
```xml
<configuration>
<properties>
<property name="jdbc.username" value="test" />
<property name="jdbc.password" value="test" />
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
</configuration>
propertiesElement(root.evalNode("properties"));
用来存储一些全局的变量 <!-- 全局启用或禁用【延迟加载】。当禁用时,所有关联对象都会即时加载 -->
<setting name="lazyLoadingEnabled" value="true" />
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
别名映射
typeAliasesElement(root.evalNode("typeAliases"));
插件,如分页插件等
pluginElement(root.evalNode("plugins"));
当我们获取到结果集时,mybatis每次都会生成一个objectFactory,一般为默认
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"));
配置mapper接口所在位置。
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
如果事以这种方式配置,则接口和xml的名字要相同。
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
src目录下
String resource = child.getStringAttribute("resource");
如果是在电脑或者服务器其他位置,这里可以写绝对路径
String url = child.getStringAttribute("url");
注解方式
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} 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.");
}
}
}
}
}
研究以下为什么配置package的方式必须要保证接口名字和xml的相同
packageName xml文件中配置的包名,superType Object类
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
执行完这一步之后,接口会被全部加载进一个set集合
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
获取上一步加载的接口集合
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
new ResolverUtil.IsA(superType)
IsA是ResolverUtil类中的一个静态内部类。也是Test的一个子类。
实例化了一个将Object.class传进去的Test子类。
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
看这里的find方法
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;
}
addIfMatching(test, child);
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);
这个类不为空且是实例话ISA的时候穿进去的那个类的一个子类
isAssignableFrom()方法是从类继承的角度去判断,instanceof关键字是从实例继承的角度去判断。
isAssignableFrom()方法是判断是否为某个类的父类,instanceof关键字是判断是否某个类的子类。
if (test.matches(type)) {
// 注意此处的类型 private Set<Class<? extends T>> matches = new HashSet<Class<? extends T>>();
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());
}
}
addMapper(mapperClass);
```java
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
判断这个MapperRegistry类中的集合Map<Class<?>, MapperProxyFactory<?>> knownMappers是否已经包含了这个接口
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
将这个接口放入
knownMappers.put(type, new MapperProxyFactory<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
加载xml文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
注意此处xml文件的加载路径为类路径加.xml
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
重点来看parseStatement(Method method)是对接口和xml文件的关联
这个方法是对注解上的sql进行解析
结束后将会把所有的sql信息存进assistant中
三. SqlsessionFactory创建Sqlsession的过程
四.