一直以來都在使用MyBatis作持久化框架,也知道當咱們定義XXXMapper接口類並利用它來作CRUD操做時,Mybatis是利用了動態代理的技術幫咱們生成代理類。那麼動態代理內部的實現細節究竟是怎麼的呀?XXXMapper.java類和XXXMapper.xml究竟是如何關聯起來的呀?本篇文章就來詳細剖析下MyBatis的動態代理的具體實現機制。java
在詳細探究MyBatis中動態代理機制以前,先來補充一下基礎知識,認識一下MyBatis的核心組件。mysql
注意: 如今咱們使用Mybatis,通常都是和Spring框架整合在一塊兒使用,這種狀況下,SqlSession將被Spring框架所建立,因此每每不須要咱們使用SqlSessionFactoryBuilder或者SqlSessionFactory去建立SqlSession
下面展現一下如何使用MyBatis的這些組件,或者如何快速使用MyBatis:git
CREATE TABLE user( id int, name VARCHAR(255) not NULL , age int , PRIMARY KEY (id) )ENGINE =INNODB DEFAULT CHARSET=utf8;
@Data public class User { private int id; private int age; private String name; @Override public String toString() { return "User{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--全局配置文件的根元素--> <configuration> <!--enviroments表示環境配置,能夠配置成開發環境(development)、測試環境(test)、生產環境(production)等--> <environments default="development"> <environment id="development"> <!--transactionManager: 事務管理器,屬性type只有兩個取值:JDBC和MANAGED--> <transactionManager type="MANAGED" /> <!--dataSource: 數據源配置--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> <!--mappers文件路徑配置--> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration>
public interface UserMapper { User selectById(int id); }
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace屬性表示命令空間,不一樣xml映射文件namespace必須不一樣--> <mapper namespace="com.pjmike.mybatis.UserMapper"> <select id="selectById" parameterType="int" resultType="com.pjmike.mybatis.User"> SELECT id,name,age FROM user where id= #{id} </select> </mapper>
public class MybatisTest { private static SqlSessionFactory sqlSessionFactory; static { try { sqlSessionFactory = new SqlSessionFactoryBuilder() .build(Resources.getResourceAsStream("mybatis-config.xml")); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.selectById(1); System.out.println("User : " + user); } } } // 結果: User : User{id=1, age=21, name='pjmike'}
上面的例子簡單的展現瞭如何使用MyBatis,與此同時,我也將用這個例子來進一步探究MyBatis動態原理的實現。github
public static void main(String[] args) { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// <1> User user = userMapper.selectById(1); System.out.println("User : " + user); } }
在前面的例子中,咱們使用sqlSession.getMapper()方法獲取UserMapper對象,實際上這裏咱們是獲取了UserMapper接口的代理類,而後再由代理類執行方法。那麼這個代理類是如何生成的呢?在探究動態代理類如何生成以前,咱們先來看下SqlSessionFactory工廠的建立過程作了哪些準備工做,好比說mybatis-config配置文件是如何讀取的,映射器文件是如何讀取的?sql
private static SqlSessionFactory sqlSessionFactory; static { try { sqlSessionFactory = new SqlSessionFactoryBuilder() .build(Resources.getResourceAsStream("mybatis-config.xml")); } catch (IOException e) { e.printStackTrace(); } }
咱們使用new SqlSessionFactoryBuilder().build()的方式建立SqlSessionFactory工廠,走進build方法數據庫
public SqlSessionFactory build(InputStream inputStream, Properties properties) { return build(inputStream, null, properties); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
對於mybatis的全局配置文件的解析,相關解析代碼位於XMLConfigBuilder的parse()方法中:api
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); loadCustomLogImpl(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")); //解析mapper映射器文件 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
從parseConfiguration方法的源代碼中很容易就能夠看出它對mybatis全局配置文件中各個元素屬性的解析。固然最終解析後返回一個Configuration對象,Configuration是一個很重要的類,它包含了Mybatis的全部配置信息,它是經過XMLConfigBuilder取錢構建的,Mybatis經過XMLConfigBuilder讀取mybatis-config.xml中配置的信息,而後將這些信息保存到Configuration中mybatis
//解析mapper映射器文件 mapperElement(root.evalNode("mappers"));
該方法是對全局配置文件中mappers屬性的解析,走進去:app
mapperParser.parse()
方法就是XMLMapperBuilder對Mapper映射器文件進行解析,可與XMLConfigBuilder進行類比框架
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); //解析映射文件的根節點mapper元素 configuration.addLoadedResource(resource); bindMapperForNamespace(); //重點方法,這個方法內部會根據namespace屬性值,生成動態代理類 } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
該方法主要用於將mapper文件中的元素信息,好比insert
、select
這等信息解析到MappedStatement對象,並保存到Configuration類中的mappedStatements屬性中,以便於後續動態代理類執行CRUD操做時可以獲取真正的Sql語句信息
buildStatementFromContext方法就用於解析insert、select
這類元素信息,並將其封裝成MappedStatement對象,具體的實現細節這裏就不細說了。
該方法是核心方法,它會根據mapper文件中的namespace屬性值,爲接口生成動態代理類,這就來到了咱們的主題內容——動態代理類是如何生成的。
bindMapperForNamespace方法源碼以下所示:
private void bindMapperForNamespace() { //獲取mapper元素的namespace屬性值 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { // 獲取namespace屬性值對應的Class對象 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //若是沒有這個類,則直接忽略,這是由於namespace屬性值只須要惟一便可,並不必定對應一個XXXMapper接口 //沒有XXXMapper接口的時候,咱們能夠直接使用SqlSession來進行增刪改查 } if (boundType != null) { if (!configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); //若是namespace屬性值有對應的Java類,調用Configuration的addMapper方法,將其添加到MapperRegistry中 configuration.addMapper(boundType); } } } }
這裏提到了Configuration的addMapper方法,實際上Configuration類裏面經過MapperRegistry對象維護了全部要生成動態代理類的XxxMapper接口信息,可見Configuration類確實是至關重要一類
public class Configuration { ... protected MapperRegistry mapperRegistry = new MapperRegistry(this); ... public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } ... }
其中兩個重要的方法:getMapper()和addMapper()
Configuration將addMapper方法委託給MapperRegistry的addMapper進行的,源碼以下:
public <T> void addMapper(Class<T> type) { // 這個class必須是一個接口,由於是使用JDK動態代理,因此須要是接口,不然不會針對其生成動態代理 if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // 生成一個MapperProxyFactory,用於以後生成動態代理類 knownMappers.put(type, new MapperProxyFactory<>(type)); //如下代碼片斷用於解析咱們定義的XxxMapper接口裏面使用的註解,這主要是處理不使用xml映射文件的狀況 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
MapperRegistry內部維護一個映射關係,每一個接口對應一個MapperProxyFactory(生成動態代理工廠類)
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
這樣便於在後面調用MapperRegistry的getMapper()時,直接從Map中獲取某個接口對應的動態代理工廠類,而後再利用工廠類針對其接口生成真正的動態代理類。
Configuration的getMapper()方法內部就是調用MapperRegistry的getMapper()方法,源代碼以下:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //根據Class對象獲取建立動態代理的工廠對象MapperProxyFactory final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { //這裏能夠看到每次調用都會建立一個新的代理對象返回 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
從上面能夠看出,建立動態代理類的核心代碼就是在MapperProxyFactory.newInstance方法中,源碼以下:
protected T newInstance(MapperProxy<T> mapperProxy) { //這裏使用JDK動態代理,經過Proxy.newProxyInstance生成動態代理類 // newProxyInstance的參數:類加載器、接口類、InvocationHandler接口實現類 // 動態代理能夠將全部接口的調用重定向到調用處理器InvocationHandler,調用它的invoke方法 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
PS: 關於JDK動態代理的詳細介紹這裏就再也不細說了,有興趣的能夠參閱我以前寫的文章: 動態代理的原理及其應用
這裏的InvocationHandler接口的實現類是MapperProxy,其源碼以下:
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //若是調用的是Object類中定義的方法,直接經過反射調用便可 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } //調用XxxMapper接口自定義的方法,進行代理 //首先將當前被調用的方法Method構形成一個MapperMethod對象,而後掉用其execute方法真正的開始執行。 final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } ... }
最終的執行邏輯在於MapperMethod類的execute方法,源碼以下:
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { //insert語句的處理邏輯 case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } //update語句的處理邏輯 case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } //delete語句的處理邏輯 case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } //select語句的處理邏輯 case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); //調用sqlSession的selectOne方法 result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; } ... }
在MapperMethod中還有兩個內部類,SqlCommand和MethodSignature類,在execute方法中首先用switch case語句根據SqlCommand的getType()方法,判斷要執行的sql類型,好比INSET、UPDATE、DELETE、SELECT和FLUSH,而後分別調用SqlSession的增刪改查等方法。
慢着,說了這麼多,那麼這個getMapper()方法何時被調用呀?實際是一開始咱們調用SqlSession的getMapper()方法:
UserMapper userMapper = sqlSession.getMapper(UserMapper.class); public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final Executor executor; @Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } ... }
因此getMapper方法的大體調用邏輯鏈是:
SqlSession#getMapper() ——> Configuration#getMapper() ——> MapperRegistry#getMapper() ——> MapperProxyFactory#newInstance() ——> Proxy#newProxyInstance()
還有一點咱們須要注意:咱們經過SqlSession的getMapper方法得到接口代理來進行CRUD操做,其底層仍是依靠的是SqlSession的使用方法。
根據上面的探究過程,簡單畫了一個邏輯圖(不必定準確):
本篇文章主要介紹了MyBatis的動態原理,回過頭來,咱們須要知道咱們使用UserMapper的動態代理類進行CRUD操做,本質上仍是經過SqlSession這個關鍵類執行增刪改查操做,可是對於SqlSession如何具體執行CRUD的操做並無仔細闡述,有興趣的同窗能夠查閱相關資料。