zoukankan      html  css  js  c++  java
  • 【Mybatis进阶】动态代理

    如果你想更深刻的理解Mybatis动态代理的原理,那么你应该先知道

    • 什么是代理模式?
    • 在没有动态代理的时候Mybatis是如何实现dao层的?

    什么是代理模式

    具体可以阅读笔者的博客—— 代理模式

    在没有动态代理的时候Mybatis是如何实现dao层的

    本篇博客基于mybatis的环境已经搭建完成,如果不知道如何搭建,具体可以阅读笔者的博客——【从零开始学Mybatis笔记(三)】Dao开发方法

    1. 接口
    public interface UserMapper {
        
        List<User> findAll();
    }
    
    1. xml
        <select id="findAll" resultType="User">
             select *
             from user
        </select>
    
    1. 实现类
    public class UserMapperImpl implements UserMapper {
    
        private SqlSessionFactory factory;
    
        public UserMapperImpl(SqlSessionFactory factory) {
            this.factory = factory;
        }
    
        @Override
        public List<User> findAll() {
            //1.获取sqlSession对象
            SqlSession sqlSession = factory.openSession();
            //2.调用selectList方法
            List<User> list = sqlSession.selectList("com.tyust.dao.UserMapper.findAll");
            //3.关闭流
            sqlSession.close();
            return list;
        }
    }
    

    到这里我们知道,Mybatis实现的关键就在于List<User> list = sqlSession.selectList("com.example.dao.UserDao.findAll")中通过sqlSession来实现对相应方法的操作。其实动态代理的最终结果也是使用sqlSession的相应方法。

    Mybatis动态代理的实现原理

        @Override
        public List<User> findAll() {
            //1.获取sqlSession对象
            SqlSession sqlSession = factory.openSession();
            //2.调用selectList方法
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> list = mapper.findAll();
            //3.关闭流
            sqlSession.close();
            return list;
        }
    

    和Dao操作一样,只不过使用了getMapper()来获得一个代理类,进行所有的操作。所以这就是我们关注的关键对象。

    在单步调试之前,我们需要知道动态代理中最重要的类:SqlSession、MapperProxy、MapperMethod。

    好了我们开始吧

    1. 进入UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    因为SqlSesseion为接口,所以我们通过Debug方式发现这里使用的实现类为DefaultSqlSession。

    @Override
      public <T> T getMapper(Class<T> type) {
        return configuration.<T>getMapper(type, this);
      }
    

    找到DeaultSqlSession中的getMapper方法,发现这里没有做其他的动作,只是将工作继续抛到了Configuration类中,Configuration为类不是接口,可以直接进入该类的getMapper方法中。

    1. 进入configuration.<T>getMapper(type, this)
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
      }
    

    Configuration是MyBatis初始化后全局唯一的配置对象,它内部保存着配置文件解析过程中所有的配置信息。找到Configuration类的getMapper方法,这里也是将工作继续交到MapperRegistry的getMapper的方法中,所以我们继续向下进行。

    1. 进入mapperRegistry.getMapper(type, sqlSession)
      @SuppressWarnings("unchecked")
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        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);
        }
      }
    

    MapperRegistry是Mapper接口动态代理工厂类的注册中心。找到MapperRegistry的getMapper的方法,看到这里发现和以前不一样了。

    首先Type就是我们的接口UserMapper;

    mapperProxyFactory一个HashMap,private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();; 通过 (MapperProxyFactory<T>) knownMappers.get(type);我们就获得了MapperProxyFactory。
    通过MapperProxyFactory的命名方式我们知道这里将通过这个工厂生成我们所关注的MapperProxy的代理类,然后我们通过mapperProxyFactory.newInstance(sqlSession);进入MapperProxyFactory的newInstance方法中。

    1. 进入mapperProxyFactory.newInstance(sqlSession);
      @SuppressWarnings("unchecked")
      protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    

    找到MapperProxyFactory的newIntance方法,通过参数类型SqlSession可以得知,上面的调用先进入第二个newInstance方法中并创建我们所需要重点关注的MapperProxy对象,第二个方法中再调用第一个newInstance方法并将MapperProxy对象传入进去,根据该对象创建代理类并返回。看到Proxy.newProxyInstance,我们应该就知道这是JDK的动态代理api,这里已经得到需要的代理类了,但是我们的代理类所做的工作还得继续向下看MapperProxy类。

    1. MapperProxy类

    找到MapperProxy类,发现其确实实现了JDK动态代理必须实现的接口InvocationHandler,所以我们重点关注invoke()方法。

    @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          } else {
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }
    

    method就是我们的findAll()方法,而proxy就是之前通过newInstance生成的代理类。

    getDeclaringClass()方法返回表示声明由此Method对象表示的方法的类的Class对象,说白了就是UserMapper。这里的判断就是方法是不是代理本身的方法,如果是,直接调用方法。如果不是,通过缓存方法生成一个对象再调用。具体是什么对象呢,我们看cachedInvoker方法。

    1. MapperMethodInvoker

    在看cachedInvoker方法之前,我们先了解一下,什么是MapperMethodInvoker。

      interface MapperMethodInvoker {
        Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
      }
    
      private static class PlainMethodInvoker implements MapperMethodInvoker {
        private final MapperMethod mapperMethod;
    
        public PlainMethodInvoker(MapperMethod mapperMethod) {
          super();
          this.mapperMethod = mapperMethod;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          return mapperMethod.execute(sqlSession, args);
        }
      }
    
      private static class DefaultMethodInvoker implements MapperMethodInvoker {
        private final MethodHandle methodHandle;
    
        public DefaultMethodInvoker(MethodHandle methodHandle) {
          super();
          this.methodHandle = methodHandle;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          return methodHandle.bindTo(proxy).invokeWithArguments(args);
        }
      }
    }
    
    

    我们可以看到MapperMethodInvoker 是MapperProxy的一个内部接口,有两个实现类DefaultMethodInvoker和PlainMethodInvoker,二者都有一个构造函数和一个invoke方法,区别就在于构造函数封装的实例和invoke方法的逻辑。

    PlainMethodInvoker类:是Mapper接口普通方法的调用类,它实现了MethodInvoker接口。其内部封装了MapperMethod实例。
    DefaultMethodInvoker类:MethodInvoker接口的默认方法实现类。其内部封装了MethodHandle 实例。

    MapperMethod:封装了Mapper接口中对应方法的信息,以及对应的SQL语句的信息;它是mapper接口与映射配置文件中SQL语句的桥梁。

    Mapper接口中的每一个方法都对应一个MapperMethodInvoker对象,而MapperMethodInvoker对象里面的MapperMethod保存着对应的SQL信息和返回类型以完成SQL调用。

    1. 进入cachedInvoker方法
      private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
          // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
          // It should be removed once the fix is backported to Java 8 or
          // MyBatis drops Java 8 support. See gh-1929
          MapperMethodInvoker invoker = methodCache.get(method);
          if (invoker != null) {
            return invoker;
          }
    
          return methodCache.computeIfAbsent(method, m -> {
            if (m.isDefault()) {
              try {
                if (privateLookupInMethod == null) {
                  return new DefaultMethodInvoker(getMethodHandleJava8(method));
                } else {
                  return new DefaultMethodInvoker(getMethodHandleJava9(method));
                }
              } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                  | NoSuchMethodException e) {
                throw new RuntimeException(e);
              }
            } else {
              return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            }
          });
        } catch (RuntimeException re) {
          Throwable cause = re.getCause();
          throw cause == null ? re : cause;
        }
      }
    

    根据被调用接口方法的Method对象,从缓存中获取MapperMethodInvoker对象,如果没有则创建一个并放入缓存。然后会判断用户当前调用的是否是接口的default方法,如果不是就会创建一个PlainMethodInvoker对象并返回。

    搞懂了cacheInvoker,我们再看 return cachedInvoker(method).invoke(proxy, method, args, sqlSession);,可以发现当cacheInvoker返回了PalinMethodInvoker实例之后,紧接着调用了这个实例的PlainMethodInvoker::invoke方法。进入PlainMethodInvoker::invoke方法我们发现它底层调用的是MapperMethod::execute方法

    1. 进入mapperMethod.execute(sqlSession, args)
      public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
          case INSERT: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
          }
          case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
          }
          case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
          }
          case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
              executeWithResultHandler(sqlSession, args);
              result = null;
            } else if (method.returnsMany()) {
              result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
              result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
              result = executeForCursor(sqlSession, args);
            } else {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = sqlSession.selectOne(command.getName(), param);
              if (method.returnsOptional()
                  && (result == null || !method.getReturnType().equals(result.getClass()))) {
                result = Optional.ofNullable(result);
              }
            }
            break;
          case FLUSH:
            result = sqlSession.flushStatements();
            break;
          default:
            throw new BindingException("Unknown execution method for: " + command.getName());
        }
        if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
          throw new BindingException("Mapper method '" + command.getName()
              + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
        }
        return result;
      }
    

    MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进行了封装使用。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。MethodSignature用来封装方法的参数以及返回类型,在execute的方法中我们发现在这里又回到了SqlSession中的接口调用,和我们自己实现UerDao接口的方式中直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。

    参考文献

    Mybatis mapper动态代理的原理详解
    https://blog.csdn.net/tianjindong0804/article/details/105862940
    手把手带你阅读Mybatis源码(一)构造篇
    手把手带你阅读Mybatis源码(二)执行篇

  • 相关阅读:
    C++中的乱七八糟问题
    在Win环境下配置java的环境进行开发步骤
    常用软件破解
    关于QT建立项目中遇到的相关问题的处理办法
    QT5.4.0安装以及与VS2010整合安装---64bit操作系统解决方案
    STL容器之一vector
    STL
    三种初步简易的方法求解数值问题 of C++
    Visual Studio 简单使用常识操作
    江城感怀---诗一首
  • 原文地址:https://www.cnblogs.com/zllk/p/14245159.html
Copyright © 2011-2022 走看看