zoukankan      html  css  js  c++  java
  • Mybatis苞米豆源码分析二-方法执行

    执行具体过程(集成到spring)

    1. 找到扫包类ClassPathMapperScanner,和以往的扫包形式一样,扫描包下所有类, 并获得BeanDefinition
    2. 基于BeanDefinition,通过设置definition.setBeanClass,然后在spring 容器中通过getBean的方式获取Mapper对象(此时是基础对象下面要继续织入插件)
    3. Mapper对象只有简单持有sqlSession来做数据库操作的能力, 而Mybatis提供了插件的功能, 就需要已实例化的Mapper对象进行再次代理, 将插件能力用方法拦截的方式编织到进去(插件要编织代码具体执行位置依据实际情况)

    Mybatis知识: 重重代理之后最终操作数据库执行链 Executor -> StatementHandler -> statement.excute()-> ResultHander.handleResultSets(statement)

    //spring-mybatis扫包注解 实例化ClassPathMapperScanner 并设置所需属性值, 属spring 范畴,忽略掉,直接关注ClassPathMapperScanner
    @MapperScan("com.moredian.audit.dao.mapper")
    public class MybatisPlusConfig {
        忽略.......
    }

    查看最关心的doScan与processBeanDefinitions方法

    ClassPathMapperScanner

     @Override
      public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        //使用spring自带扫包方式 先得到所有定义类
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    
        if (beanDefinitions.isEmpty()) {
          logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
          // 具体Bean处理方法 接着往下看
          processBeanDefinitions(beanDefinitions);
        }
    
        return beanDefinitions;
      }
      
      //实际是对定义类的一系列设置
      private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
      
        忽略若干行......
        
        //definition.setBeanClass 很眼熟的代码, 设置FactoryBean 后续spring容器通过FactoryBean的getObject()方法得到具体代理对象
        //getObject的实现在 MapperFactoryBean中实现, 接下来所有的东西都是围绕MapperFactoryBean的getObject来实现的
        definition.setBeanClass(this.mapperFactoryBean.getClass());
        
        忽略若干行.......
        
      }

    实例化Mapper对象

    MapperFactoryBean

    @Override
      public T getObject() throws Exception {
        //这里使用spring-mybatis集成 使用的是getSqlSession()=SqlSessionTemplate
        return getSqlSession().getMapper(this.mapperInterface);
      }

    委托SqlSessionTemplate来实例化Mapper

    SqlSessionTemplate

    @Override
      public <T> T getMapper(Class<T> type) {
        //使用mybatis configuration来实例化Mapper
        return getConfiguration().getMapper(type, this);
      }
    
      @Override
      public Configuration getConfiguration() {
        //获取sqlSessionFactory中的configuration
        //在上一节MybatisPlusAutoConfiguration初始化过程中 sqlSessionFactory的configuration设置为苞米豆重写类
        return this.sqlSessionFactory.getConfiguration();
      }

    继续调用方法getMapper

    MybatisConfiguration

    @Override
        public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
            return mybatisMapperRegistry.getMapper(type, sqlSession);
        }

    MybatisMapperRegistry

    @Override
        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 MybatisPlusMapperRegistry.");
            }
            try {
                //在这里会通过代理实例化一个Mapper对象 最终使用MapperProxy实例化代理对象
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception e) {
                throw new BindingException("Error getting mapper instance. Cause: " + e, e);
            }
        }

    直接看invoke方法,Mapper每个方法都会被此方法代理执行, jdk代理方式 不过多解释

    MapperProxy

    @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 if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
        //此处为执行方法类 这个方法很重要, 后面查询时, 对于是否查询条目边界, 就在cachedMapperMethod方法中设置, 判断Mapper方法参数是否含有有RowBounds子类(举例:Page extends RowBounds)
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
      }

    到这里 Mapper代理实例化方式和方法执行过程基本结束, 接下来看Mapper方法的具体执行, 接上面最后一行 mapperMethod.execute(sqlSession, args) 以查询列表为例查看源码

    MapperMethod

    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        List<E> result;
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
          RowBounds rowBounds = method.extractRowBounds(args);
          //通过看前面代码可只此处sqlSession的实现为Spring的 SqlSessionTemplate, 但其实SqlSessionTemplate并没有实现Sqlsession的功能,而是委托给Mybatis自带的DefaultSqlSession来完成操作
          result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
        } else {
         //通过看前面代码可只此处sqlSession的实现为Spring的 SqlSessionTemplate, 但其实SqlSessionTemplate并没有实现Sqlsession的功能,而是委托给Mybatis自带的DefaultSqlSession来完成操作
          result = sqlSession.<E>selectList(command.getName(), param);
        }
       
        忽略.......
        return result;
      }

    实际执行者为DefaultSqlSession, 接着往下看selectList

    DefaultSqlSession

    @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
          //在上一节解析XML的时候 已经将所有的statement设置到configuration 此处直接取用
          MappedStatement ms = configuration.getMappedStatement(statement);
          //executor 有两个实现类BaseExecutor 和 cachedExecutor(二级缓存), 为了方便代码追踪, 不开二级缓存,使用BaseExecutor来执行
          return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }

    我们更关注的是从数据库取数据的执行过程, 直接跳到queryFromDatabase方法

    BaseExecutor

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        
        忽略缓存代码....
        try {
          list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
          localCache.removeObject(key);
        }
        忽略缓存代码....
        return list;
      }
      
      
      //下面这段代码为此篇文章最为重点的代码
      @Override
      public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
          throws SQLException {
        Statement stmt = null;
        try {
          //啥也没干 不需要关心
          flushStatements();
          //获取 苞米豆Configuration
          Configuration configuration = ms.getConfiguration();
          //这里是mybatis插件核心所在 非常重要 直接进入方法
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
          Connection connection = getConnection(ms.getStatementLog());
          stmt = handler.prepare(connection, transaction.getTimeout());
          handler.parameterize(stmt);
          return handler.<E>query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }

    实例化具体Statement执行器

    Configuration

    //实例化Statement执行器
    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        //创建RoutingStatementHandler对象 用于执行Statement, 其实从名字可以看出RoutingStatementHandler 并不做真正的处理, 而是将处理过程交给其他基础StatementHandler实现类
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        //这里就是Mybatis的精髓所在了 将创建的RoutingStatementHandler对象再次代理, 添加插件执行功能, 进入interceptorChain.pluginAll 看具体如何代理的
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }

    通过jdk代理方式 植入插件调用链

    InterceptorChain

      //将所有的插件interceptors 通过代理方式植入到目标对象中
      public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
          // 从下面这段代码可以简单看出,要么给target通过反射方式设置属性,要么通过jdk重新代理当前target, 这里根据自己需要实现拦截器(也即插件) 
          // 下面从苞米豆分页插件(PaginationInterceptor)为例子来看怎么实现
          target = interceptor.plugin(target);
        }
        return target;
      }

    分页插件织入

    PaginationInterceptor

     @Override
        public Object plugin(Object target) {
            if (target instanceof StatementHandler) {
                //具体包装交给Plugin工具类来做的 直接看工具类wrap方法
                return Plugin.wrap(target, this);
            }
            return target;
        }

    mybatis插件执行包装

    Plugin

    public static Object wrap(Object target, Interceptor interceptor) {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
          //这段代码表明 代理方法实现在Plugin 直接进Plugin查看invoke方法
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
        }
        return target;
      }
      
      
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          //获取当前类 所有需要拦截的方法
          Set<Method> methods = signatureMap.get(method.getDeclaringClass());
          //若方法需要拦截 则前置执行拦截器方法  
          if (methods != null && methods.contains(method)) {
            //具体的方法执行Invocation在intercept中执行, 当然这取决于是否真的需要执行
            return interceptor.intercept(new Invocation(target, method, args));
          }
          //若方法不需要拦截 则直接执行方法
          return method.invoke(target, args);
        } catch (Exception e) {
          throw ExceptionUtil.unwrapThrowable(e);
        }
      }
     
  • 相关阅读:
    hdu 2020
    hdu 1005
    hdu1014
    集合(一)
    史上最全的 Java 新手问题汇总
    表单验证
    oracle优化原则(二)
    vue的学习--如何使用Intellij IDEA配置并运行vue项目
    在win10环境下IED配置spark项目
    在win10环境下配置spark和scala
  • 原文地址:https://www.cnblogs.com/xieyanke/p/12143530.html
Copyright © 2011-2022 走看看