MyBatis 源码分析——动态代理

MyBatis框架是哪些去履行SQL语句?相信不只是你们,笔者也想要知道是怎么样举办的。相信有上一章的辅导大家都理解SqlSession接口的效用。当然默认情况下或者使用DefaultSqlSession类。关于SqlSession接口的用法有很多种。笔者依然相比喜欢用getMapper方法。对于getMapper方法的落实形式。笔者不可能下一个结论。笔者只是想表示一下协调的精通而以——动态代理。

笔者把有关getMapper方法的实现情势了解为动态代理。事实上笔者还想说他得以是一个AOP思想的贯彻。那么具体是一个咋样体统东西。相信笔者说了也不可以表示咋样。一切依旧有大家温馨去查看和透亮。从源码上我们可以见见getMapper方法会去调用Configuration类的getMapper方法。好了。一切的最先都在那里了。

DefaultSqlSession类:

 public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

对此Configuration类上一章里面就印证她里头存放了具有有关XML文件的安排音讯。从参数上我们可以看出他要我们传入一个Class<T>类型。这曾经足以看来后头肯定要用到反射机制和动态变化对应的类实例。让我们更是查看一下源码。

Configuration类:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

当笔者点击进入发现她又调用MapperRegistry类的getMapper方法的时候,心里面有一种又恨又爱的扼腕——那就是构架之美和复杂之恨。MapperRegistry类笔者把他领略存放动态代理工厂(MapperProxyFactory类)的库存。当然我们依旧进入看一看源码吧。

MapperRegistry类:

 1  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 2     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
 3     if (mapperProxyFactory == null) {
 4       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
 5     }
 6     try {
 7       return mapperProxyFactory.newInstance(sqlSession);
 8     } catch (Exception e) {
 9       throw new BindingException("Error getting mapper instance. Cause: " + e, e);
10     }
11   }

好了。笔者相信我们看看这一段代码的时候都领悟——MapperRegistry类就是用来存放在MapperProxyFactory类的。我们依旧在看一下knownMappers成员是一个怎样要规范的会面类型。

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

knownMappers是一个字典类型。从Key的门类上我们得以判定出来是一个类一个动态代理工厂。笔者见到这里的时候都会去点击一个MapperProxyFactory类的源码。看看她里头又是部分什么东东。

 1 public class MapperProxyFactory<T> {
 2 
 3   private final Class<T> mapperInterface;
 4   private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
 5 
 6   public MapperProxyFactory(Class<T> mapperInterface) {
 7     this.mapperInterface = mapperInterface;
 8   }
 9 
10   public Class<T> getMapperInterface() {
11     return mapperInterface;
12   }
13 
14   public Map<Method, MapperMethod> getMethodCache() {
15     return methodCache;
16   }
17 
18   @SuppressWarnings("unchecked")
19   protected T newInstance(MapperProxy<T> mapperProxy) {
20     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
21   }
22 
23   public T newInstance(SqlSession sqlSession) {
24     final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
25     return newInstance(mapperProxy);
26   }
27 
28 }

还好。代码不是累累,领悟起来也不是很复杂。略看一下源码,笔者做了一个很勇敢的推断——一个类,一个动态代理工厂,五个章程代理。我们先把估算放在此处,然后让我们回来地点部分吗。大家发现MapperRegistry类的getMapper方法里面最终会去调用MapperProxyFactory类的newInstance方法。这些时候大家又见到她实例化了一个MapperProxy类。MapperProxy类是怎么。这些就关系到Proxy类的用法了。所以读者们自己去查看相关材料了。意思显明每执行一回XxxMapper(例如:笔者例子里面的IProductMapper接口)的法门都会创立一个MapperProxy类。方法执行在此以前都会先去调用相应MapperProxy类里面的invoke方法。如下

MapperProxy类:

 1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 2     if (Object.class.equals(method.getDeclaringClass())) {
 3       try {
 4         return method.invoke(this, args);
 5       } catch (Throwable t) {
 6         throw ExceptionUtil.unwrapThrowable(t);
 7       }
 8     }
 9     final MapperMethod mapperMethod = cachedMapperMethod(method);
10     return mapperMethod.execute(sqlSession, args);
11   }

从源码的情致:从缓存中收获执行措施对应的MapperMethod类实例。假诺MapperMethod类实例不存在的状态,创立参加缓存并再次回到相关的实例。最终调用MapperMethod类的execute方法。

到这边笔者小结一下,上边讲到笔者例子里面用到的getMapper方法。getMapper方法就是用来得到相关的数码操作类接口。而实际数据操作类邦定了动态代理。所以操据操作类执行措施的时候,会打动每个方法相应的MapperProxy类的invoke方法。所以invoke方法重临的结果就是操据操作类执行措施的结果。这规范我们就通晓最终的任务交给了MapperMethod类实例。

MapperMethod类里面有俩个成员:SqlCommand类和MethodSignature类。从名字上大家大体的能体悟一个可能跟SQL语句有提到,一个恐怕跟要实施的措施有涉及。事实也是如此。笔者查阅了SqlCommand类的源码。确切来讲这一有的的情节跟XxxMapper的XML配置文件之中的select节点、delete节点等关于。我们都会知晓节点上有id属性值。那么MyBatis框架会把每一个节点(如:select节点、delete节点)生成一个MappedStatement类。要找到MappedStatement类就务须透过id来获取。有一个细节要小心:代码应用的id
= 当前接口类 + XML文件的节点的ID属性。如下

 1 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
 2       String statementName = mapperInterface.getName() + "." + method.getName();
 3       MappedStatement ms = null;
 4       if (configuration.hasStatement(statementName)) {
 5         ms = configuration.getMappedStatement(statementName);
 6       } else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
 7         String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
 8         if (configuration.hasStatement(parentStatementName)) {
 9           ms = configuration.getMappedStatement(parentStatementName);
10         }
11       }
12       if (ms == null) {
13         if(method.getAnnotation(Flush.class) != null){
14           name = null;
15           type = SqlCommandType.FLUSH;
16         } else {
17           throw new BindingException("Invalid bound statement (not found): " + statementName);
18         }
19       } else {
20         name = ms.getId();
21         type = ms.getSqlCommandType();
22         if (type == SqlCommandType.UNKNOWN) {
23           throw new BindingException("Unknown execution method for: " + name);
24         }
25       }
26     }

观看此间的时候,我们就可以回头去找一找在怎么时候扩充了MappedStatement类。下边之所以得以实施是建立在XML配置信息都被加载进来了。所以MappedStatement类也肯定是在加载配置的时候就开展的。请读者们自行查看一下MapperBuilderAssistant类的addMappedStatement方法——加深领会。SqlCommand类的name成员和type成员大家仍然关注一下。name成员即便节点的ID,type成员表示查寻依旧更新或是删除。至于MethodSignature类呢。他用于阐明方法的有些音信,首要有再次回到信息。

作者下面讲了这多一点重如果为着查看execute方法源码容易一点。因为execute方法都要用到SqlCommand类和MethodSignature类。

 1   public Object execute(SqlSession sqlSession, Object[] args) {
 2     Object result;
 3     switch (command.getType()) {
 4       case INSERT: {
 5         Object param = method.convertArgsToSqlCommandParam(args);
 6         result = rowCountResult(sqlSession.insert(command.getName(), param));
 7         break;
 8       }
 9       case UPDATE: {
10         Object param = method.convertArgsToSqlCommandParam(args);
11         result = rowCountResult(sqlSession.update(command.getName(), param));
12         break;
13       }
14       case DELETE: {
15         Object param = method.convertArgsToSqlCommandParam(args);
16         result = rowCountResult(sqlSession.delete(command.getName(), param));
17         break;
18       }
19       case SELECT:
20         if (method.returnsVoid() && method.hasResultHandler()) {
21           executeWithResultHandler(sqlSession, args);
22           result = null;
23         } else if (method.returnsMany()) {
24           result = executeForMany(sqlSession, args);
25         } else if (method.returnsMap()) {
26           result = executeForMap(sqlSession, args);
27         } else if (method.returnsCursor()) {
28           result = executeForCursor(sqlSession, args);
29         } else {
30           Object param = method.convertArgsToSqlCommandParam(args);
31           result = sqlSession.selectOne(command.getName(), param);
32         }
33         break;
34       case FLUSH:
35         result = sqlSession.flushStatements();
36         break;
37       default:
38         throw new BindingException("Unknown execution method for: " + command.getName());
39     }
40     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
41       throw new BindingException("Mapper method '" + command.getName() 
42           + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
43     }
44     return result;
45   }

首要部分就是这里,我们会发觉我们转了一圈,最后仍旧要回来SqlSession接口实例上。完美的一圈!笔者用青色标出来了。

看来了此处大家就驾驭调头去看一下SqlSession接口实例吧。

网站地图xml地图