先来思考几个问题:
先说出答案,MyBatis是使用JDK动态代理来设计的:
利用java.lang.reflect.Proxy对象实现,底层用到其实是反射。
// jdk动态代理
public class ProxyFactory{
//维护一个目标对象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//给目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务2");
//执行目标对象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务2");
return returnValue;
}
}
);
}
}
上一篇我们已经知道了怎么样用SqlSession.selectList()来执行查询了,但是我们在做项目时,用的都是xxxMapper.selectByPriMary()来执行查询的,这个过程mybatis是怎么转换到使用sqlsession呢?
// 获取UserMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
我们就从getMapper去追源码就可以了。
// 1.可以看到是从configuration获取的
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
// 2.configuration类的获取方法,mapperRegistry顾名思义是一个注册mapper的对象
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
/* 3.mapperRegistry类的获取方法
knownMappers是一个HashMap,在项目启动时会从配置文件中(配置的包扫描)
将所有mapper全部放到这个map,key是Mapper的Class对象,value就是Mapper对应的MapperProxyFactory,
一个Mapper对应一个MapperProxyFactory*/
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
}
}
再看MapperProxyFactory对象的newInstance():
public T newInstance(SqlSession sqlSession) {
// MapperProxy对象是jdk代理的newProxyInstance()方法中用到的第三个参数,继承了InvocationHandler,会对mapper方法进行拦截,然后使用反射执行代理方法
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
// 调用重载方法
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
// 重点来了,看到jdk动态代理所用到的Proxy对象了么
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
再看MapperProxy类:
// 实现了InvocationHandler
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
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);
}
}
// 甚至对执行过的方法进行了缓存,下次就不用再创建新的了
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 最后在execute()里边开始使用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;
}
到此就追完了,但是我在写的过程中发现一个问题:
说好的JDK动态代理,目标类需要实现一个接口呢,为什么MyBatis的mapper没有实现呢,mapper本身就是接口还实现个啥。自己就是顶级接口,所以就不需要实现了,哈哈哈