美文网首页
面试官:为什么Mybatis就能直接调用userMapper接口

面试官:为什么Mybatis就能直接调用userMapper接口

作者: 一只程序猿哟 | 来源:发表于2021-12-23 13:40 被阅读0次

    先上案例代码,这样大家可以更加熟悉是如何使用的,看过Mybatis系列的小伙伴,对这段代码差不多都可以背下来了。

    哈哈~,有点夸张吗?不夸张的,就这行代码。

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

                }

            }

    看源码有什么用?

    通过源码的学习,我们可以收获Mybatis的核心思想和框架设计,另外还可以收获设计模式的应用。

    Mybatis配置文件解析到获取SqlSession,下面我们来分析从SqlSession到userMapper:

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

    已经知道了这里的sqlSession使用的是默认实现类DefaultSqlSession。所以我们直接进入DefaultSqlSession的getMapper方法。

    //DefaultSqlSession中 

        private final Configuration configuration;

        //type=UserMapper.class

        @Override

        public <T> T getMapper(Class<T> type) {

          return configuration.getMapper(type, this);

        }

    这里有三个问题:

    问题1:getMapper返回的是个什么对象?

    上面可以看出,getMapper方法调用的是Configuration中的getMapper方法。然后我们进入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);

        }

    这里也没做什么,继续调用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类中使用了两种设计模式:

    单例模式methodCache(注册式单例模式)。

    工厂模式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。

    为什么要在MapperRegistry中保存一个工厂类?

    原来他是用来创建并返回代理类的。这里是代理模式的一个非常经典的应用。

    MapperProxy如何实现对接口的代理?

    JDK动态代理

    我们知道,JDK动态代理有三个核心角色:

    被代理类(即就是实现类)

    接口

    实现了InvocationHanndler的触发管理类,用来生成代理对象。

    被代理类必须实现接口,因为要通过接口获取方法,而且代理类也要实现这个接口。

    而Mybatis中并没有Mapper接口的实现类,怎么被代理呢?它忽略了实现类,直接对Mapper接口进行代理。

    MyBatis动态代理:

    在Mybatis中,JDK动态代理为什么不需要实现类呢?

    请看下面这张图:

    最后返回的userMapper就是MapperProxyFactory的创建的代理对象,然后这个对象中包含了MapperProxy对象,

    问题3:到底是怎么根据Mapper.java找到Mapper.xml的?

    最后我们调用userMapper.selectUserById(),本质上调用的是MapperProxy的invoke()方法。

    请看下面这张图:

    如果根据(接口+方法名找到Statement ID ),这个逻辑在InvocationHandler子类(MapperProxy类)中就可以完成了,其实也就没有必要在用实现类了。

    总结

    本文中主要是讲getMapper方法,该方法实质上是获取一个JDK动态代理对象(类型是Proxy+数字),这个代理类会继承MapperProxy类,实现被代理的接口UserMapper,并且里面持有一个MapperProxy类型的触发管理类。这里我们就拿到代理类了,后面我们就可以使用这个代理对象进行方法调用。

    问题涉及到的设计模式:

    代理模式。

    工厂模式。

    单例模式。

    整个流程图:

    相关文章

      网友评论

          本文标题:面试官:为什么Mybatis就能直接调用userMapper接口

          本文链接:https://www.haomeiwen.com/subject/ymphqrtx.html