使用mybatis接口映射的示例用户代码:
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.saveUser(user);
}
UserMapper只是一个java接口,实现类都没有,mybatis竟然让我们直接调用一个接口方法就可以执行数据库操作,这背后的细节就是代理机制的使用,语句sqlSession.getMapper(UserMapper.class)
实际上是利用java的动态代理机制返回一个实现了UserMapper接口的代理对象,可以打印userMapper.getClass().getName()
的结果或者用IDE调试工具可以看到是一个代理对象。
那到底是如何获得一个代理对象的呢,SqlSession
接口的默认实现是DefaultSqlSession
,我们看看在DefaultSqlSession
的getMapper
方法的实现:
// DefaultSqlSession#getMapper:
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
继续跟踪Configuration里的getMapper方法实现:
// Configuration#getMapper:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 直接调用了mapperRegistry的getMapper()
// mapperRegistry是Configuration中的mapper注册对象
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry#getMapper:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 从knownMappers容器中读取与mapper类型匹配的MapperProxyFactory对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
// throw e
}
try {
// 调用工厂方法产生代理实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
// throw e
}
}
上面列出代码的注释中标注的,程序流程从knownMappers容器中获取一个与mapper类型匹配的MapperProxyFactory
实例,这是一个可以创建代理对象的工厂类,工厂方法newInstance
获得的就是一个mapper类型的代理实例。这里knownMappers是MapperRegistry
中维护的一个map结构的容器字段:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
knownMappers容器中缓存了mapper类型和产生mapper类型实例的工厂,缓存内容是在mybatis初始化全局的Configuration
对象的过程中设置的,我们可以在MapperRegistry中找到添加缓存条目的方法:
// MapperProxy#addMapper:
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
// throw e
}
boolean loadCompleted = false;
try {
// 在缓存中增加一个条目mapperType -> MapperProxyFactory
knownMappers.put(type, new MapperProxyFactory<T>(type));
// ...
} finally {
// ...
}
}
}
现在我们来看看MapperProxyFactory
这个工厂,它的代码不多,如下所示是它的全部实现代码。
// MapperProxyFactroy:
public class MapperProxyFactory<T> {
// mapperInterface字段维护着目标mapper类型
private final Class<T> mapperInterface;
// 缓存着已经构建好的mapper方法和和其对应的MapperMethod对象;
// MapperMethod类似一个包含mapper方法的执行上下文信息的封装对象。
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
// 构造方法,在前一个代码片段MapperProxy#addMapper里有调用体现
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) {
// 动态代理创建代理实例
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
// 对外暴露的工厂方法
public T newInstance(SqlSession sqlSession) {
// 首先创建一个MapperProxy实例
// MapperProxy事实上就是一个InvocationHandler实例
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
// 调用内部重载的newInstance方法返回mapper实例
return newInstance(mapperProxy);
}
}
注释里已经说明了MapperProxyFactory的整体结构和代码流程,主要就是两个重载的newInstance方法体现了利用动态代理来创建mapper代理实例,公开的newInstance方法内部先实例化了一个MapperProxy
对象,MapperProxy其实就是一个InvocationHandler
,通过动态代理创建的代理对象内部都维持一个InvocationHandler的引用,最终的数据库的操作就实现在InvocationHandler的invoke方法中,在创建MapperProxy对象时构造方法接受了几个参数,这些参数在invoke方法中操作数据库时都需要依赖,我们看看它声明的这几个字段以及构造方法:
// MapperProxy:
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;
}
methodCache字段就是前面说过的methodCache缓存,sqlSession指向的就是最开头的用户代码中使用的sqlSession对象,需要把sqlSession对象传递这么远,因为最终还是通过sqlSession的insert、update、select等方法来实现数据库操作的。
最后看看invoke
方法:
// MapperProxy#invoke:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {// 如果不是接口
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {// 如果是default方法,java 8支持默认方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 这里才表示是接口代理
// 从前面所说的methodCache中取MapperMethod,
// 缓存中没有就创建一个,可以查看cachedMapperMethod方法的实现
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 通过MapperMethod对象执行实际操作
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.execute(sqlSession, args)
执行数据库操作的,前面说过,MapperMethod其实像一个上下文对象,它封装的是正在执行的mapper方法的细节,还有就是方法执行数据库操作所需要的环境依赖信息等。
看看它的构造方法和字段:
// 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);
}
然后我们跟踪它的execute方法实现细节:
// MapperMethod#execute:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
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);
result = sqlSession.selectOne(command.getName(), param);
}
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;
}
execute方法中检测当前sql命令的形式,根据类型是select、insert、upadte、delete或者flush分别采取不同的执行过程,以insert为例,看看insert这个case分支里有:
result = rowCountResult(sqlSession.insert(command.getName(), param));
最终还是进入了sqlSession的insert方法过程,接口映射可以理解为就是利用动态代理包装了SqlSession实例,我们得到的是代理对象,在代理对象上进行crud操作,然后又把请求委托给SqlSession处理。
来源:oschina
链接:https://my.oschina.net/u/3227308/blog/3224555