MyBatisMyBatis 源码分析——动态代理

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地图