mybatis源码分析(四)---------------代理对象的生成
在mybatis两种开发方式这边文章中,我们提到了Mapper动态代理开发这种方式,现在抛出一个问题:通过sqlSession.getMapper(XXXMapper.class)来获取代理对象的过程是怎样的?生成的代理对象是通过怎样的方式来调用Mapper接口指定的方法的?
我们根据源码来一步步分析:
首先进入getMapper方法,通过一步步追踪,我们可以进入到MapperRegistry类中的getMapper方法,现在对整个类做分析:
/** * @author Clinton Begin * @author Eduardo Macarron * @author Lasse Voss */
// MapperRegistry整个类的作用就是注册Mapper接口和生成Mapper接口的代理对象 public class MapperRegistry { // 配置文件对象 private Configuration config;
// 这是一个HashMap,key是Mapper接口的类型对象,value是MapperProxyFactory
// 至于MapperProxyFactory是一个创建Mapper接口代理对象的工厂,后面会介绍 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); public MapperRegistry(Configuration config) { this.config = config; }
// 整个就是生成代理对象的方法 @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据Mapper接口的类型对象获取对应的 生成该Mapper接口代理对象的工厂 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); } } public <T> boolean hasMapper(Class<T> type) { return knownMappers.containsKey(type); }
// 注册Mapper接口 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 {
// 把Mapper接口的类型对象和MapperProxyFactory对象,放到HashMap中 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); } } } } /** * @since 3.2.2 */ public Collection<Class<?>> getMappers() { return Collections.unmodifiableCollection(knownMappers.keySet()); } /** * @since 3.2.2 */ public void addMappers(String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); for (Class<?> mapperClass : mapperSet) { addMapper(mapperClass); } } /** * @since 3.2.2
* 通过包名扫描其下的所有Mapper接口 */ public void addMappers(String packageName) { addMappers(packageName, Object.class); }
然后进入 mapperProxyFactory.newInstance(sqlSession);这个方法,
// 该类就是用来生成Mapper接口的代理对象的工厂
public class MapperProxyFactory<T> { // Mapper接口的class对象 private final Class<T> mapperInterface;
// key是Method方法对象,MapperMethod是对Mapper接口中方法的封装,这个MapperMethod值得探究,后面会有讲解 private 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) {
//通过JDK动态代理的方式生成了Mapper接口的代理对象,所以当代理对象调用Mapper接口的方法时,会触发mapperProxy对象中的invoke方法,下面对MapperProxy进行分析 (重点) return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
贴出MapperProxy类的源码:
// 该类实现了InvocationHandler接口
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; }
// 代理对象调用Mapper接口中的方法时,会触发该方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }
// 通过方法名找到MapperMethod,MapperMethod是对方法及其他参数的封装,稍后会提到 final MapperMethod mapperMethod = cachedMapperMethod(method);
// 此处是对SqlSession调用的封装,在另外一篇文章中,我们提到了Mapper动态代理的方法底层还是通过SQLSession来和数据库交互的,在这里就能看出来了(核心代码就在这个方法中) return mapperMethod.execute(sqlSession, args); }
private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } }
进入上面提到的方法,这里只贴出了MapperMethod类的属性和部分方法:
public class MapperMethod { // SqlCommand是对方法的全名(方法的全名 = 包名+类名+方法名(也就是Mapper.xml中的id))和操作类型(Mapper.xml中的insert,select,update等)的封装 private final SqlCommand command;
// MethodSignature 封装了方法的参数,返回类型等信息 private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, method); } // 核心逻辑在这里 public Object execute(SqlSession sqlSession, Object[] args) { Object result; if (SqlCommandType.INSERT == command.getType()) {
// 对参数做处理 Object param = method.convertArgsToSqlCommandParam(args);
// 通过sqlSession.insert(方法全名,param)这种方式来和数据库交互,并对返回的结果做处理,以下都是类似的情况:update,delete,select result = rowCountResult(sqlSession.insert(command.getName(), param)); } else if (SqlCommandType.UPDATE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); } else if (SqlCommandType.DELETE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param));
// 这个是查询 sqlSession.select().... } else if (SqlCommandType.SELECT == command.getType()) { 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 { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } } else { 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; } }
到这里我们明白了一点:通过Mapper动态代理的方式使用mybatis,其实就是利用了JDK的动态代理特性来生成对象,然后通过反射的方式来调用方法,最终底层还是通过mybatis提供的API sqlSession来和数据库进行交互的。
最后,再对SqlCommand 和 MethodSignature做一个简单的分析:
SqlCommand类有两个属性:name和type分别代表什么呢?通过例子来理解
public static class SqlCommand { private final String name; // com.yht.mybatisTest.dao.GoodsDao.selectGoodsById 方法的全名 private final SqlCommandType type; //SELECT Mapper.xml文件中节点的类型:SELECT,INSERT,DELETE或者UPDATE
}
MethodSignature类的属性如下:
public static class MethodSignature { private final boolean returnsMany; //是否返回多条数据 private final boolean returnsMap; //是否返回Map数据类型 private final boolean returnsVoid; // 是否返回void private final Class<?> returnType; // 具体的返回的数据对象类型,如:com.yht.mybatisTest.entity.Goods private final String mapKey; private final Integer resultHandlerIndex; private final Integer rowBoundsIndex; private final SortedMap<Integer, String> params; // 参数 private final boolean hasNamedParameters; }
到此,就结束了。