作者:小傅哥 博客:https:bugstack。cn包含:Java基础,面经手册,Netty4。x,手写Spring,用Java实现JVM,重学Java设计模式,SpringBoot中间件开发,IDEA插件开发,DDD系统架构项目开发,字节码编程。。。 源码:https:github。comfuzhengweiitstackdemocodemybatis 沉淀、分享、成长,让自己和他人都能有所收获!一、前言介绍 MyBatis是一款非常优秀的持久层框架,相对于IBatis更是精进了不少。与此同时它还提供了很多的扩展点,比如最常用的插件;语言驱动器,执行器,对象工厂,对象包装器工厂等等都可以扩展。那么,如果想成为一个有深度的男人(程序猿),还是应该好好的学习一下这款开源框架的源码,以此可以更好的领会设计模式的精髓(面试?)。其实可能平常的业务开发中,并不会去深究各个框架的源代码,也常常会听到即使不会也可以开发代码。但!每个人的目标不同,就像;代码写的好工资加的少(没有bug怎么看出你工作嘞!),好!为了改变世界,开始分析喽! 在分析之前先出一个题,看看你适合看源码不;Testpublicvoidtest(){BbnewB();b。scan();我的输出结果是什么?}staticclassA{publicvoidscan(){doScan();}protectedvoiddoScan(){System。out。println(A。doScan);}}staticclassBextendsA{OverrideprotectedvoiddoScan(){System。out。println(B。doScan);}} 其实无论你的答案对错,都不影响你对源码的分析。只不过,往往在一些框架中会有很多的设计模式和开发技巧,如果上面的代码在你平时的开发中几乎没用过,那么可能你暂时更多的还是开发着CRUD的功能(莫慌,我还写过PHP呢)。 接下来先分析Mybatis单独使用时的源码执行过程,再分析MybatisSpring整合源码,好!开始。二、案例工程 为了更好的分析,我们创建一个Mybatis的案例工程,其中包括;Mybatis单独使用、MybatisSpring整合使用itstackdemomybatissrcmainjavaorg。itstack。demodaoISchool。javaIUserDao。javainterfacesSchool。javaUser。javaresourcesmapperSchoolMapper。xmlUserMapper。xmlpropsjdbc。propertiesspringmybatisconfigdatasource。xmlspringconfigdatasource。xmllogback。xmlmybatisconfig。xmlspringconfig。xmlwebappWEBINFtestjavaorg。itstack。demo。testMybatisApiTest。javaSpringApiTest。java三、环境配置JDK1。8IDEA2019。3。1mybatis3。4。6{不同版本源码略有差异和bug修复}mybatisspring1。3。2{以下源码分析会说代码行号,注意不同版本可能会有差异}四、(mybatis)源码分析dependencygroupIdorg。mybatisgroupIdmybatisartifactIdversion3。4。6versiondependency Mybatis的整个源码还是很大的,以下主要将部分核心内容进行整理分析,以便于后续分析Mybatis与Spring整合的源码部分。简要包括;容器初始化、配置文件解析、Mapper加载与动态代理。1。从一个简单的案例开始 要学习Mybatis源码,最好的方式一定是从一个简单的点进入,而不是从Spring整合开始分析。SqlSessionFactory是整个Mybatis的核心实例对象,SqlSessionFactory对象的实例又通过SqlSessionFactoryBuilder对象来获得。SqlSessionFactoryBuilder对象可以从XML配置文件加载配置信息,然后创建SqlSessionFactory。如下例子: MybatisApiTest。javapublicclassMybatisApiTest{TestpublicvoidtestqueryUserInfoById(){Stringresourcespringmybatisconfigdatasource。Rtry{readerResources。getResourceAsReader(resource);SqlSessionFactorysqlMappernewSqlSessionFactoryBuilder()。build(reader);SqlSessionsessionsqlMapper。openSession();try{Userusersession。selectOne(org。itstack。demo。dao。IUserDao。queryUserInfoById,1L);System。out。println(JSON。toJSONString(user));}finally{session。close();reader。close();}}catch(IOExceptione){e。printStackTrace();}}} daoIUserDao。javapublicinterfaceIUserDao{UserqueryUserInfoById(Longid);} springmybatisconfigdatasource。?xmlversion1。0encodingUTF8?!DOCTYPEconfigurationPUBLICmybatis。orgDTDConfig3。0ENhttp:mybatis。orgdtdmybatis3config。dtdconfigurationenvironmentsdefaultdevelopmentenvironmentiddevelopmenttransactionManagertypeJDBCdataSourcetypePOOLEDpropertynamedrivervaluecom。mysql。jdbc。Driverpropertynameurlvaluejdbc:mysql:127。0。0。1:3306itstack?useUnicodetruepropertynameusernamevaluerootpropertynamepasswordvalue123456dataSourceenvironmentenvironmentsmappersmapperresourcemapperUserMapper。xmlmappersconfiguration 如果一切顺利,那么会有如下结果:{age:18,createTime:1571376957000,id:1,name:花花,updateTime:1571376957000} 从上面的代码块可以看到,核心代码;SqlSessionFactoryBuilder()。build(reader),负责Mybatis配置文件的加载、解析、构建等职责,直到最终可以通过SqlSession来执行并返回结果。2。容器初始化 从上面代码可以看到,SqlSessionFactory是通过SqlSessionFactoryBuilder工厂类创建的,而不是直接使用构造器。容器的配置文件加载和初始化流程如下: 微信公众号:bugstack虫洞栈初始化流程流程核心类SqlSessionFactoryBuilderXMLConfigBuilderXPathParserConfiguration SqlSessionFactoryBuilder。javapublicclassSqlSessionFactoryBuilder{publicSqlSessionFactorybuild(Readerreader){returnbuild(reader,null,null);}publicSqlSessionFactorybuild(Readerreader,Stringenvironment){returnbuild(reader,environment,null);}publicSqlSessionFactorybuild(Readerreader,Propertiesproperties){returnbuild(reader,null,properties);}publicSqlSessionFactorybuild(Readerreader,Stringenvironment,Propertiesproperties){try{XMLConfigBuilderparsernewXMLConfigBuilder(reader,environment,properties);returnbuild(parser。parse());}catch(Exceptione){throwExceptionFactory。wrapException(ErrorbuildingSqlSession。,e);}finally{ErrorContext。instance()。reset();try{reader。close();}catch(IOExceptione){Intentionallyignore。Preferpreviouserror。}}}publicSqlSessionFactorybuild(InputStreaminputStream){returnbuild(inputStream,null,null);}publicSqlSessionFactorybuild(InputStreaminputStream,Stringenvironment){returnbuild(inputStream,environment,null);}publicSqlSessionFactorybuild(InputStreaminputStream,Propertiesproperties){returnbuild(inputStream,null,properties);}publicSqlSessionFactorybuild(InputStreaminputStream,Stringenvironment,Propertiesproperties){try{XMLConfigBuilderparsernewXMLConfigBuilder(inputStream,environment,properties);returnbuild(parser。parse());}catch(Exceptione){throwExceptionFactory。wrapException(ErrorbuildingSqlSession。,e);}finally{ErrorContext。instance()。reset();try{inputStream。close();}catch(IOExceptione){Intentionallyignore。Preferpreviouserror。}}}publicSqlSessionFactorybuild(Configurationconfig){returnnewDefaultSqlSessionFactory(config);}} 从上面的源码可以看到,SqlSessionFactory提供三种方式build构建对象;字节流:java。io。InputStream字符流:java。io。Reader配置类:org。apache。ibatis。session。Configuration 那么,字节流、字符流都会创建配置文件解析类:XMLConfigBuilder,并通过parser。parse()生成Configuration,最后调用配置类构建方法生成SqlSessionFactory。 XMLConfigBuilder。javapublicclassXMLConfigBuilderextendsBaseBuilder{privatefinalXPathPprivateSprivatefinalReflectorFactorylocalReflectorFactorynewDefaultReflectorFactory();。。。publicXMLConfigBuilder(Readerreader,Stringenvironment,Propertiesprops){this(newXPathParser(reader,true,props,newXMLMapperEntityResolver()),environment,props);}。。。}XMLConfigBuilder对于XML文件的加载和解析都委托于XPathParser,最终使用JDK自带的javax。xml进行XML解析(XPath)XPathParser(Readerreader,booleanvalidation,Propertiesvariables,EntityResolverentityResolver)reader:使用字符流创建新的输入源,用于对XML文件的读取validation:是否进行DTD校验variables:属性配置信息entityResolver:Mybatis硬编码了newXMLMapperEntityResolver()提供XML默认解析器 XMLMapperEntityResolver。javapublicclassXMLMapperEntityResolverimplementsEntityResolver{privatestaticfinalStringIBATISCONFIGSYSTEMibatis3config。privatestaticfinalStringIBATISMAPPERSYSTEMibatis3mapper。privatestaticfinalStringMYBATISCONFIGSYSTEMmybatis3config。privatestaticfinalStringMYBATISMAPPERSYSTEMmybatis3mapper。privatestaticfinalStringMYBATISCONFIGDTDorgapacheibatisbuilderxmlmybatis3config。privatestaticfinalStringMYBATISMAPPERDTDorgapacheibatisbuilderxmlmybatis3mapper。ConvertsapublicDTDintoalocaloneparampublicIdThepublicidthatiswhatcomesafterPUBLICparamsystemIdThesystemidthatiswhatcomesafterthepublicid。returnTheInputSourcefortheDTDthrowsorg。xml。sax。SAXExceptionIfanythinggoeswrongOverridepublicInputSourceresolveEntity(StringpublicId,StringsystemId)throwsSAXException{try{if(systemId!null){StringlowerCaseSystemIdsystemId。toLowerCase(Locale。ENGLISH);if(lowerCaseSystemId。contains(MYBATISCONFIGSYSTEM)lowerCaseSystemId。contains(IBATISCONFIGSYSTEM)){returngetInputSource(MYBATISCONFIGDTD,publicId,systemId);}elseif(lowerCaseSystemId。contains(MYBATISMAPPERSYSTEM)lowerCaseSystemId。contains(IBATISMAPPERSYSTEM)){returngetInputSource(MYBATISMAPPERDTD,publicId,systemId);}}}catch(Exceptione){thrownewSAXException(e。toString());}}privateInputSourcegetInputSource(Stringpath,StringpublicId,StringsystemId){InputSif(path!null){try{InputStreaminResources。getResourceAsStream(path);sourcenewInputSource(in);source。setPublicId(publicId);source。setSystemId(systemId);}catch(IOExceptione){ignore,nullisok}}}}Mybatis依赖于dtd文件进行进行解析,其中的ibatis3config。dtd主要是用于兼容用途getInputSource(Stringpath,StringpublicId,StringsystemId)的调用里面有两个参数publicId(公共标识符)和systemId(系统标示符) XPathParser。javapublicXPathParser(Readerreader,booleanvalidation,Propertiesvariables,EntityResolverentityResolver){commonConstructor(validation,variables,entityResolver);this。documentcreateDocument(newInputSource(reader));}privatevoidcommonConstructor(booleanvalidation,Propertiesvariables,EntityResolverentityResolver){this。this。entityResolverentityRthis。XPathFactoryfactoryXPathFactory。newInstance();this。xpathfactory。newXPath();}privateDocumentcreateDocument(InputSourceinputSource){important:thismustonlybecalledAFTERcommonconstructortry{DocumentBuilderFactoryfactoryDocumentBuilderFactory。newInstance();factory。setValidating(validation);factory。setNamespaceAware(false);factory。setIgnoringComments(true);factory。setIgnoringElementContentWhitespace(false);factory。setCoalescing(false);factory。setExpandEntityReferences(true);DocumentBuilderbuilderfactory。newDocumentBuilder();builder。setEntityResolver(entityResolver);builder。setErrorHandler(newErrorHandler(){Overridepublicvoiderror(SAXParseExceptionexception)throwsSAXException{}OverridepublicvoidfatalError(SAXParseExceptionexception)throwsSAXException{}Overridepublicvoidwarning(SAXParseExceptionexception)throwsSAXException{}});returnbuilder。parse(inputSource);}catch(Exceptione){thrownewBuilderException(Errorcreatingdocumentinstance。Cause:e,e);}}从上到下可以看到主要是为了创建一个Mybatis的文档解析器,最后根据builder。parse(inputSource)返回Document得到XPathParser实例后,接下来在调用方法:this(newXPathParser(reader,true,props,newXMLMapperEntityResolver()),environment,props);XMLConfigBuilder。this(newXPathParser(reader,true,props,newXMLMapperEntityResolver()),environment,props); privateXMLConfigBuilder(XPathParserparser,Stringenvironment,Propertiesprops){ super(newConfiguration()); ErrorContext。instance()。resource(SQLMapperConfiguration); this。configuration。setVariables(props); this。 this。 this。 }其中调用了父类的构造函数publicabstractclassBaseBuilder{ protectedfinalC protectedfinalTypeAliasRegistrytypeAliasR protectedfinalTypeHandlerRegistrytypeHandlerR publicBaseBuilder(Configurationconfiguration){ this。 this。typeAliasRegistrythis。configuration。getTypeAliasRegistry(); this。typeHandlerRegistrythis。configuration。getTypeHandlerRegistry(); } }XMLConfigBuilder创建完成后,sqlSessionFactoryBuild调用parser。parse()创建ConfigurationpublicclassXMLConfigBuilderextendsBaseBuilder{ publicConfigurationparse(){ if(parsed){ thrownewBuilderException(EachXMLConfigBuildercanonlybeusedonce。); } parseConfiguration(parser。evalNode(configuration)); } }3。配置文件解析 这一部分是整个XML文件解析和装载的核心内容,其中包括;属性解析propertiesElement加载settings节点settingsAsProperties载自定义VFSloadCustomVfs解析类型别名typeAliasesElement加载插件pluginElement加载对象工厂objectFactoryElement创建对象包装器工厂objectWrapperFactoryElement加载反射工厂reflectorFactoryElement元素设置settingsElement加载环境配置environmentsElement数据库厂商标识加载databaseIdProviderElement加载类型处理器typeHandlerElement(核心)加载mapper文件mapperElementparseConfiguration(parser。evalNode(configuration));privatevoidparseConfiguration(XNoderoot){try{issue117readpropertiesfirst属性解析propertiesElementpropertiesElement(root。evalNode(properties));加载settings节点settingsAsPropertiesPropertiessettingssettingsAsProperties(root。evalNode(settings));加载自定义VFSloadCustomVfsloadCustomVfs(settings);解析类型别名typeAliasesElementtypeAliasesElement(root。evalNode(typeAliases));加载插件pluginElementpluginElement(root。evalNode(plugins));加载对象工厂objectFactoryElementobjectFactoryElement(root。evalNode(objectFactory));创建对象包装器工厂objectWrapperFactoryElementobjectWrapperFactoryElement(root。evalNode(objectWrapperFactory));加载反射工厂reflectorFactoryElementreflectorFactoryElement(root。evalNode(reflectorFactory));元素设置settingsElement(settings);readitafterobjectFactoryandobjectWrapperFactoryissue631加载环境配置environmentsElementenvironmentsElement(root。evalNode(environments));数据库厂商标识加载databaseIdProviderElementdatabaseIdProviderElement(root。evalNode(databaseIdProvider));加载类型处理器typeHandlerElementtypeHandlerElement(root。evalNode(typeHandlers));加载mapper文件mapperElementmapperElement(root。evalNode(mappers));}catch(Exceptione){thrownewBuilderException(ErrorparsingSQLMapperConfiguration。Cause:e,e);}} 所有的root。evalNode()底层都是调用XMLDOM方法:Objectevaluate(Stringexpression,Objectitem,QNamereturnType),表达式参数expression,通过XObjectresultObjecteval(expression,item)返回最终节点内容,可以参考http:mybatis。orgdtdmybatis3config。dtd,如下;!ELEMENTconfiguration(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)!ELEMENTdatabaseIdProvider(property)!ATTLISTdatabaseIdProvidertypeCDATAREQUIRED!ELEMENTproperties(property)!ATTLISTpropertiesresourceCDATAIMPLIEDurlCDATAIMPLIED!ELEMENTpropertyEMPTY!ATTLISTpropertynameCDATAREQUIREDvalueCDATAREQUIRED!ELEMENTsettings(setting)!ELEMENTsettingEMPTY!ATTLISTsettingnameCDATAREQUIREDvalueCDATAREQUIRED!ELEMENTtypeAliases(typeAlias,package)!ELEMENTtypeAliasEMPTY!ATTLISTtypeAliastypeCDATAREQUIREDaliasCDATAIMPLIED!ELEMENTtypeHandlers(typeHandler,package)!ELEMENTtypeHandlerEMPTY!ATTLISTtypeHandlerjavaTypeCDATAIMPLIEDjdbcTypeCDATAIMPLIEDhandlerCDATAREQUIRED!ELEMENTobjectFactory(property)!ATTLISTobjectFactorytypeCDATAREQUIRED!ELEMENTobjectWrapperFactoryEMPTY!ATTLISTobjectWrapperFactorytypeCDATAREQUIRED!ELEMENTreflectorFactoryEMPTY!ATTLISTreflectorFactorytypeCDATAREQUIRED!ELEMENTplugins(plugin)!ELEMENTplugin(property)!ATTLISTplugininterceptorCDATAREQUIRED!ELEMENTenvironments(environment)!ATTLISTenvironmentsdefaultCDATAREQUIRED!ELEMENTenvironment(transactionManager,dataSource)!ATTLISTenvironmentidCDATAREQUIRED!ELEMENTtransactionManager(property)!ATTLISTtransactionManagertypeCDATAREQUIRED!ELEMENTdataSource(property)!ATTLISTdataSourcetypeCDATAREQUIRED!ELEMENTmappers(mapper,package)!ELEMENTmapperEMPTY!ATTLISTmapperresourceCDATAIMPLIEDurlCDATAIMPLIEDclassCDATAIMPLIED!ELEMENTpackageEMPTY!ATTLISTpackagenameCDATAREQUIRED mybatis3config。dtd定义文件中有11个配置文件,如下;properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers? 以上每个配置都是可选。最终配置内容会保存到org。apache。ibatis。session。Configuration,如下;publicclassConfiguration{protectedE允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。默认为falseprotectedbooleansafeRowBoundsE允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false。protectedbooleansafeResultHandlerE是否开启自动驼峰命名规则(camelcase)映射,即从经典数据库列名ACOLUMN到经典Java属性名aColumn的类似映射。默认falseprotectedbooleanmapUnderscoreToCamelC当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。默认值false(truein3。4。1)protectedbooleanaggressiveLazyL是否允许单一语句返回多结果集(需要兼容驱动)。protectedbooleanmultipleResultSetsE允许JDBC支持自动生成主键,需要驱动兼容。这就是insert时获取mysql自增主键oraclesequence的开关。注:一般来说,这是希望的结果,应该默认值为true比较合适。protectedbooleanuseGeneratedK使用列标签代替列名,一般来说,这是希望的结果protectedbooleanuseColumnL是否启用缓存{默认是开启的,可能这也是你的面试题}protectedbooleancacheE指定当结果集中值为null的时候是否调用映射对象的setter(map对象时为put)方法,这对于有Map。keySet()依赖或null值初始化的时候是有用的。protectedbooleancallSettersOnN允许使用方法签名中的名称作为语句参数名称。为了使用该特性,你的工程必须采用Java8编译,并且加上parameters选项。(从3。4。1开始)protectedbooleanuseActualParamN当返回行的所有列都是空时,MyBatis默认返回null。当开启这个设置时,MyBatis会返回一个空实例。请注意,它也适用于嵌套的结果集(i。e。collectioinandassociation)。(从3。4。2开始)注:这里应该拆分为两个参数比较合适,一个用于结果集,一个用于单记录。通常来说,我们会希望结果集不是null,单记录仍然是nullprotectedbooleanreturnInstanceForEmptyR指定MyBatis增加到ahrefhttps:www。bs178。comrizhitargetblankclassinfotextkey日志a名称的前缀。protectedStringlogP指定MyBatis所用ahrefhttps:www。bs178。comrizhitargetblankclassinfotextkey日志a的具体实现,未指定时将自动查找。一般建议指定为slf4j或log4jprotectedC?extendsLoglogI指定VFS的实现,VFS是mybatis提供的用于访问AS内资源的一个简便接口protectedC?extendsVFSvfsIMyBatis利用本地缓存机制(LocalCache)防止循环引用(circularreferences)和加速重复嵌套查询。默认值为SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为STATEMENT,本地会话仅用在语句执行上,对相同SqlSession的不同调用将不会共享数据。protectedLocalCacheScopelocalCacheScopeLocalCacheScope。SESSION;当没有为参数提供特定的JDBC类型时,为空值指定JDBC类型。某些驱动需要指定列的JDBC类型,多数情况直接用一般类型即可,比如NULL、VARCHAR或OTHER。protectedJdbcTypejdbcTypeForNullJdbcType。OTHER;指定对象的哪个方法触发一次延迟加载。protectedSetStringlazyLoadTriggerMethodsnewHashSetString(Arrays。asList(newString〔〕{equals,clone,hashCode,toString}));设置超时时间,它决定驱动等待数据库响应的秒数。默认不超时protectedIntegerdefaultStatementT为驱动的结果集设置默认获取数量。protectedIntegerdefaultFetchSSIMPLE就是普通的执行器;REUSE执行器会重用预处理语句(preparedstatements);BATCH执行器将重用语句并执行批量更新。protectedExecutorTypedefaultExecutorTypeExecutorType。SIMPLE;指定MyBatis应如何自动映射列到字段或属性。NONE表示取消自动映射;PARTIAL只会自动映射没有定义嵌套结果集映射的结果集。FULL会自动映射任意复杂的结果集(无论是否嵌套)。protectedAutoMappingBehaviorautoMappingBehaviorAutoMappingBehavior。PARTIAL;指定发现自动映射目标未知列(或者未知属性类型)的行为。这个值应该设置为WARNING比较合适protectedAutoMappingUnknownColumnBehaviorautoMappingUnknownColumnBehaviorAutoMappingUnknownColumnBehavior。NONE;settings下的properties属性protectedPropertiesvariablesnewProperties();默认的反射器工厂,用于操作属性、构造器方便protectedReflectorFactoryreflectorFactorynewDefaultReflectorFactory();对象工厂,所有的类resultMap类都需要依赖于对象工厂来实例化protectedObjectFactoryobjectFactorynewDefaultObjectFactory();对象包装器工厂,主要用来在创建非原生对象,比如增加了某些监控或者特殊属性的代理类protectedObjectWrapperFactoryobjectWrapperFactorynewDefaultObjectWrapperFactory();延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。protectedbooleanlazyLoadingE指定Mybatis创建具有延迟加载能力的对象所用到的代理工具。MyBatis3。3使用JAVASSISTprotectedProxyFactoryproxyFactorynewJavassistProxyFactory();224UsinginternalJavassistinsteadofOGNLMyBatis可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的databaseId属性。protectedStringdatabaseId;。。。} 以上可以看到,Mybatis把所有的配置;resultMap、Sql语句、插件、缓存等都维护在Configuration中。这里还有一个小技巧,在Configuration还有一个StrictMap内部类,它继承于HashMap完善了put时防重、get时取不到值的异常处理,如下;protectedstaticclassStrictMapVextendsHashMapString,V{privatestaticfinallongserialVersionUID4950446264854982944L;privatefinalSpublicStrictMap(Stringname,intinitialCapacity,floatloadFactor){super(initialCapacity,loadFactor);this。}publicStrictMap(Stringname,intinitialCapacity){super(initialCapacity);this。}publicStrictMap(Stringname){super();this。}publicStrictMap(Stringname,MapString,?extendsVm){super(m);this。}} (核心)加载mapper文件mapperElement Mapper文件处理是Mybatis框架的核心服务,所有的SQL语句都编写在Mapper中,这块也是我们分析的重点,其他模块可以后续讲解。 XMLConfigBuilder。parseConfiguration()mapperElement(root。evalNode(mappers));privatevoidmapperElement(XNodeparent)throwsException{if(parent!null){for(XNodechild:parent。getChildren()){如果要同时使用package自动扫描和通过mapper明确指定要加载的mapper,一定要确保package自动扫描的范围不包含明确指定的mapper,否则在通过package扫描的interface的时候,尝试加载对应xml文件的loadXmlResource()的逻辑中出现判重出错,报org。apache。ibatis。binding。BindingException异常,即使xml文件中包含的内容和mapper接口中包含的语句不重复也会出错,包括加载mapper接口时自动加载的xmlmapper也一样会出错。if(package。equals(child。getName())){StringmapperPackagechild。getStringAttribute(name);configuration。addMappers(mapperPackage);}else{Stringresourcechild。getStringAttribute(resource);Stringurlchild。getStringAttribute(url);StringmapperClasschild。getStringAttribute(class);if(resource!nullurlnullmapperClassnull){ErrorContext。instance()。resource(resource);InputStreaminputStreamResources。getResourceAsStream(resource);XMLMapperBuildermapperParsernewXMLMapperBuilder(inputStream,configuration,resource,configuration。getSqlFragments());mapperParser。parse();}elseif(resourcenullurl!nullmapperClassnull){ErrorContext。instance()。resource(url);InputStreaminputStreamResources。getUrlAsStream(url);XMLMapperBuildermapperParsernewXMLMapperBuilder(inputStream,configuration,url,configuration。getSqlFragments());mapperParser。parse();}elseif(resourcenullurlnullmapperClass!null){C?mapperInterfaceResources。classForName(mapperClass);configuration。addMapper(mapperInterface);}else{thrownewBuilderException(Amapperelementmayonlyspecifyaurl,resourceorclass,butnotmorethanone。);}}}}}Mybatis提供了两类配置Mapper的方法,第一类是使用package自动搜索的模式,这样指定package下所有接口都会被注册为mapper,也是在Spring中比较常用的方式,例如: packagenameorg。itstack。demo mappers另外一类是明确指定Mapper,这又可以通过resource、url或者class进行细分,例如; mapperUserMapper。xml mapperclass mappersliul4。Mapper加载与动态代理 通过package方式自动搜索加载,生成对应的mapper代理类,代码块和流程,如下;privatevoidmapperElement(XNodeparent)throwsException{if(parent!null){for(XNodechild:parent。getChildren()){if(package。equals(child。getName())){StringmapperPackagechild。getStringAttribute(name);configuration。addMappers(mapperPackage);}else{。。。}}}} 微信公众号:bugstack虫洞栈动态代理过程 Mapper加载到生成代理对象的流程中,主要的核心类包括;XMLConfigBuilderConfigurationMapperRegistryMapperAnnotationBuilderMapperProxyFactory MapperRegistry。java 解析加载MapperpublicvoidaddMappers(StringpackageName,C?superType){mybatis框架提供的搜索classpath下指定package以及子package中符合条件(注解或者继承于某个类接口)的类,默认使用Thread。currentThread()。getContextClassLoader()返回的加载器,和spring的工具类殊途同归。ResolverUtilC?resolverUtilnewResolverUtilC?();无条件的加载所有的类,因为调用方传递了Object。class作为父类,这也给以后的指定mapper接口预留了余地resolverUtil。find(newResolverUtil。IsA(superType),packageName);所有匹配的calss都被存储在ResolverUtil。matches字段中SetC?extendsC?mapperSetresolverUtil。getClasses();for(C?mapperClass:mapperSet){调用addMapper方法进行具体的mapper类接口解析addMapper(mapperClass);}} 生成代理类:MapperProxyFactorypublicTvoidaddMapper(ClassTtype){对于mybatismapper接口文件,必须是interface,不能是classif(type。isInterface()){if(hasMapper(type)){thrownewBindingException(TypetypeisalreadyknowntotheMapperRegistry。);}booleanloadCtry{为mapper接口创建一个MapperProxyFactory代理knownMappers。put(type,newMapperProxyFactoryT(type));Itsimportantthatthetypeisaddedbeforetheparserisrunotherwisethebindingmayautomaticallybeattemptedbythemapperparser。Ifthetypeisalreadyknown,itwonttry。MapperAnnotationBuilderparsernewMapperAnnotationBuilder(config,type);parser。parse();loadC}finally{if(!loadCompleted){knownMappers。remove(type);}}}} 在MapperRegistry中维护了接口类与代理工程的映射关系,knownMprivatefinalMapC?,MapperProxyF?knownMappersnewHashMapC?,MapperProxyF?(); MapperProxyFactory。javapublicclassMapperProxyFactoryT{privatefinalClassTmapperIprivatefinalMapMethod,MapperMethodmethodCachenewConcurrentHashMapMethod,MapperMethod();publicMapperProxyFactory(ClassTmapperInterface){this。mapperInterfacemapperI}publicClassTgetMapperInterface(){returnmapperI}publicMapMethod,MapperMethodgetMethodCache(){returnmethodC}SuppressWarnings(unchecked)protectedTnewInstance(MapperProxyTmapperProxy){return(T)Proxy。newProxyInstance(mapperInterface。getClassLoader(),newClass〔〕{mapperInterface},mapperProxy);}publicTnewInstance(SqlSessionsqlSession){finalMapperProxyTmapperProxynewMapperProxyT(sqlSession,mapperInterface,methodCache);returnnewInstance(mapperProxy);}} 如上是Mapper的代理类工程,构造函数中的mapperInterface就是对应的接口类,当实例化时候会获得具体的MapperProxy代理,里面主要包含了SqlSession。五、(mybatisspring)源码分析dependencygroupIdorg。mybatisgroupIdmybatisspringartifactIdversion1。3。2versiondependency 作为一款好用的ORM框架,一定是萝莉脸(单纯)、御姐心(强大),铺的了床(屏蔽与JDBC直接打交道)、暖的了房(速度性能好)!鉴于这些优点几乎在国内互联网大部分开发框架都会使用到Mybatis,尤其在一些需要高性能的场景下需要优化sql那么一定需要手写sql在xml中。那么,准备好了吗!开始分析分析它的源码;1。从一个简单的案例开始 与分析mybatis源码一样,先做一个简单的案例;定义dao、编写配置文件、junit单元测试; SpringApiTest。javaRunWith(SpringJUnit4ClassRunner。class)ContextConfiguration(classpath:springconfig。xml)publicclassSpringApiTest{privateLoggerloggerLoggerFactory。getLogger(SpringApiTest。class);ResourceprivateISchoolDaoschoolDResourceprivateIUserDaouserDTestpublicvoidtestqueryRuleTreeByTreeId(){SchoolruleTreeschoolDao。querySchoolInfoById(1L);logger。info(JSON。toJSONString(ruleTree));UseruseruserDao。queryUserInfoById(1L);logger。info(JSON。toJSONString(user));}} springconfigdatasource。?xmlversion1。0encodingUTF8?beansxmlnshttp:www。springframework。orgschemabeansxmlns:xsihttp:www。w3。org2001XMLSchemainstancexsi:schemaLocationhttp:www。springframework。orgschemabeanshttp:www。springframework。orgschemabeansspringbeans。xsd!1。数据库连接池:DriverManagerDataSource也可以使用DBCP2beaniddataSourceclassorg。springframework。jdbc。datasource。DriverManagerDataSourcepropertynamedriverClassNamevalue{db。jdbc。driverClassName}propertynameurlvalue{db。jdbc。url}propertynameusernamevalue{db。jdbc。username}propertynamepasswordvalue{db。jdbc。password}bean!2。配置SqlSessionFactory对象beanidsqlSessionFactoryclassorg。mybatis。spring。SqlSessionFactoryBean!注入数据库连接池propertynamedataSourcerefdataSource!配置MyBaties全局配置文件:mybatisconfig。xmlpropertynameconfigLocationvalueclasspath:mybatisconfig。xml!扫描entity包使用别名propertynametypeAliasesPackagevalueorg。itstack。demo。po!扫描sql配置文件:mapper需要的xml文件propertynamemapperLocationsvalueclasspath:mapper。xmlbean!3。配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中beanclassorg。mybatis。spring。mapper。MapperScannerConfigurer!注入sqlSessionFactorypropertynamesqlSessionFactoryBeanNamevaluesqlSessionFactory!给出需要扫描Dao接口包,多个逗号隔开propertynamebasePackagevalueorg。itstack。demo。daobeanbeans 如果一切顺利,那么会有如下结果:{address:北京市海淀区颐和园路5号,createTime:1571376957000,id:1,name:北京大学,updateTime:1571376957000}{age:18,createTime:1571376957000,id:1,name:花花,updateTime:1571376957000} 从上面单元测试的代码可以看到,两个没有方法体的注解就这么神奇的执行了我们的xml中的配置语句并输出了结果。其实主要得益于以下两个类;org。mybatis。spring。SqlSessionFactoryBeanorg。mybatis。spring。mapper。MapperScannerConfigurer2。扫描装配注册(MapperScannerConfigurer) MapperScannerConfigurer为整个Dao接口层生成动态代理类注册,启动到了核心作用。这个类实现了如下接口,用来对扫描的Mapper进行处理:BeanDefinitionRegistryPostProcessorInitializingBeanApplicationContextAwareBeanNameAware 整体类图如下; 微信公众号:bugstack虫洞栈MapperScannerConfigurer类图 执行流程如下; 微信公众号:bugstack虫洞栈执行流程图 上面的类图流程图,其实已经很清楚的描述了MapperScannerConfigurer初始化过程,但对于头一次看的新人来说依旧是我太难了,好继续! MapperScannerConfigurer。java部分截取OverridepublicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistryregistry){if(this。processPropertyPlaceHolders){processPropertyPlaceHolders();}ClassPathMapperScannerscannernewClassPathMapperScanner(registry);scanner。setAddToConfig(this。addToConfig);scanner。setAnnotationClass(this。annotationClass);scanner。setMarkerInterface(this。markerInterface);scanner。setSqlSessionFactory(this。sqlSessionFactory);scanner。setSqlSessionTemplate(this。sqlSessionTemplate);scanner。setSqlSessionFactoryBeanName(this。sqlSessionFactoryBeanName);scanner。setSqlSessionTemplateBeanName(this。sqlSessionTemplateBeanName);scanner。setResourceLoader(this。applicationContext);scanner。setBeanNameGenerator(this。nameGenerator);scanner。registerFilters();scanner。scan(StringUtils。tokenizeToStringArray(this。basePackage,ConfigurableApplicationContext。CONFIGLOCATIONDELIMITERS));}实现了BeanDefinitionRegistryPostProcessor。postProcessBeanDefinitionRegistry用于注册Bean到Spring容器中306行:newClassPathMapperScanner(registry);硬编码类路径扫描器,用于解析Mybatis的Mapper文件317行:scanner。scan对Mapper进行扫描。这里包含了一个继承类实现关系的调用,也就是本文开头的测试题。 ClassPathMapperScanner。java部分截取OverridepublicSetBeanDefinitionHolderdoScan(String。。。basePackages){SetBeanDefinitionHolderbeanDefinitionssuper。doScan(basePackages);if(beanDefinitions。isEmpty()){logger。warn(NoMyBatismapperwasfoundinArrays。toString(basePackages)package。Pleasecheckyourconfiguration。);}else{processBeanDefinitions(beanDefinitions);}returnbeanD}优先调用父类的super。doScan(basePackages);进行注册Bean信息 ClassPathBeanDefinitionScanner。java部分截取protectedSetBeanDefinitionHolderdoScan(String。。。basePackages){Assert。notEmpty(basePackages,Atleastonebasepackagemustbespecified);SetBeanDefinitionHolderbeanDefinitionsnewLinkedHashSetBeanDefinitionHolder();for(StringbasePackage:basePackages){SetBeanDefinitioncandidatesfindCandidateComponents(basePackage);for(BeanDefinitioncandidate:candidates){ScopeMetadatascopeMetadatathis。scopeMetadataResolver。resolveScopeMetadata(candidate);candidate。setScope(scopeMetadata。getScopeName());StringbeanNamethis。beanNameGenerator。generateBeanName(candidate,this。registry);if(candidateinstanceofAbstractBeanDefinition){postProcessBeanDefinition((AbstractBeanDefinition)candidate,beanName);}if(candidateinstanceofAnnotatedBeanDefinition){AnnotationConfigUtils。processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate)}if(checkCandidate(beanName,candidate)){BeanDefinitionHolderdefinitionHoldernewBeanDefinitionHolder(candidate,beanName);definitionHolderAnnotationConfigUtils。applyScopedProxyMode(scopeMetadata,definitionHolder,this。regibeanDefinitions。add(definitionHolder);registerBeanDefinition(definitionHolder,this。registry);}}}returnbeanD}优先调用了父类的doScan方法,用于Mapper扫描和Bean的定义以及注册到DefaultListableBeanFactory。{DefaultListableBeanFactory是Spring中IOC容器的始祖,所有需要实例化的类都需要注册进来,之后在初始化}272行:findCandidateComponents(basePackage),扫描package包路径,对于注解类的有另外的方式,大同小异288行:registerBeanDefinition(definitionHolder,this。registry);注册Bean信息的过程,最终会调用到:org。springframework。beans。factory。support。DefaultListableBeanFactory ClassPathMapperScanner。java部分截取processBeanDefinitions(beanDefinitions);privatevoidprocessBeanDefinitions(SetBeanDefinitionHolderbeanDefinitions){GenericBeanDfor(BeanDefinitionHolderholder:beanDefinitions){definition(GenericBeanDefinition)holder。getBeanDefinition();if(logger。isDebugEnabled()){logger。debug(CreatingMapperFactoryBeanwithnameholder。getBeanName()anddefinition。getBeanClassName()mapperInterface);}themapperinterfaceistheoriginalclassofthebeanbut,theactualclassofthebeanisMapperFactoryBeandefinition。getConstructorArgumentValues()。addGenericArgumentValue(definition。getBeanClassName());issue59definition。setBeanClass(this。mapperFactoryBean。getClass());definition。getPropertyValues()。add(addToConfig,this。addToConfig);booleanexplicitFactoryUif(StringUtils。hasText(this。sqlSessionFactoryBeanName)){definition。getPropertyValues()。add(sqlSessionFactory,newRuntimeBeanReference(this。sqlSessionFactoryBeanName));explicitFactoryU}elseif(this。sqlSessionFactory!null){definition。getPropertyValues()。add(sqlSessionFactory,this。sqlSessionFactory);explicitFactoryU}if(StringUtils。hasText(this。sqlSessionTemplateBeanName)){if(explicitFactoryUsed){logger。warn(Cannotuseboth:sqlSessionTemplateandsqlSessionFactorytogether。sqlSessionFactoryisignored。);}definition。getPropertyValues()。add(sqlSessionTemplate,newRuntimeBeanReference(this。sqlSessionTemplateBeanName));explicitFactoryU}elseif(this。sqlSessionTemplate!null){if(explicitFactoryUsed){logger。warn(Cannotuseboth:sqlSessionTemplateandsqlSessionFactorytogether。sqlSessionFactoryisignored。);}definition。getPropertyValues()。add(sqlSessionTemplate,this。sqlSessionTemplate);explicitFactoryU}if(!explicitFactoryUsed){if(logger。isDebugEnabled()){logger。debug(EnablingautowirebytypeforMapperFactoryBeanwithnameholder。getBeanName()。);}definition。setAutowireMode(AbstractBeanDefinition。AUTOWIREBYTYPE);}}}163行:super。doScan(basePackages);,调用完父类方法后开始执行内部方法:processBeanDefinitions(beanDefinitions)186行:definition。getConstructorArgumentValues()。addGenericArgumentValue(definition。getBeanClassName());设置BeanName参数,也就是我们的:ISchoolDao、IUserDao187行:definition。setBeanClass(this。mapperFactoryBean。getClass());,设置BeanClass,接口本身是没有类的,那么这里将MapperFactoryBean类设置进来,最终所有的dao层接口类都是这个MapperFactoryBean MapperFactoryBean。java部分截取 这个类有继承也有接口实现,最好先了解下整体类图,如下; 微信公众号:bugstack虫洞栈MapperFactoryBean类图 这个类就非常重要了,最终所有的sql信息执行都会通过这个类获取getObject(),也就是SqlSession获取mapper的代理类:MapperProxyFactoryMapperProxypublicclassMapperFactoryBeanTextendsSqlSessionDaoSupportimplementsFactoryBeanT{privateClassTmapperIprivatebooleanaddToCpublicMapperFactoryBean(){intentionallyempty}publicMapperFactoryBean(ClassTmapperInterface){this。mapperInterfacemapperI}当SpringBean容器初始化时候会调用到checkDaoConfig(),他是继承类中的抽象方法{inheritDoc}OverrideprotectedvoidcheckDaoConfig(){super。checkDaoConfig();notNull(this。mapperInterface,PropertymapperInterfaceisrequired);ConfigurationconfigurationgetSqlSession()。getConfiguration();if(this。addToConfig!configuration。hasMapper(this。mapperInterface)){try{configuration。addMapper(this。mapperInterface);}catch(Exceptione){logger。error(Errorwhileaddingthemapperthis。mapperInterfacetoconfiguration。,e);thrownewIllegalArgumentException(e);}finally{ErrorContext。instance()。reset();}}}{inheritDoc}OverridepublicTgetObject()throwsException{returngetSqlSession()。getMapper(this。mapperInterface);}。。。}72行:checkDaoConfig(),当SpringBean容器初始化时候会调用到checkDaoConfig(),他是继承类中的抽象方法95行:getSqlSession()。getMapper(this。mapperInterface);,通过接口获取Mapper(代理类),调用过程如下;DefaultSqlSession。getMapper(Classtype),获取MapperConfiguration。getMapper(Classtype,SqlSessionsqlSession),从配置中获取MapperRegistry。getMapper(Classtype,SqlSessionsqlSession),从注册中心获取到实例化生成publicTgetMapper(Classtype,SqlSessionsqlSession){ finalMapperProxyFactorymapperProxyFactory(MapperProxyFactory)knownMappers。get(type); if(mapperProxyFactorynull){ thrownewBindingException(TypetypeisnotknowntotheMapperRegistry。); } try{ returnmapperProxyFactory。newInstance(sqlSession); }catch(Exceptione){ thrownewBindingException(Errorgettingmapperinstance。Cause:e,e); } } mapperProxyFactory。newInstance(sqlSession);,通过反射工程生成MapperProxySuppressWarnings(unchecked) protectedTnewInstance(MapperProxymapperProxy){ return(T)Proxy。newProxyInstance(mapperInterface。getClassLoader(),newClass〔〕{mapperInterface},mapperProxy); } publicTnewInstance(SqlSessionsqlSession){ finalMapperProxymapperProxynewMapperProxy(sqlSession,mapperInterface,methodCache); returnnewInstance(mapperProxy); } MapperProxy。java部分截取publicclassMapperProxyTimplementsInvocationHandler,Serializable{privatestaticfinallongserialVersionUID6424540398559729838L;privatefinalSqlSessionsqlSprivatefinalClassTmapperIprivatefinalMapMethod,MapperMethodmethodCpublicMapperProxy(SqlSessionsqlSession,ClassTmapperInterface,MapMethod,MapperMethodmethodCache){this。sqlSessionsqlSthis。mapperInterfacemapperIthis。methodCachemethodC}OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object〔〕args)throwsThrowable{try{if(Object。class。equals(method。getDeclaringClass())){returnmethod。invoke(this,args);}elseif(isDefaultMethod(method)){returninvokeDefaultMethod(proxy,method,args);}}catch(Throwablet){throwExceptionUtil。unwrapThrowable(t);}finalMapperMethodmapperMethodcachedMapperMethod(method);returnmapperMethod。execute(sqlSession,args);}privateMapperMethodcachedMapperMethod(Methodmethod){MapperMethodmapperMethodmethodCache。get(method);if(mapperMethodnull){mapperMethodnewMapperMethod(mapperInterface,method,sqlSession。getConfiguration());methodCache。put(method,mapperMethod);}returnmapperM}UsesJava7privateObjectinvokeDefaultMethod(Objectproxy,Methodmethod,Object〔〕args)throwsThrowable{finalConstructorMethodHandles。LookupconstructorMethodHandles。Lookup。class。getDeclaredConstructor(Class。class,int。class);if(!constructor。isAccessible()){constructor。setAccessible(true);}finalC?declaringClassmethod。getDeclaringClass();returnconstructor。newInstance(declaringClass,MethodHandles。Lookup。PRIVATEMethodHandles。Lookup。PROTECTEDMethodHandles。Lookup。PACKAGEMethodHandles。Lookup。PUBLIC)。unreflectSpecial(method,declaringClass)。bindTo(proxy)。invokeWithArguments(args);}。。。}58行:finalMapperMethodmapperMethodcachedMapperMethod(method);,从缓存中获取MapperMethod59行:mapperMethod。execute(sqlSession,args);,执行SQL语句,并返回结果(到这关于查询获取结果就到骨头(干)层了);INSERT、UPDATE、DELETE、SELECTpublicObjectexecute(SqlSessionsqlSession,Object〔〕args){ O switch(command。getType()){ caseINSERT:{ Objectparammethod。convertArgsToSqlCommandParam(args); resultrowCountResult(sqlSession。insert(command。getName(),param)); } caseUPDATE:{ Objectparammethod。convertArgsToSqlCommandParam(args); resultrowCountResult(sqlSession。update(command。getName(),param)); } caseDELETE:{ Objectparammethod。convertArgsToSqlCommandParam(args); resultrowCountResult(sqlSession。delete(command。getName(),param)); } caseSELECT: if(method。returnsVoid()method。hasResultHandler()){ executeWithResultHandler(sqlSession,args); }elseif(method。returnsMany()){ resultexecuteForMany(sqlSession,args); }elseif(method。returnsMap()){ resultexecuteForMap(sqlSession,args); }elseif(method。returnsCursor()){ resultexecuteForCursor(sqlSession,args); }else{ Objectparammethod。convertArgsToSqlCommandParam(args); resultsqlSession。selectOne(command。getName(),param); } caseFLUSH: resultsqlSession。flushStatements(); default: thrownewBindingException(Unknownexecutionmethodfor:command。getName()); } if(resultnullmethod。getReturnType()。isPrimitive()!method。returnsVoid()){ thrownewBindingException(Mappermethodcommand。getName() attemptedtoreturnnullfromamethodwithaprimitivereturntype(method。getReturnType())。); } } 以上对于MapperScannerConfigurer这一层就分析完了,从扫描定义注入到为Spring容器准备Bean的信息,代理、反射、SQL执行,基本就包括全部核心内容了,接下来在分析下SqlSessionFactoryBean3。SqlSession容器工厂初始化(SqlSessionFactoryBean) SqlSessionFactoryBean初始化过程中需要对一些自身内容进行处理,因此也需要实现如下接口;FactoryBeanInitializingBeanvoidafterPropertiesSet()throwsExceptionApplicationListener 微信公众号:bugstack虫洞栈SqlSessionFactoryBean初始化流程 以上的流程其实已经很清晰的描述整个核心流程,但同样对于新手上路会有障碍,那么!好,继续! SqlSessionFactoryBean。java部分截取publicvoidafterPropertiesSet()throwsException{notNull(dataSource,PropertydataSourceisrequired);notNull(sqlSessionFactoryBuilder,PropertysqlSessionFactoryBuilderisrequired);state((configurationnullconfigLocationnull)!(configuration!nullconfigLocation!null),PropertyconfigurationandconfigLocationcannotspecifiedwithtogether);this。sqlSessionFactorybuildSqlSessionFactory();}afterPropertiesSet(),InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。380行:buildSqlSessionFactory();内部方法构建,核心功能继续往下看。 SqlSessionFactoryBean。java部分截取protectedSqlSessionFactorybuildSqlSessionFactory()throwsIOException{CXMLConfigBuilderxmlConfigB。。。if(!isEmpty(this。mapperLocations)){for(ResourcemapperLocation:this。mapperLocations){if(mapperLocationnull){}try{XMLMapperBuilderxmlMapperBuildernewXMLMapperBuilder(mapperLocation。getInputStream(),configuration,mapperLocation。toString(),configuration。getSqlFragments());xmlMapperBuilder。parse();}catch(Exceptione){thrownewNestedIOException(Failedtoparsemappingresource:mapperLocation,e);}finally{ErrorContext。instance()。reset();}if(LOGGER。isDebugEnabled()){LOGGER。debug(Parsedmapperfile:mapperLocation);}}}else{if(LOGGER。isDebugEnabled()){LOGGER。debug(PropertymapperLocationswasnotspecifiedornomatchingresourcesfound);}}returnthis。sqlSessionFactoryBuilder。build(configuration);}513行:for(ResourcemapperLocation:this。mapperLocations)循环解析Mapper内容519行:XMLMapperBuilderxmlMapperBuildernewXMLMapperBuilder(。。。)解析XMLMapperBuilder521行:xmlMapperBuilder。parse()执行解析,具体如下; XMLMapperBuilder。java部分截取publicclassXMLMapperBuilderextendsBaseBuilder{privatefinalXPathPprivatefinalMapperBuilderAssistantbuilderAprivatefinalMapString,XNodesqlFprivatefinalSprivatevoidbindMapperForNamespace(){StringnamespacebuilderAssistant。getCurrentNamespace();if(namespace!null){C?boundTtry{boundTypeResources。classForName(namespace);}catch(ClassNotFoundExceptione){ignore,boundtypeisnotrequired}if(boundType!null){if(!configuration。hasMapper(boundType)){SpringmaynotknowtherealresourcenamesowesetaflagtopreventloadingagainthisresourcefromthemapperinterfacelookatMapperAnnotationBuilderloadXmlResourceconfiguration。addLoadedResource(namespace:namespace);configuration。addMapper(boundType);}}}}}这里413行非常重要,configuration。addMapper(boundType);,真正到了添加Mapper到配置中心 MapperRegistry。java部分截取publicclassMapperRegistry{publicTvoidaddMapper(ClassTtype){if(type。isInterface()){if(hasMapper(type)){thrownewBindingException(TypetypeisalreadyknowntotheMapperRegistry。);}booleanloadCtry{knownMappers。put(type,newMapperProxyFactoryT(type));Itsimportantthatthetypeisaddedbeforetheparserisrunotherwisethebindingmayautomaticallybeattemptedbythemapperparser。Ifthetypeisalreadyknown,itwonttry。MapperAnnotationBuilderparsernewMapperAnnotationBuilder(config,type);parser。parse();loadC}finally{if(!loadCompleted){knownMappers。remove(type);}}}}}67行:创建代理工程knownMappers。put(type,newMapperProxyFactory(type)); 截至到这,MapperScannerConfigurer、SqlSessionFactoryBean,两个类干的事情就相融合了;第一个用于扫描Dao接口设置代理类注册到IOC中,用于后续生成Bean实体类,MapperFactoryBean,并可以通过mapperInterface从Configuration获取Mapper另一个用于生成SqlSession工厂初始化,解析Mapper里的XML配置进行动态代理MapperProxyFactoryMapperProxy注入到Configuration的Mapper最终在注解类的帮助下进行方法注入,等执行操作时候即可获得动态代理对象,从而执行相应的CRUD操作Resource privateISchoolDaoschoolD schoolDao。querySchoolInfoById(1L);六、综上总结分析过程较长篇幅也很大,不一定一天就能看懂整个流程,但当耐下心来一点点研究,还是可以获得很多的收获的。以后在遇到这类的异常就可以迎刃而解了,同时也有助于面试、招聘!之所以分析Mybatis最开始是想在Dao上加自定义注解,发现切面拦截不到。想到这是被动态代理的类,之后层层往往下扒直到MapperProxy。invoke!当然,Mybatis提供了自定义插件开发。以上的源码分析只是对部分核心内容进行分析,如果希望了解全部可以参考资料;MyBatis3源码深度解析,并调试代码。IDEA中还是很方便看源码的,包括可以查看类图、调用顺序等。mybatis、mybatisspring中其实最重要的是将Mapper配置文件解析与接口类组装成代理类进行映射,以此来方便对数据库的CRUD操作。从源码分析后,可以获得更多的编程经验(套路)。Mybatis相关链接;https:github。commybatismybatis3https:mybatis。orgmybatis3zhindex。html