美團面試題:爲何能直接調用userMapper接口的方法?

字數:2434,閱讀耗時:3分40秒。java

老規矩,先上案例代碼,這樣你們能夠更加熟悉是如何使用的,看過Mybatis系列的小夥伴,對這段代碼差很少均可以背下來了。mysql

哈哈~,有點誇張嗎?不誇張的,就這行代碼。sql

    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();
            }
        }

看源碼有什麼用?設計模式

 

img

 

經過源碼的學習,咱們能夠收穫Mybatis的核心思想和框架設計,另外還能夠收穫設計模式的應用。session

前兩篇文章咱們已經Mybatis配置文件解析獲取SqlSession,下面咱們來分析從SqlSession到userMapper:mybatis

     UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

前面那篇文章已經知道了這裏的sqlSession使用的是默認實現類DefaultSqlSession。因此咱們直接進入DefaultSqlSession的getMapper方法。app

    //DefaultSqlSession中  
    private final Configuration configuration;
    //type=UserMapper.class
    @Override
    public <T> T getMapper(Class<T> type) {
      return configuration.getMapper(type, this);
    }

這裏有三個問題:框架

 

img

 

問題1:getMapper返回的是個什麼對象?

上面能夠看出,getMapper方法調用的是Configuration中的getMapper方法。而後咱們進入Configuration中ide

    //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);
    }

這裏也沒作什麼,繼續調用MapperRegistry中的getMapper:學習

    //MapperRegistry中
    public class MapperRegistry {
      //主要是存放配置信息
      private final Configuration config;
      //MapperProxyFactory 的映射
      private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

      //得到 Mapper Proxy 對象
      //type=UserMapper.class,session爲當前會話
      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 {
          //建立實例
          return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
      }

      //解析配置文件的時候就會調用這個方法,
      //type=UserMapper.class
      public <T> void addMapper(Class<T> type) {
        // 判斷 type 必須是接口,也就是說 Mapper 接口。
        if (type.isInterface()) {
            //已經添加過,則拋出 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));
                //建立 MapperAnnotationBuilder 對象,解析 Mapper 的註解配置
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                parser.parse();
                //標記加載完成
                loadCompleted = true;
            } finally {
                //若加載未完成,從 knownMappers 中移除
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
    }

MapperProxyFactory對象裏保存了mapper接口的class對象,就是一個普通的類,沒有什麼邏輯。

在MapperProxyFactory類中使用了兩種設計模式:

  1. 單例模式methodCache(註冊式單例模式)。

  2. 工廠模式getMapper()。

繼續看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) {
      //建立MapperProxy對象
      final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
      return newInstance(mapperProxy);
    }
    //最終以JDK動態代理建立對象並返回
     protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
    }

從代碼中能夠看出,依然是穩穩的基於 JDK Proxy 實現的,而 InvocationHandler 參數是 MapperProxy 對象。

    //UserMapper 的類加載器
    //接口是UserMapper
    //h是mapperProxy對象
    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                           InvocationHandler h){
    }

問題2:爲何就能夠調用他的方法?

上面調用newInstance方法時候建立了MapperProxy對象,而且是當作newProxyInstance的第三個參數,因此MapperProxy類確定實現了InvocationHandler。

進入MapperProxy類中:

    //果真實現了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;
      }
      //調用userMapper.selectById()實質上是調用這個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之後的接口默認實現方法  
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
        //建立MapperMethod對象
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //下一篇再聊
        return mapperMethod.execute(sqlSession, args);
      }
    }

也就是說,getMapper方法返回的是一個JDK動態代理對象(類型是$Proxy+數字)。這個代理對象會繼承Proxy類,實現被代理的接口UserMpper,裏面持有了一個MapperProxy類型的觸發管理類。

當咱們調用UserMpper的方法時候,實質上調用的是MapperProxy的invoke方法。

userMapper=$Proxy6@2355。

 

img

 

爲何要在MapperRegistry中保存一個工廠類?

原來他是用來建立並返回代理類的。這裏是代理模式的一個很是經典的應用。

 

img

 

MapperProxy如何實現對接口的代理?

JDK動態代理

咱們知道,JDK動態代理有三個核心角色:

  • 被代理類(即就是實現類)

  • 接口

  • 實現了InvocationHanndler的觸發管理類,用來生成代理對象。

被代理類必須實現接口,由於要經過接口獲取方法,並且代理類也要實現這個接口。

 

img

 

而Mybatis中並無Mapper接口的實現類,怎麼被代理呢?它忽略了實現類,直接對Mapper接口進行代理。

MyBatis動態代理:

在Mybatis中,JDK動態代理爲何不須要實現類呢?

 

img

 

這裏咱們的目的其實就是根據一個能夠執行的方法,直接找到Mapper.xml中statement ID ,方便調用。

最後返回的userMapper就是MapperProxyFactory的建立的代理對象,而後這個對象中包含了MapperProxy對象,

問題3:究竟是怎麼根據Mapper.java找到Mapper.xml的?

最後咱們調用userMapper.selectUserById(),本質上調用的是MapperProxy的invoke()方法。

請看下面這張圖:

 

img

 

若是根據(接口+方法名找到Statement ID ),這個邏輯在InvocationHandler子類(MapperProxy類)中就能夠完成了,其實也就沒有必要在用實現類了。

 

img

 

總結

本文中主要是講getMapper方法,該方法實質上是獲取一個JDK動態代理對象(類型是Proxy+數字),這個代理類會繼承MapperProxy類,實現被代理的接口UserMapper,而且裏面持有一個MapperProxy類型的觸發管理類。這裏咱們就拿到代理類了,後面咱們就可使用這個代理對象進行方法調用。

問題涉及到的設計模式:

  1. 代理模式。

  2. 工廠模式。

  3. 單例模式。

整個流程圖:

 

img

 

冰凍三尺,非一日之寒表面意義是冰凍了三尺,並非一天的寒冷所能達到的效果。學習亦如此,你每一天的一點點努力,都是爲你之後的成功作鋪墊。

相關文章
相關標籤/搜索