关注“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下载