【Mybatis进阶】动态代理
如果你想更深刻的理解Mybatis动态代理的原理,那么你应该先知道
- 什么是代理模式?
- 在没有动态代理的时候Mybatis是如何实现dao层的?
什么是代理模式
具体可以阅读笔者的博客—— 代理模式
在没有动态代理的时候Mybatis是如何实现dao层的
本篇博客基于mybatis的环境已经搭建完成,如果不知道如何搭建,具体可以阅读笔者的博客——【从零开始学Mybatis笔记(三)】Dao开发方法
- 接口
public interface UserMapper {
List<User> findAll();
}
- xml
<select id="findAll" resultType="User">
select *
from user
</select>
- 实现类
public class UserMapperImpl implements UserMapper {
private SqlSessionFactory factory;
public UserMapperImpl(SqlSessionFactory factory) {
this.factory = factory;
}
@Override
public List<User> findAll() {
//1.获取sqlSession对象
SqlSession sqlSession = factory.openSession();
//2.调用selectList方法
List<User> list = sqlSession.selectList("com.tyust.dao.UserMapper.findAll");
//3.关闭流
sqlSession.close();
return list;
}
}
到这里我们知道,Mybatis实现的关键就在于List<User> list = sqlSession.selectList("com.example.dao.UserDao.findAll")
中通过sqlSession来实现对相应方法的操作。其实动态代理的最终结果也是使用sqlSession的相应方法。
Mybatis动态代理的实现原理
@Override
public List<User> findAll() {
//1.获取sqlSession对象
SqlSession sqlSession = factory.openSession();
//2.调用selectList方法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.findAll();
//3.关闭流
sqlSession.close();
return list;
}
和Dao操作一样,只不过使用了getMapper()来获得一个代理类,进行所有的操作。所以这就是我们关注的关键对象。
在单步调试之前,我们需要知道动态代理中最重要的类:SqlSession、MapperProxy、MapperMethod。
好了我们开始吧
- 进入
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
因为SqlSesseion为接口,所以我们通过Debug方式发现这里使用的实现类为DefaultSqlSession。
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
找到DeaultSqlSession中的getMapper方法,发现这里没有做其他的动作,只是将工作继续抛到了Configuration类中,Configuration为类不是接口,可以直接进入该类的getMapper方法中。
- 进入
configuration.<T>getMapper(type, this)
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
Configuration是MyBatis初始化后全局唯一的配置对象,它内部保存着配置文件解析过程中所有的配置信息。找到Configuration类的getMapper方法,这里也是将工作继续交到MapperRegistry的getMapper的方法中,所以我们继续向下进行。
- 进入
mapperRegistry.getMapper(type, sqlSession)
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
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);
}
}
MapperRegistry是Mapper接口动态代理工厂类的注册中心。找到MapperRegistry的getMapper的方法,看到这里发现和以前不一样了。
首先Type就是我们的接口UserMapper;
mapperProxyFactory一个HashMap,private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
; 通过 (MapperProxyFactory<T>) knownMappers.get(type);
我们就获得了MapperProxyFactory。
通过MapperProxyFactory的命名方式我们知道这里将通过这个工厂生成我们所关注的MapperProxy的代理类,然后我们通过mapperProxyFactory.newInstance(sqlSession);
进入MapperProxyFactory的newInstance方法中。
- 进入
mapperProxyFactory.newInstance(sqlSession);
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
找到MapperProxyFactory的newIntance方法,通过参数类型SqlSession可以得知,上面的调用先进入第二个newInstance方法中并创建我们所需要重点关注的MapperProxy对象,第二个方法中再调用第一个newInstance方法并将MapperProxy对象传入进去,根据该对象创建代理类并返回。看到Proxy.newProxyInstance,我们应该就知道这是JDK的动态代理api,这里已经得到需要的代理类了,但是我们的代理类所做的工作还得继续向下看MapperProxy类。
- MapperProxy类
找到MapperProxy类,发现其确实实现了JDK动态代理必须实现的接口InvocationHandler,所以我们重点关注invoke()方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
method就是我们的findAll()方法,而proxy就是之前通过newInstance生成的代理类。
getDeclaringClass()方法返回表示声明由此Method对象表示的方法的类的Class对象,说白了就是UserMapper。这里的判断就是方法是不是代理本身的方法,如果是,直接调用方法。如果不是,通过缓存方法生成一个对象再调用。具体是什么对象呢,我们看cachedInvoker
方法。
- MapperMethodInvoker
在看cachedInvoker
方法之前,我们先了解一下,什么是MapperMethodInvoker。
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
private static class DefaultMethodInvoker implements MapperMethodInvoker {
private final MethodHandle methodHandle;
public DefaultMethodInvoker(MethodHandle methodHandle) {
super();
this.methodHandle = methodHandle;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return methodHandle.bindTo(proxy).invokeWithArguments(args);
}
}
}
我们可以看到MapperMethodInvoker 是MapperProxy的一个内部接口,有两个实现类DefaultMethodInvoker和PlainMethodInvoker,二者都有一个构造函数和一个invoke方法,区别就在于构造函数封装的实例和invoke方法的逻辑。
PlainMethodInvoker类:是Mapper接口普通方法的调用类,它实现了MethodInvoker接口。其内部封装了MapperMethod实例。
DefaultMethodInvoker类:MethodInvoker接口的默认方法实现类。其内部封装了MethodHandle 实例。
MapperMethod:封装了Mapper接口中对应方法的信息,以及对应的SQL语句的信息;它是mapper接口与映射配置文件中SQL语句的桥梁。
Mapper接口中的每一个方法都对应一个MapperMethodInvoker对象,而MapperMethodInvoker对象里面的MapperMethod保存着对应的SQL信息和返回类型以完成SQL调用。
- 进入
cachedInvoker
方法
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
// It should be removed once the fix is backported to Java 8 or
// MyBatis drops Java 8 support. See gh-1929
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
return invoker;
}
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
根据被调用接口方法的Method对象,从缓存中获取MapperMethodInvoker对象,如果没有则创建一个并放入缓存。然后会判断用户当前调用的是否是接口的default方法,如果不是就会创建一个PlainMethodInvoker对象并返回。
搞懂了cacheInvoker,我们再看 return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
,可以发现当cacheInvoker返回了PalinMethodInvoker实例之后,紧接着调用了这个实例的PlainMethodInvoker::invoke
方法。进入PlainMethodInvoker::invoke
方法我们发现它底层调用的是MapperMethod::execute
方法
- 进入
mapperMethod.execute(sqlSession, args)
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);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进行了封装使用。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。MethodSignature用来封装方法的参数以及返回类型,在execute的方法中我们发现在这里又回到了SqlSession中的接口调用,和我们自己实现UerDao接口的方式中直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。
参考文献
Mybatis mapper动态代理的原理详解
https://blog.csdn.net/tianjindong0804/article/details/105862940
手把手带你阅读Mybatis源码(一)构造篇
手把手带你阅读Mybatis源码(二)执行篇