mybatis的源碼有人已經作過一箇中文的註釋,代碼github上有mybatis中文註釋源碼java
mybatis框架有兩個很是重要的xml文件,一個是mybatis的config文件,一個就是mapper文件,mybatis會根據config的xml文件去生成一個Configuration類,在這個過程當中也會根據配置的mapper文件生成MappedStatement,這篇博客探究的就是這樣一個過程,往下看git
若是單單使用mybatis,咱們的作法是導包,配置,而後以下github
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()),sql
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變量中,session
而後往下看類型別名的解析,關於別名,首先Configuration類中定義了一個TypeAliasRegistrymybatis
//類型別名註冊機 protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
這個TypeAliasRegistry中有一個Map存放了別名和別名的類app
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的解析,其實
//看一下如何添加一個映射 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
再往下看,建立了一個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方法中看
//解析 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寶典