一、首先來看看最簡單的mybatis項目啓動過程sql
public static void mybatisTest() throws IOException { String resource = "mybatis/mybatis-config.xml"; //配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = null; try { //開啓一個session sqlSession = sqlSessionFactory.openSession(); //獲取mapper JobConfigDao jobConfigDao = sqlSession.getMapper(JobConfigDao.class); //查詢數據 List<JobConfig> jobConfigList = jobConfigDao.listConfig(); System.out.println(jobConfigList); } finally { if (sqlSession != null) sqlSession.close(); } }
這個過程主要是SqlSessionFactory 的建立,先獲取配置信息,而後經過SqlSessionFactoryBuilder來建立SqlSessionFactory 。
build方法的核心代碼是:緩存
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); var5 = this.build(parser.parse());
能夠看到mybatis是經過XMLConfigBuilder來解析配置文件的。來看看XMLConfigBuilder的parse方法:session
public Configuration parse() { //XMLConfigBuilder中有一個boolean類型parsed,來標記是否解析過配置文件,解析過以後就設置爲true,防止一樣的配置被重複解析 if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true; // 解析configuration節點下的配置 this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } } private void parseConfiguration(XNode root) { try { this.propertiesElement(root.evalNode("properties")); Properties settings = this.settingsAsProperties(root.evalNode("settings")); this.loadCustomVfs(settings); this.typeAliasesElement(root.evalNode("typeAliases")); this.pluginElement(root.evalNode("plugins")); this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectorFactoryElement(root.evalNode("reflectorFactory")); this.settingsElement(settings); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); // 解析mapper this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }
能夠看到,在parseConfiguration方法中會將mybatis-config.xml配置下的各類屬性獲取出來一一解析,並映射到相關的屬性上面去。mybatis
經過上面的啓動過程,已經知道mybatis是經過XMLConfigBuilder來解析配置文件的,具體解析是在parseConfiguration方法的中,獲取到mappers配置節點,最後一行this.mapperElement(root.evalNode("mappers"))將節點信息轉交給XMLMapperBuilder來完成對mapper的解析。app
來看看XMLMapperBuilder的parse方法:ide
public void parse() { if (!this.configuration.isResourceLoaded(this.resource)) { this.configurationElement(this.parser.evalNode("/mapper")); this.configuration.addLoadedResource(this.resource); //經過命名空間綁定mapper this.bindMapperForNamespace(); } this.parsePendingResultMaps(); this.parsePendingCacheRefs(); this.parsePendingStatements(); }
由於這裏的重點是想知道定義的接口是怎麼實現的,因此直接來看看bindMapperForNamespace方法對如何來實現mapper的綁定:ui
private void bindMapperForNamespace() { String namespace = this.builderAssistant.getCurrentNamespace(); if (namespace != null) { Class boundType = null; try { //經過反射獲取類類型 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException var4) { ; } //若是當前類尚未綁定到配置的緩存中 if (boundType != null && !this.configuration.hasMapper(boundType)) { this.configuration.addLoadedResource("namespace:" + namespace); //經過mybatis的Configuration將mapper註冊到MapperRegistry this.configuration.addMapper(boundType); } } }
來看看MapperRegistry 的addMapper:this
public <T> void addMapper(Class<T> type) { this.mapperRegistry.addMapper(type); } public <T> void addMapper(Class<T> type) { if (type.isInterface()) {//先判斷是否爲接口 if (this.hasMapper(type)) {//若是已經註冊,拋出異常 throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { //建立一個代理MapperProxyFactory 並將該mapper緩存到內存的Map中去 this.knownMappers.put(type, new MapperProxyFactory(type)); //綁定註解 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { this.knownMappers.remove(type); } } } }
經過對上面源碼的跟蹤分析,終於知道,mybatis經過MapperProxyFactory爲每一個接口都提供了一個代理實現,而後經過MapperRegistry將mapper註冊到容器中的。spa
那麼在使用的時候又是如何來獲取這個mapper的呢?代理
答案是:反射加上代理
首先經過SqlSession的getMapper方法,mybatis爲SqlSession提供了一個默認的實現DefaultSqlSession,DefaultSqlSession的內部屬性以下:
//Configuration配置 private final Configuration configuration; private final Executor executor; private final boolean autoCommit; private boolean dirty; private List<Cursor<?>> cursorList;
DefaultSqlSession獲取配置,而後配置中獲取MapperRegistry,從MapperRegistry中獲取到mapper代理工廠,再經過MapperProxyFactory的newInstance來建立mapper代理類MapperProxy
啓動時建立的SqlSessionFactory是DefaultSqlSessionFactory,DefaultSqlSessionFactory中擁有屬性Configuration,Configuration是經過XMLConfigBuilder從配置文件中解析出來的各類配置信息,Configuration中有MapperRegistry,MapperRegistry中用一個final類型的Map存儲了用戶定義Mapper代理實現工廠MapperProxyFactory,Mapper代理實現是MapperProxy。