Mybatis的xml是如何對映到介面的(2)?
重點分析了SqlSession的建立過程.SqlSession建立成功後:
String resource = "com/analyze/mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA");
sqlSession.close();
String resource = "com/analyze/mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map map = userMapper.getUA();
sqlSession.close();
以上兩段程式碼最終將得到相同的結果.
對比可以發現兩段程式碼不同之處為:
Map map = sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA");
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map map = userMapper.getUA();
根據sqlSession.selectOne("com.analyze.mybatis.mapper.UserMapper.getUA");
可以想像selectOne會用com.analyze.mybatis.mapper.UserMapper.getUA查詢相應的配置,然後執行sql.
接著分析sqlSession.selectOne()
DefaultSqlSession.java
public <T> T selectOne(String statement) {
return this.<T>selectOne(statement, null);
}
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//從配置中獲取statement資訊
MappedStatement ms = configuration.getMappedStatement(statement);
//呼叫執行器
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
但UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map map = userMapper.getUA();這兩行程式碼能夠看出一些問題,在程式碼裡並沒有實現UserMapper這個介面,也就是說userMapper.getUA();並沒實現,最終卻能夠被呼叫.那麼這到底是怎麼實現的呢?
進一步分析原始碼:
DefaultSqlSession.java
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
Configuration.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry.java
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);
}
}
從以上程式碼可以看出最終呼叫的是mapperProxyFactory.newInstance()
接著分析MapperProxyFactory的原始碼
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final 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) {
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);
}
}
從newInstance裡的MapperProxy很容易就可以看出使用了動態代理.
再來看看MapperProxy裡的invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//當執行的方法是繼承自Object時執行this裡的相應方法
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方法
return mapperMethod.execute(sqlSession, args);
}
從以上程式碼可以看出呼叫mapper裡的介面的時候執行的都是mapperMethod.execute(sqlSession, args);
繼續跟進程式碼會發現上面的例子最終執行的是
sqlSession.selectOne(command.getName(), param);
可謂殊途同歸.
mapper動態代理涉及到的類有MapperRegistry,MapperProxyFactory,MapperProxy,MapperMethod
MapperRegistry的資料來源頭Configuration.java
MapperRegistry mapperRegistry = new MapperRegistry(this);
XMLConfigBuilder中的方法parseConfiguration()呼叫mapperElement(root.evalNode("mappers"));
mapperElement()會呼叫addMapper()最後將資料新增到MapperRegistry中的knownMappers
分析完原始碼可以仿照以上程式碼來實現自己的功能:
public interface UserService {
Map getUser();
}
public class ServiceProxy implements InvocationHandler {
public <T> T newInstance(Class<T> clz) {
return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[]{clz}, this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
System.out.println("----proxy----" + method.getName());
return null;
}
}
//測試程式碼
public static void main(String[] args) {
UserService userService = new ServiceProxy().newInstance(UserService.class);
userService.toString();
userService.getUser();
}
//輸出結果
----proxy----getUser