美團(tuán)面試:為什么就能直接調(diào)用userMapper接口的方法?
老規(guī)矩,先上案例代碼,這樣大家可以更加熟悉是如何使用的,看過(guò)Mybatis系列的小伙伴,對(duì)這段代碼差不多都可以背下來(lái)了。
哈哈~,有點(diǎn)夸張嗎?不夸張的,就這行代碼。
- public class MybatisApplication {
- public static final String URL = "jdbc:mysql://localhost:3306/mblog";
- public static final String USER = "root";
- public static final String PASSWORD = "123456";
- public static void main(String[] args) {
- String resource = "mybatis-config.xml";
- InputStream inputStream = null;
- SqlSession sqlSession = null;
- try {
- inputStream = Resources.getResourceAsStream(resource);
- SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
- sqlSession = sqlSessionFactory.openSession();
- //今天主要這行代碼
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
- System.out.println(userMapper.selectById(1));
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- inputStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- sqlSession.close();
- }
- }
看源碼有什么用?
通過(guò)源碼的學(xué)習(xí),我們可以收獲Mybatis的核心思想和框架設(shè)計(jì),另外還可以收獲設(shè)計(jì)模式的應(yīng)用。
前兩篇文章我們已經(jīng)Mybatis配置文件解析到獲取SqlSession,下面我們來(lái)分析從SqlSession到userMapper:
- UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
前面那篇文章已經(jīng)知道了這里的sqlSession使用的是默認(rèn)實(shí)現(xiàn)類DefaultSqlSession。所以我們直接進(jìn)入DefaultSqlSession的getMapper方法。
- //DefaultSqlSession中
- private final Configuration configuration;
- //type=UserMapper.class
- @Override
- public <T> T getMapper(Class<T> type) {
- return configuration.getMapper(type, this);
- }
這里有三個(gè)問(wèn)題:
問(wèn)題1:getMapper返回的是個(gè)什么對(duì)象?
上面可以看出,getMapper方法調(diào)用的是Configuration中的getMapper方法。然后我們進(jìn)入Configuration中
- //Configuration中
- protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
- ////type=UserMapper.class
- public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
- return mapperRegistry.getMapper(type, sqlSession);
- }
這里也沒(méi)做什么,繼續(xù)調(diào)用MapperRegistry中的getMapper:
- //MapperRegistry中
- public class MapperRegistry {
- //主要是存放配置信息
- private final Configuration config;
- //MapperProxyFactory 的映射
- private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
- //獲得 Mapper Proxy 對(duì)象
- //type=UserMapper.class,session為當(dāng)前會(huì)話
- public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
- //這里是get,那就有add或者put
- final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
- if (mapperProxyFactory == null) {
- throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
- }
- try {
- //創(chuàng)建實(shí)例
- return mapperProxyFactory.newInstance(sqlSession);
- } catch (Exception e) {
- throw new BindingException("Error getting mapper instance. Cause: " + e, e);
- }
- }
- //解析配置文件的時(shí)候就會(huì)調(diào)用這個(gè)方法,
- //type=UserMapper.class
- public <T> void addMapper(Class<T> type) {
- // 判斷 type 必須是接口,也就是說(shuō) Mapper 接口。
- if (type.isInterface()) {
- //已經(jīng)添加過(guò),則拋出 BindingException 異常
- if (hasMapper(type)) {
- throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
- }
- boolean loadCompleted = false;
- try {
- //添加到 knownMappers 中
- knownMappers.put(type, new MapperProxyFactory<>(type));
- //創(chuàng)建 MapperAnnotationBuilder 對(duì)象,解析 Mapper 的注解配置
- MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
- parser.parse();
- //標(biāo)記加載完成
- loadCompleted = true;
- } finally {
- //若加載未完成,從 knownMappers 中移除
- if (!loadCompleted) {
- knownMappers.remove(type);
- }
- }
- }
- }
- }
MapperProxyFactory對(duì)象里保存了mapper接口的class對(duì)象,就是一個(gè)普通的類,沒(méi)有什么邏輯。
在MapperProxyFactory類中使用了兩種設(shè)計(jì)模式:
- 單例模式methodCache(注冊(cè)式單例模式)。
- 工廠模式getMapper()。
繼續(xù)看MapperProxyFactory中的newInstance方法。
- public class MapperProxyFactory<T> {
- private final Class<T> mapperInterface;
- private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
- public MapperProxyFactory(Class<T> mapperInterface) {
- this.mapperInterface = mapperInterface;
- }
- public T newInstance(SqlSession sqlSession) {
- //創(chuàng)建MapperProxy對(duì)象
- final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
- return newInstance(mapperProxy);
- }
- //最終以JDK動(dòng)態(tài)代理創(chuàng)建對(duì)象并返回
- protected T newInstance(MapperProxy<T> mapperProxy) {
- return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
- }
- }
從代碼中可以看出,依然是穩(wěn)穩(wěn)的基于 JDK Proxy 實(shí)現(xiàn)的,而 InvocationHandler 參數(shù)是 MapperProxy 對(duì)象。
- //UserMapper 的類加載器
- //接口是UserMapper
- //h是mapperProxy對(duì)象
- public static Object newProxyInstance(ClassLoader loader,
- Class<?>[] interfaces,
- InvocationHandler h){
- }
問(wèn)題2:為什么就可以調(diào)用他的方法?
上面調(diào)用newInstance方法時(shí)候創(chuàng)建了MapperProxy對(duì)象,并且是當(dāng)做newProxyInstance的第三個(gè)參數(shù),所以MapperProxy類肯定實(shí)現(xiàn)了InvocationHandler。
進(jìn)入MapperProxy類中:
- //果然實(shí)現(xiàn)了InvocationHandler接口
- public class MapperProxy<T> implements InvocationHandler, Serializable {
- private static final long serialVersionUID = -6424540398559729838L;
- 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;
- }
- //調(diào)用userMapper.selectById()實(shí)質(zhì)上是調(diào)用這個(gè)invoke方法
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- try {
- //如果是Object的方法toString()、hashCode()等方法
- if (Object.class.equals(method.getDeclaringClass())) {
- return method.invoke(this, args);
- } else if (method.isDefault()) {
- //JDK8以后的接口默認(rèn)實(shí)現(xiàn)方法
- return invokeDefaultMethod(proxy, method, args);
- }
- } catch (Throwable t) {
- throw ExceptionUtil.unwrapThrowable(t);
- }
- //創(chuàng)建MapperMethod對(duì)象
- final MapperMethod mapperMethod = cachedMapperMethod(method);
- //下一篇再聊
- return mapperMethod.execute(sqlSession, args);
- }
- }
也就是說(shuō),getMapper方法返回的是一個(gè)JDK動(dòng)態(tài)代理對(duì)象(類型是$Proxy+數(shù)字)。這個(gè)代理對(duì)象會(huì)繼承Proxy類,實(shí)現(xiàn)被代理的接口UserMpper,里面持有了一個(gè)MapperProxy類型的觸發(fā)管理類。
當(dāng)我們調(diào)用UserMpper的方法時(shí)候,實(shí)質(zhì)上調(diào)用的是MapperProxy的invoke方法。
- userMapper=$Proxy6@2355。
為什么要在MapperRegistry中保存一個(gè)工廠類?
原來(lái)他是用來(lái)創(chuàng)建并返回代理類的。這里是代理模式的一個(gè)非常經(jīng)典的應(yīng)用。
MapperProxy如何實(shí)現(xiàn)對(duì)接口的代理?
JDK動(dòng)態(tài)代理
我們知道,JDK動(dòng)態(tài)代理有三個(gè)核心角色:
- 被代理類(即就是實(shí)現(xiàn)類)
- 接口
- 實(shí)現(xiàn)了InvocationHanndler的觸發(fā)管理類,用來(lái)生成代理對(duì)象。
被代理類必須實(shí)現(xiàn)接口,因?yàn)橐ㄟ^(guò)接口獲取方法,而且代理類也要實(shí)現(xiàn)這個(gè)接口。
而Mybatis中并沒(méi)有Mapper接口的實(shí)現(xiàn)類,怎么被代理呢?它忽略了實(shí)現(xiàn)類,直接對(duì)Mapper接口進(jìn)行代理。
MyBatis動(dòng)態(tài)代理:
在Mybatis中,JDK動(dòng)態(tài)代理為什么不需要實(shí)現(xiàn)類呢?
這里我們的目的其實(shí)就是根據(jù)一個(gè)可以執(zhí)行的方法,直接找到Mapper.xml中statement ID ,方便調(diào)用。
最后返回的userMapper就是MapperProxyFactory的創(chuàng)建的代理對(duì)象,然后這個(gè)對(duì)象中包含了MapperProxy對(duì)象,
問(wèn)題3:到底是怎么根據(jù)Mapper.java找到Mapper.xml的?
最后我們調(diào)用userMapper.selectUserById(),本質(zhì)上調(diào)用的是MapperProxy的invoke()方法。
請(qǐng)看下面這張圖:
如果根據(jù)(接口+方法名找到Statement ID ),這個(gè)邏輯在InvocationHandler子類(MapperProxy類)中就可以完成了,其實(shí)也就沒(méi)有必要在用實(shí)現(xiàn)類了。
總結(jié)
本文中主要是講getMapper方法,該方法實(shí)質(zhì)上是獲取一個(gè)JDK動(dòng)態(tài)代理對(duì)象(類型是Proxy+數(shù)字),這個(gè)代理類會(huì)繼承MapperProxy類,實(shí)現(xiàn)被代理的接口UserMapper,并且里面持有一個(gè)MapperProxy類型的觸發(fā)管理類。這里我們就拿到代理類了,后面我們就可以使用這個(gè)代理對(duì)象進(jìn)行方法調(diào)用。
問(wèn)題涉及到的設(shè)計(jì)模式:
- 代理模式。
- 工廠模式。
- 單例模式。
整個(gè)流程圖:
本文轉(zhuǎn)載自微信公眾號(hào)「Java后端技術(shù)全?!梗梢酝ㄟ^(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java后端技術(shù)全棧公眾號(hào)。