本篇主要分析第二种实现方式(*Mapper mapper = sqlSession.getMapper(*Mapper.class)是如何实现的.
本文主要分析JDK动态代理,有关cglib代理和Byte Buddy代理的使用示例请参考源码仓库。
注意:cglib不建议使用,因为它不再维护了,并且在JDK17中存储问题,建议使用Byte Buddy
普通的JDK动态代理基本由以下几部分构成
一个接口ProxyService
public interface ProxyService {
String print(String message);
}
该接口的实现类ProxyServiceImpl
public class ProxyServiceImpl implements ProxyService {
@Override
public String print(String message) {
return message;
}
}
再加上一个至关重要的动态代理的Handler,CommonProxyHandler
public class CommonProxyHandler implements InvocationHandler {
private Object target;
public CommonProxyHandler(Object tarject) {
this.target = tarject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target,args);
}
}
最后就是使用的时候如何使用
@SpringBootTest
public class CommonProxyTest {
@Test
public void testCommonProxy(){
// 利用java的多态,接收其实例化的实例ProxyServiceImpl
ProxyService proxyService = new ProxyServiceImpl();
// handler中传入目标接口的实例化的对象ProxyServiceImpl
CommonProxyHandler commonProxyHandler = new CommonProxyHandler(proxyService);
// 第一个参数:当前类的类加载器
// 第二个参数:被代理类的实现的接口
// 第三个参数:handler
// 返回:该ProxyServiceImpl的代理类
ProxyService proxyInstance = (ProxyService) Proxy.newProxyInstance(CommonProxyTest.class.getClassLoader(),
proxyService.getClass().getInterfaces(), commonProxyHandler);
String s = proxyInstance.print("我是一个小代理");
System.out.println(s);
}
}
以上就是最常见的动态代理的简易实现方式。
因为第四篇里分析里JDK的动态代理,稍微思考一下,我发现了一个问题,既然动态代理的被代理类是接口的实现类,那么按照这个道理来说,我应该对UserMapper接口进行具体的实现才能实现JDK的动态代理。可是Mybatis并没有让我们去实现Mapper接口。
debug开始
在调用接口的具体方法之前可以看到,当前的mapper实例是一个MapperProxy的类型。是通过上一步sqlSession.getMapper(*Mapper.class)获得的,那就在上一步debug,进去。
会调用configuration对象的getMapper方法,参数就是当前UserMapper的完整类名
接下来会进入到mapperRegistry.getMapper中,根据当前的类名,获得对应的mapperProxyFactory对象,再利用mapperProxyFactory产生一个MapperProxy对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//在这里获得MapperProxyFactory的一个对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 在这里开始产生一个MapperProxy对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
mapperProxyFactory.newInstance()方法点进去,MapperProxy是实现了InvocationHandler接口的,他就等价于我们上面提到的CommonProxyHandler
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);
}
特别注意:
重点在这里.在文章开头给的例子中,CommonPorxyHandler实例化的时候,我们传进去的是一个接口的实现类,Impl类,而这里传进去的当前这个接口,可以点new MapperProxy那里看一下。
这样就产生了UserMapper接口的代理对象,而不是我们给出的一般JDK动态代理的Impl类的代理对象。
PS:这里有细心的人可能会问,这些我都看懂了,可是第一个,configuration.getMapper()、mapperResitry.getMapper、还有那个很关键的knowMppers.get()这里之前,里面的参数是怎么加入进去的,其实是在项目启动的时候,解析Mapper.xml文件和mybatis-config.xml的时候通过bindMapperForNamespace这个方法中调用configuration.addMapper加入进去的。第三篇分析的时候特意标红了的,可以看一下。
在获得了UserMapper的代理对象之后,使用代理对象调用addUser方法之前,我又想到了一个问题,第四篇中动态代理分析中,明确指出了最终执行的对象要是接口的具体实现类,否则执行method.invoke()的时候会报错。那么Mybatis是如何跳过这个障碍的?debug进去,
可以清楚的看到,Mybatis通过判断并没有进入method.invoke()方法,这样就不会报错,实例化一个MapperMethod对象,
根据传入的method对象那个,实例化了MapperMethod中的command对象,对象的名字就是方法的具体路径。
最终会执行mapperMethod.execute()方法,进入会根据当前的SQL执行类型进行执行,如下:
核心执行方法为sqlSession.insert()到这里就很明了了,可以看到这里传入的参数就是我们第二种方式中sqlSession显示指定的方法名字和参数。接下来的执行过程就和第一种没有任何差别了。
Mybatis的精妙之处就在于,把核心功能交给sqlSession对象进行操作,供用户使用的Mapper接口则通过入参方式进行调用,同时利用JDK代理的另外一种方式,即产生接口的代理对象,而不是接口实现类的代理对象的方式,让用户(程序员)使用更少的代码来完成核心功能的调用。
说实话看了Mybatis的源码之后我才发现,JDK的动态代理并不局限于接口的实现类,可以另辟蹊跷完成动态代理的使用。建议仔细看一下MapperProxy这个类,看看Mybatis是如何接收接口的。
到此为止:Mybatis框架一些粗糙的东西就分析完了,写的一些小的demo放在了GitHub上,如有需要,自取。这里是地址