mybatis的源码有人曾经做过一个中文的正文,代码github上有mybatis中文正文源码
mybatis框架有两个十分重要的xml文件,一个是mybatis的config文件,一个就是mapper文件,mybatis会依据config的xml文件去生成一个Configuration类,在这个过程中也会依据配置的mapper文件生成MappedStatement,这篇博客探索的就是这样一个过程,往下看
如果单单应用mybatis,咱们的做法是导包,配置,而后如下
String resource = "org/mybatis/example/mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);try (SqlSession session = sqlSessionFactory.openSession()) { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(101);}
所以从SqlSessionFactoryBuilder().build说起,点击进入build办法,新建了一个XMLConfigBuilder,而后build(parser.parse()),
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());
先看parser.parse()办法,这办法中将之前的mybatis的xml文件进行解析,生成了Configration类返回,
//解析配置 private void parseConfiguration(XNode root) { try { //分步骤解析 //issue #117 read properties first //1.properties propertiesElement(root.evalNode("properties")); //2.类型别名 typeAliasesElement(root.evalNode("typeAliases")); //3.插件 pluginElement(root.evalNode("plugins")); //4.对象工厂 objectFactoryElement(root.evalNode("objectFactory")); //5.对象包装工厂 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //6.设置 settingsElement(root.evalNode("settings")); // read it after objectFactory and objectWrapperFactory issue #631 //7.环境 environmentsElement(root.evalNode("environments")); //8.databaseIdProvider databaseIdProviderElement(root.evalNode("databaseIdProvider")); //9.类型处理器 typeHandlerElement(root.evalNode("typeHandlers")); //10.映射器 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
仔细分析这几行代码,首先看第一个properties解析
//1.properties //<properties resource="org/mybatis/example/config.properties"> // <property name="username" value="dev_user"/> // <property name="password" value="F2Fa3!33TYyg"/> //</properties> private void propertiesElement(XNode context) throws Exception { if (context != null) { //如果在这些中央,属性多于一个的话,MyBatis 依照如下的程序加载它们: //1.在 properties 元素体内指定的属性首先被读取。 //2.从类门路下资源或 properties 元素的 url 属性中加载的属性第二被读取,它会笼罩曾经存在的齐全一样的属性。 //3.作为办法参数传递的属性最初被读取, 它也会笼罩任一曾经存在的齐全一样的属性,这些属性可能是从 properties 元素体内和资源/url 属性中加载的。 //传入形式是调用构造函数时传入,public XMLConfigBuilder(Reader reader, String environment, Properties props) //1.XNode.getChildrenAsProperties函数不便失去孩子所有Properties Properties defaults = context.getChildrenAsProperties(); //2.而后查找resource或者url,退出后面的Properties String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); if (resource != null && url != null) { throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); } if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } //3.Variables也全副退出Properties Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } parser.setVariables(defaults); configuration.setVariables(defaults); } }
具体的xml解析过程就没必要具体看了,最初能够看到所有的properties都被存入了Configuration的variables变量中,
而后往下看类型别名的解析,对于别名,首先Configuration类中定义了一个TypeAliasRegistry
//类型别名注册机 protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
这个TypeAliasRegistry中有一个Map寄存了别名和别名的类
private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
所以typeAliasesElement(root.evalNode("typeAliases"))这个办法中的操作就是解析出别名放入这个map中,定义别名的两种形式具体能够看官网。
再往下看,插件的解析
//3.插件 //MyBatis 容许你在某一点拦挡已映射语句执行的调用。默认状况下,MyBatis 容许应用插件来拦挡办法调用//<plugins>// <plugin interceptor="org.mybatis.example.ExamplePlugin">// <property name="someProperty" value="100"/>// </plugin>//</plugins> private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); //调用InterceptorChain.addInterceptor configuration.addInterceptor(interceptorInstance); } } }
插件尽管比较复杂,然而解析的局部却很简略,次要是resolveClass办法
//依据别名解析Class,其实是去查看 类型别名注册/事务管理器别名 protected Class<?> resolveClass(String alias) { if (alias == null) { return null; } try { return resolveAlias(alias); } catch (Exception e) { throw new BuilderException("Error resolving class. Cause: " + e, e); } }
这个别名的解析过程其实就是去之前说的那个别名的map中查问,有的话就返回,没的话就间接转成Class,所以mybatis外面很多配置属性type="xxx"的,例如datasource的type="POOLED",这个POOLED其实就是类型的别名。最初获取到Class之后newInstance创立一个对象,放入Interceptor拦截器链中,这个拦截器链和SpringMvc相似,其实就是一个拦截器链对象InterceptorChain外面放了一个List汇合,调用的时候for循环顺次调用,去看看代码
protected final InterceptorChain interceptorChain = new InterceptorChain();
Configuration类中定义了这样一个过滤器链,前面某个中央必定会执行pluginAll办法
public Object pluginAll(Object target) { //循环调用每个Interceptor.plugin办法 for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
这中央用过插件就很相熟了,plugin办法中咱们根本都这样写,而这个办法就是创立了一个代理对象
return Plugin.wrap(target, this);
public static Object wrap(Object target, Interceptor interceptor) { //获得签名Map Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); //获得要扭转行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor) Class<?> type = target.getClass(); //获得接口 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); //产生代理 if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; }
先看获取签名getSignatureMap这个办法
//获得签名Map private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { //取Intercepts注解,例子可参见ExamplePlugin.java Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // issue #251 //必须得有Intercepts注解,没有报错 if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } //value是数组型,Signature的数组 Signature[] sigs = interceptsAnnotation.value(); //每个class里有多个Method须要被拦挡,所以这么定义 Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); for (Signature sig : sigs) { Set<Method> methods = signatureMap.get(sig.type()); if (methods == null) { methods = new HashSet<Method>(); signatureMap.put(sig.type(), methods); } try { Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; }
这里从咱们正文在拦截器插件的类注解Intercepts 上获取Signature数组,循环数组,解析后果放入signatureMap中,signatureMap是一个Class为键,Method的Set列表为Value的Map,说白了这个解析后果就是一个对象中须要拦挡的哪几个办法。
再回头往下看,
很相熟的动静代理办法,因为传入的InvocationHandler也是Plugin这个类,所以invoke办法也在这个类中
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //看看如何拦挡 Set<Method> methods = signatureMap.get(method.getDeclaringClass()); //看哪些办法须要拦挡 if (methods != null && methods.contains(method)) { //调用Interceptor.intercept,也即插入了咱们本人的逻辑 return interceptor.intercept(new Invocation(target, method, args)); } //最初还是执行原来逻辑 return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }
剖析一下这段代码,这就是从方才解析的须要拦挡的办法的Map中取出该类的拦挡列表办法,看看是不是包含以后的办法,是的话就执行intercept也就是咱们写的那些拦挡办法。再最初执行办法自身的逻辑。规范老套娃!
再回到XMLConfigBuilder中,接着往下
//4.对象工厂 objectFactoryElement(root.evalNode("objectFactory"));
这个就是解析出一个类办法放到Configuration的objectFactory中,笼罩它默认的对象工厂
而后是解析对象包装工厂,反射器工厂,settings,environments等等原理和之前都差不多,所以跳过,
看重点最初一个mapperElement办法
//10.映射器// 10.1应用类门路// <mappers>// <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>// <mapper resource="org/mybatis/builder/BlogMapper.xml"/>// <mapper resource="org/mybatis/builder/PostMapper.xml"/>// </mappers>//// 10.2应用相对url门路// <mappers>// <mapper url="file:///var/mappers/AuthorMapper.xml"/>// <mapper url="file:///var/mappers/BlogMapper.xml"/>// <mapper url="file:///var/mappers/PostMapper.xml"/>// </mappers>//// 10.3应用java类名// <mappers>// <mapper class="org.mybatis.builder.AuthorMapper"/>// <mapper class="org.mybatis.builder.BlogMapper"/>// <mapper class="org.mybatis.builder.PostMapper"/>// </mappers>//// 10.4主动扫描包下所有映射器// <mappers>// <package name="org.mybatis.builder"/>// </mappers> private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { //10.4主动扫描包下所有映射器 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) { //10.1应用类门路 ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); //映射器比较复杂,调用XMLMapperBuilder //留神在for循环里每个mapper都从新new一个XMLMapperBuilder,来解析 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { //10.2应用相对url门路 ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); //映射器比较复杂,调用XMLMapperBuilder XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { //10.3应用java类名 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的解析,其实 <mapper xxx/>这种模式解析过程也相似,要害都是调用了configuration.addMapper这个办法,所以间接看这个办法,这个办法在Configuration类的mapperRegistry中
//看一下如何增加一个映射 public <T> void addMapper(Class<T> type) { //mapper必须是接口!才会增加 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<T>(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 { //如果加载过程中出现异常须要再将这个mapper从mybatis中删除,这种形式比拟俊俏吧,难道是不得已而为之? if (!loadCompleted) { knownMappers.remove(type); } } } }
重点就是new MapperProxyFactory<T>(type),这里将存入一个Mapper的代理工厂类。
再往下看,创立了一个MapperAnnotationBuilder,而后再看parse办法。
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(); }
首先configuration.isResourceLoaded会判断是否加载了mapper的xml,很显然,如果用package形式的,走到这一步,就只是找到了接口,将代理工厂存入map中,并没有去加载xml,所以会loadXmlResource()
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(); } } }
这里将接口全面的.替换成了/,所以如果接口是a.test,那xml就肯定得是a/test.xml,而后会新建一个XMLMapperBuilder,这里能够回去mapperElement办法中看<mapper resource="xxx"/>的解析,也是通过XMLMapperBuilder,所以这些解析形式其实大同小异,而后再看XMLMapperBuilder的parse办法
//解析 public void parse() { //如果没有加载过再加载,避免反复加载 if (!configuration.isResourceLoaded(resource)) { //配置mapper configurationElement(parser.evalNode("/mapper")); //标记一下,曾经加载过了 configuration.addLoadedResource(resource); //绑定映射器到namespace bindMapperForNamespace(); } //还有没解析完的东东这里接着解析? parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); }
先看configurationElement办法
//配置mapper元素// <mapper namespace="org.mybatis.example.BlogMapper">// <select id="selectBlog" parameterType="int" resultType="Blog">// select * from Blog where id = #{id}// </select>// </mapper> private void configurationElement(XNode context) { try { //1.配置namespace String namespace = context.getStringAttribute("namespace"); if (namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); //2.配置cache-ref cacheRefElement(context.evalNode("cache-ref")); //3.配置cache cacheElement(context.evalNode("cache")); //4.配置parameterMap(曾经废除,老式格调的参数映射) parameterMapElement(context.evalNodes("/mapper/parameterMap")); //5.配置resultMap(高级性能) resultMapElements(context.evalNodes("/mapper/resultMap")); //6.配置sql(定义可重用的 SQL 代码段) sqlElement(context.evalNodes("/mapper/sql")); //7.配置select|insert|update|delete TODO buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } }
首先看cache-ref的解析
//2.配置cache-ref,在这样的 状况下你能够应用 cache-ref 元素来援用另外一个缓存。 //<cache-ref namespace="com.someone.application.data.SomeMapper"/> private void cacheRefElement(XNode context) { if (context != null) { //减少cache-ref configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace")); CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace")); try { cacheRefResolver.resolveCacheRef(); } catch (IncompleteElementException e) { configuration.addIncompleteCacheRef(cacheRefResolver); } } }
先往configuration中寄存cache-ref的map中增加以后解析的cache-ref的namespace,而后创立一个cache-ref解析器解析,
public Cache resolveCacheRef() { //反调MapperBuilderAssistant解析 return assistant.useCacheRef(cacheRefNamespace); }
public Cache useCacheRef(String namespace) { if (namespace == null) { throw new BuilderException("cache-ref element requires a namespace attribute."); } try { unresolvedCacheRef = true; Cache cache = configuration.getCache(namespace); if (cache == null) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found."); } currentCache = cache; unresolvedCacheRef = false; return cache; } catch (IllegalArgumentException e) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e); } }
这里调用的是MapperBuilderAssistant这个助手的办法,而在这个助手类中,逻辑是这样的,去configuration的cache的map中获取cache,如果cache曾经创立了,就返回。如果还没有创立,那么就抛出一个IncompleteElementException异样,异样被内部捕捉,将以后cache-ref的解析器放入一个用来寄存未实现cache-ref解析的列表中。
而后接下来解析cache,
//3.配置cache cacheElement(context.evalNode("cache"));
办法中仍旧是调用助手类的办法
//调用builderAssistant.useNewCache builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
接下来的几个resultmap,sql等解析的过程根本相似。
以后解析实现之后,再往下看,会去解析之前未齐全解析的各类对象,进入第一个办法
private void parsePendingResultMaps() { Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps(); synchronized (incompleteResultMaps) { Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator(); while (iter.hasNext()) { try { iter.next().resolve(); iter.remove(); } catch (IncompleteElementException e) { // ResultMap is still missing a resource... } } } }
之前存入map中的未齐全解析的解析器取出循环调用之前同样的办法,而在此刻,之前须要期待创立的对象当初都曾经创立实现,所以能够实现创立(我想了一下,这外面如同没有a须要b,b须要c的这种,被依赖的如同都是没有须要依赖的)。
再回到MapperAnnotationBuilder中,接下去是办法的注解解析,和之前xml的区别就是解析的办法,跳过。
最终SqlSessionFactoryBuilder会执行到这行代码,生成一个DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
到此解析完结。
关注公众号:java宝典