关于java:美团面试为什么就能直接调用userMapper接口的方法

5次阅读

共计 7726 个字符,预计需要花费 20 分钟才能阅读完成。

关注“Java 后端技术全栈”

回复“面试”获取全套面试材料

字数:2434,浏览耗时:3 分 40 秒。

老规矩,先上案例代码,这样大家能够更加相熟是如何应用的,看过 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 类中应用了两种设计模式:

  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。

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

原来他是用来创立并返回代理类的。这里是代理模式的一个十分经典的利用。

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

JDK 动静代理

咱们晓得,JDK 动静代理有三个外围角色:

  • 被代理类(即就是实现类)
  • 接口
  • 实现了 InvocationHanndler 的触发治理类,用来生成代理对象。

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

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

MyBatis 动静代理:

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

这里咱们的目标其实就是依据一个能够执行的办法,间接找到 Mapper.xml 中 statement ID,不便调用。

最初返回的 userMapper 就是 MapperProxyFactory 的创立的代理对象,而后这个对象中蕴含了 MapperProxy 对象,

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

最初咱们调用 userMapper.selectUserById(),实质上调用的是 MapperProxy 的 invoke() 办法。

请看上面这张图:

如果依据 (接口 + 办法名找到 Statement ID),这个逻辑在 InvocationHandler 子类(MapperProxy 类)中就能够实现了,其实也就没有必要在用实现类了。

总结

本文中次要是讲 getMapper 办法,该办法本质上是获取一个 JDK 动静代理对象(类型是 Proxy+ 数字), 这个代理类会继承 MapperProxy 类,实现被代理的接口 UserMapper,并且外面持有一个 MapperProxy 类型的触发治理类。这里咱们就拿到代理类了,前面咱们就能够应用这个代理对象进行办法调用。

问题波及到的设计模式:

  1. 代理模式。
  2. 工厂模式。
  3. 单例模式。

整个流程图:

冰冻三尺,非一日之寒外表意义是冰冻了三尺,并不是一天的凛冽所能达到的成果。学习亦如此,你每一天的一点点致力,都是为你当前的胜利做铺垫。

举荐浏览

面试官:Integer 缓存最大范畴只能是 -128 到 127 吗?

6000 多字 | 秒杀零碎设计留神点【实践】

面试官:说说你对 Java 异样的了解

《程序员面试宝典》.pdf 下载

正文完
 0