前言
剛開始使用Mybaits的同學有沒有這樣的疑惑,為什么我們沒有編寫Mapper的實現類,卻能調用Mapper的方法呢?本篇文章我帶大家一起來解決這個疑問
上一篇文章我們獲取到了DefaultSqlSession,接着我們來看第一篇文章測試用例后面的代碼
//獲取對應的mapper UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //執行方法 List<User> list = userMapper.getAll();
為 Mapper 接口創建代理對象
我們先從 DefaultSqlSession 的 getMapper 方法開始看起,如下:
public class DefaultSqlSession implements SqlSession { private final Configuration configuration; @Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } } public class Configuration { protected final MapperRegistry mapperRegistry = new MapperRegistry(this); public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } } public class MapperRegistry { private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 從 knownMappers 中獲取與 type 對應的 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
根據名稱看,可以理解為Mapper代理的創建工廠,是不是Mapper的代理對象由它創建呢?我們先來回顧一下knownMappers 集合中的元素是何時存入的。這要在我前面的文章中找答案,MyBatis 在解析配置文件的 <mappers> 節點的過程中,會調用 MapperRegistry 的 addMapper 方法將 Class 到 MapperProxyFactory 對象的映射關系存入到 knownMappers。有興趣的同學可以看看我之前的文章,我們來回顧一下源碼:
public class XMLMapperBuilder extends BaseBuilder { private final XPathParser parser; private final MapperBuilderAssistant builderAssistant; private void bindMapperForNamespace() { // 獲取映射文件的命名空間 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { // 根據命名空間解析 mapper 類型 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //ignore, bound type is not required } if (boundType != null) { // 檢測當前 mapper 類是否被綁定過 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); // 綁定 mapper 類 configuration.addMapper(boundType); } } } } } public class Configuration { protected final MapperRegistry mapperRegistry = new MapperRegistry(this); public <T> void addMapper(Class<T> type) { // 通過 MapperRegistry 綁定 mapper 類 mapperRegistry.addMapper(type); } } public class MapperRegistry { private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { /* * 將 type 和 MapperProxyFactory 進行綁定,MapperProxyFactory 可為 mapper 接口生成代理類 */ 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 { if (!loadCompleted) { knownMappers.remove(type); } } } } }
在解析Mapper.xml的最后階段,獲取到Mapper.xml的namespace,然后利用反射,獲取到namespace的Class,並創建一個MapperProxyFactory的實例,namespace的Class作為參數,最后將namespace的Class為key,MapperProxyFactory的實例為value存入knownMappers。
注意,我們這里是通過映射文件的命名空間的Class當做knownMappers的Key。然后我們看看getMapper方法,是通過參數User.class也就是Mapper接口的Class來獲取MapperProxyFactory,所以我們明白了為什么要求xml配置中的namespace要和和對應的Mapper接口的全限定名了。
生成代理對象
我們看return mapperProxyFactory.newInstance(sqlSession);
,很明顯是調用了MapperProxyFactory的一個工廠方法,我們跟進去看看
public class MapperProxyFactory<T> { //存放Mapper接口Class private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return methodCache; } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { //生成mapperInterface的代理類 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { /* * 創建 MapperProxy 對象,MapperProxy 實現了 InvocationHandler 接口,代理邏輯封裝在此類中 * 將sqlSession傳入MapperProxy對象中,第二個參數是Mapper的接口,並不是其實現類 */ final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
上面的代碼首先創建了一個 MapperProxy 對象,該對象實現了 InvocationHandler 接口。然后將對象作為參數傳給重載方法,並在重載方法中調用 JDK 動態代理接口為 Mapper接口 生成代理對象。
這里要注意一點,MapperProxy這個InvocationHandler 創建的時候,傳入的參數並不是Mapper接口的實現類,我們以前是怎么創建JDK動態代理的?先創建一個接口,然后再創建一個接口的實現類,最后創建一個InvocationHandler並將實現類傳入其中作為目標類,創建接口的代理類,然后調用代理類方法時會回調InvocationHandler的invoke方法,最后在invoke方法中調用目標類的方法,但是我們這里調用Mapper接口代理類的方法時,需要調用其實現類的方法嗎?不需要,我們需要調用對應的配置文件的SQL,所以這里並不需要傳入Mapper的實現類到MapperProxy中,那Mapper接口的代理對象是如何調用對應配置文件的SQL呢?下面我們來看看。
Mapper代理類如何執行SQL?
上面一節中我們已經獲取到了EmployeeMapper的代理類,並且其InvocationHandler為MapperProxy,那我們接着看Mapper接口方法的調用
List<User> list = userMapper.getAll();
知道JDK動態代理的同學都知道,調用代理類的方法,最后都會回調到InvocationHandler的Invoke方法,那我們來看看這個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); //如果是接口中的default方法,JDK8的新特性之一 } else if (isDefaultMethod(method)) { //如果用戶執行的是接口中的default方法的話,MyBatis就需要為用戶提供正常的代理流程。 return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 從緩存中獲取 MapperMethod 對象,若緩存未命中,則創建 MapperMethod 對象 final MapperMethod mapperMethod = cachedMapperMethod(method); // 調用 execute 方法執行 SQL return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { //創建一個MapperMethod,參數為mapperInterface和method還有Configuration mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } }
如上,回調函數invoke邏輯會首先檢測被攔截的方法是不是定義在 Object 中的,比如 equals、hashCode 方法等。對於這類方法,直接執行即可。緊接着從緩存中獲取或者創建 MapperMethod 對象,然后通過該對象中的 execute 方法執行 SQL。我們先來看看如何創建MapperMethod
創建 MapperMethod 對象
public class MapperMethod { //包含SQL相關信息,比喻MappedStatement的id屬性,(mapper.UserMapper.getAll) private final SqlCommand command; //包含了關於執行的Mapper方法的參數類型和返回類型。 private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { // 創建 SqlCommand 對象,該對象包含一些和 SQL 相關的信息 this.command = new SqlCommand(config, mapperInterface, method); // 創建 MethodSignature 對象,從類名中可知,該對象包含了被攔截方法的一些信息 this.method = new MethodSignature(config, mapperInterface, method); } }
MapperMethod包含SqlCommand 和MethodSignature 對象,我們來看看其創建過程
1、創建 SqlCommand 對象
public static class SqlCommand { //name為MappedStatement的id,也就是namespace.methodName(mapper.UserMapper.getAll) private final String name; //SQL的類型,如insert,delete,update private final SqlCommandType type; public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { //拼接Mapper接口名和方法名,(mapper.UserMapper.getAll) final String methodName = method.getName(); final Class<?> declaringClass = method.getDeclaringClass(); //檢測configuration是否有key為mapper.UserMapper.getAll的MappedStatement //獲取MappedStatement MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); // 檢測當前方法是否有對應的 MappedStatement if (ms == null) { if (method.getAnnotation(Flush.class) != null) { name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { // 設置 name 和 type 變量 name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } } private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) { String statementId = mapperInterface.getName() + "." + methodName; //檢測configuration是否有key為statementName的MappedStatement if (configuration.hasStatement(statementId)) { return configuration.getMappedStatement(statementId); } else if (mapperInterface.equals(declaringClass)) { return null; } for (Class<?> superInterface : mapperInterface.getInterfaces()) { if (declaringClass.isAssignableFrom(superInterface)) { MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, configuration); if (ms != null) { return ms; } } }