首先先道歉一下, 我之前并没有研究太深入,然后发现有些东西写错了。然后7月7 号,忙到夜里2点改这篇文章。改完之后太累了就关了电脑。。。然后没保存发布,又重写了一遍。
第二篇的地址
Mybatis 的两个入口一个是SqlSessionFactoryBean, 另外一个是MapperScannerConfigurer。这个可以在spring.xml 配置中找到。SqlSessionFactoryBean实现了InitializingBean接口,InitializingBean接口必须得实现afterpropertiesset()方法,此方法将配置文件中的各种属性列如 mapperlocation 、typealias、plugins等等放入configuration中,configuration就相当于mybatis的大管家, 各种解析封装后的属性都会放到这里。 然后您会看到
else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
xmlConfigBuilder这个东西是解析xml 的一个类
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
这里就是装配配置文件
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
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();
}
}
继续往下运行的时候会发现
xmlConfigBuilder.parse();
这个xml 里面的最外层标签是configuration;所以下面最先解析的是configuration
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(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标签的话就开始解析了。然后我们主讲一下mappers 标签。
mapperElement(root.evalNode("mappers"));
这句话就是解析mappers的子元素了
如果子元素是package 那么调用configuration的addMappers()将包路径扔给configuration.
如果mappers标签的子元素标签不是package 。那么得到他子元素3个属性的值,分别是resource、url、class 。三者只能同时存在一个。同时存在多以一个则报异常。因为configuration.addMappers()方法最终也是调到XMLMapperBuilder.
configuration.addMappers() 里面是调用mapperRegistry.addMappers()。
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 {
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.");
}
}
}
}
}
我对于mapperRegistry是哪里来的 很感兴趣。于是我看到了下面代码所示
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
这里的this 代表的就是Configuration这个类。
进入mapperRegistry.addMappers() 方法会进入下图。 先调用下面代码中最后一个方法, 再调用上一个方法。
public void addMappers(String packageName, Class> superType) {
ResolverUtil> resolverUtil = new ResolverUtil>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set>> mapperSet = resolverUtil.getClasses();
for (Class> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
/**
* @since 3.2.2
*/
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
这边再道个歉。 先不上图了,本来有图的 我忘了保存。 我就嘴说说吧
这边的mapperSet 呢解析出来是什么格式呢:
[interface com.fuchanghai.mybatis.mapper.DailyMapper, interface com.fuchanghai.mybatis.mapper.EmployeeMapper]
意思就是interface+全路径。 很有意思的是:比如我这样配置的
那么只会解析com.fuchanghai.mybatis.mapper下的。 如果你的xml放在com.fuchanghai.mybatis.mapper.test。这个包下, 那么是不会在mapperSet中的。注意看图下的size.
我们继续看调到addMapper()方法:
public void addMapper(Class 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));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
hasMapper(type)方法调用的是什么呢?看下面代码
public boolean hasMapper(Class type) {
return knownMappers.containsKey(type);
}
意思就是看看knownMappers 里面有没有,即是缓存里面存在不存在。knownMappers 这个属性很重要 再之后的代码中会出现正到。
如果没有的话就存到knownMappers 中
这个 newMapperProxyFactory()重中之重 干了什么 呢? 没错动态代理, 掉个胃口,下次再说。
然后要看MapperAnnotationBuild这个方法了。
type传进去只为了再后面拼接一个.java 然后赋值给resource属性
我们主要看parse.parse();
下面代码 中主要的是loadXmlResource(), 和parseStatement(method);
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
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();
}
loadXmlResource()就是解析xml 文件了 最终调用了xmlMapperBuilder。 这里的!configuration.isResourceLoaded("namespace:" + type.getName())也很有深意的。各位试试debug 看看。
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())) {
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();
}
}
}
这个是运用反射渠道对应的接口类放到大管家里面(configuration):
Class> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
这边就是解析mapper标签。继续点进去是这样的:
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
private void buildStatementFromContext(List list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
上图一定会进入
buildStatementFromContext(List list)
这个方法是得到 select|updata等等东西的contant。
然后进
buildStatementFromContext(List list, String requiredDatabaseId)
requiredDatabaseId这个配置的是musql 或者oracle 判断是哪个数据库源
statementParser.parseStatementNode();
这个里面进去找找到langDriver.createSqlSource 然后继续找builder.parseScriptNode(), 进去继续找 List
至于为什么 mapper接口 明明是一个接口怎么 能进行动态代理这个问提。 我不想一步步跟了,简单的说一下就是在mapperlocation那边进去找到MapperRegistry。
最后可以看到
public