zoukankan      html  css  js  c++  java
  • MyBatis源码分析(三)

    之前已经讲了一个MyBatis项目的基本配置,接下来我们来通过断点跟踪,一步一步揭开它的神秘面纱.

    @Slf4j
    public class App 
    {
        public static void main( String[] args ) throws IOException {
            //通过配置文件获取输入流
            InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
            //构建SqlSessionFactory
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            //打开session
            SqlSession sqlSession = sqlSessionFactory.openSession();
            //第四步 获取Mapper接口对象
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            //第五步 调用Mapper接口对象的方法操作数据库
            User user = mapper.selectByPrimaryKey(1);
            //获取结果,处理业务
            log.info("查询结果:",user.getId());
        }
    }

    1.如何通过配置文件获取输入流

    //第一步,调用
      public static InputStream getResourceAsStream(String resource) throws IOException {
        return getResourceAsStream(null, resource);
      }
    //第二步调用
      public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
        InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
        if (in == null) {
          throw new IOException("Could not find resource " + resource);
        }
        return in;
      }

    通过进入源码,我们可以看到通过配置文件获取输入流最核心的一步  classLoaderWrapper.getResourceAsStream(resource, loader);

    那么classLoaderWrapper这个成员变量又是如何来的呢?原来是Resource的静态成员变量

      private static ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper();

    在静态变量实例化的时候,我把关键的几行代码粘出来.只是给成员变量systemClassLoader赋值.

    public class ClassLoaderWrapper {
    
      ClassLoader defaultClassLoader;
      ClassLoader systemClassLoader;
    
      ClassLoaderWrapper() {
        try {
          systemClassLoader = ClassLoader.getSystemClassLoader();
        } catch (SecurityException ignored) {
          // AccessControlException on Google App Engine
        }
      }
      ClassLoader[] getClassLoaders(ClassLoader classLoader) {
        return new ClassLoader[]{
            classLoader,
            defaultClassLoader,
            Thread.currentThread().getContextClassLoader(),
            getClass().getClassLoader(),
            systemClassLoader};
      }
    
    }

    我们回到classLoaderWrapper.getResourceAsStream(resource, loader),可以看出最终是通过类加载器去读取配置文件获取输入流

      public InputStream getResourceAsStream(String resource) {
        return getResourceAsStream(resource, getClassLoaders(null));
      }
    
      InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
        for (ClassLoader cl : classLoader) {
          if (null != cl) {
    
            // try to find the resource as passed
            InputStream returnValue = cl.getResourceAsStream(resource);
    
            // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
            if (null == returnValue) {
              returnValue = cl.getResourceAsStream("/" + resource);
            }
    
            if (null != returnValue) {
              return returnValue;
            }
          }
        }
        return null;
      }

    类加载器是一个数组,一共五个类加载器,前两个初次是没有赋值的,可以最终的到一个APP-classLoader,根据;类加载机制双亲委派,三种类型加载器

    BootStrapClassLoader-ExtClassLoader-APPClassLoader

      ClassLoader[] getClassLoaders(ClassLoader classLoader) {
        return new ClassLoader[]{
            classLoader,
            defaultClassLoader,
            Thread.currentThread().getContextClassLoader(),
            getClass().getClassLoader(),
            systemClassLoader};
      }

    第一步,我们可以总结为简单的一句话:通过类加载器读取类的根路径下的配置文件获取输入流,我们以后的开发也可以借鉴这种方式

    2.构建SqlSessionFactory

     //构建SqlSessionFactory
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    这一步主要是build方法,前面的创建对象调用默认无参构造,并没有做什么事情.

      public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
      }

    调用同一个类的重载方法

      public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            inputStream.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }

    重点在于:XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

    我们通过传递可知,后两个参数environment为null,properties为null.

      public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
      }
    new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())这一步到底做了什么?
    首先看下new XMLMapperEntityResolver(),你会在下图看到熟悉的dtd,你会想到什么?这一步也只是初始化了内部成员变量.

    我们再看下new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())下一步调用
     public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
        commonConstructor(validation, variables, entityResolver);
        this.document = createDocument(new InputSource(inputStream));
      }
    首先进入第一个方法内部一探究竟,给成员变量赋值
      private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
        this.validation = validation;
        this.entityResolver = entityResolver;
        this.variables = variables;
        XPathFactory factory = XPathFactory.newInstance();
        this.xpath = factory.newXPath();
      }

    然后是通过输入流,构建Document对象,赋值给成员变量document

    
    
      private Document createDocument(InputSource inputSource) {
        // important: this must only be called AFTER common constructor
        try {
          DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
          factory.setValidating(validation);
    
          factory.setNamespaceAware(false);
          factory.setIgnoringComments(true);
          factory.setIgnoringElementContentWhitespace(false);
          factory.setCoalescing(false);
          factory.setExpandEntityReferences(true);
    
          DocumentBuilder builder = factory.newDocumentBuilder();
          builder.setEntityResolver(entityResolver);
          builder.setErrorHandler(new ErrorHandler() {
            @Override
            public void error(SAXParseException exception) throws SAXException {
              throw exception;
            }
    
            @Override
            public void fatalError(SAXParseException exception) throws SAXException {
              throw exception;
            }
    
            @Override
            public void warning(SAXParseException exception) throws SAXException {
            }
          });
          return builder.parse(inputSource);
        } catch (Exception e) {
          throw new BuilderException("Error creating document instance.  Cause: " + e, e);
        }
      }
    这里面一大堆到底做了什么呢,大致就是设置xml的验证,毕竟我们要保证xml的配置是符合MyBatis的配置书写规则,不然后续的解析也是有问题的.
    XPathParser解析完毕之后,XPathParser此时成员变量已经赋值完毕,调用重载方法

     首先看一下new Configuration()做了什么?里面的成员变量响应初始化,我们暂且不看,看看无参构造干了什么.下面的代码是不是很熟悉?好像在哪里见过?

    没错,这些我们在配置mybatis-config.xml的时候见过,通过别名可以找到对应的类,而我们配置文件只需要写别名,方便我们的记忆和配置

      public Configuration() {
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    
        typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
        typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    
        typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
        typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
        typeAliasRegistry.registerAlias("LRU", LruCache.class);
        typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
    
        typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
    
        typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
    
        typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
        typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
        typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
        typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
        typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
        typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
    
        typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
        typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
    
        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
        languageRegistry.register(RawLanguageDriver.class);
      }
    ErrorContext.instance().resource("SQL Mapper Configuration");这个是什么呢,我们也进去瞅一眼

       我们一眼就可以看到ThreadLocal,想到了什么吗?指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据.这个是错误上下文,打印异常使用,可以认为是线程单例,因为它针对的级别是线程.

    我们再次回到SqlSessionFactory的build方法

     调用parser.parse()的方法

     XPATH解析/configuration,解析之后我们会把所有解析获取的配置信息防止到成员变量configuration里面

     最终我们获取的SqlSessionFactory的时候,里面的Configuration成员变量就包含了我们xml里面配置的所有解析出来的信息

    3.打开session  SqlSession sqlSession = sqlSessionFactory.openSession();

    public class DefaultSqlSessionFactory implements SqlSessionFactory {
    
      private final Configuration configuration;
    
      public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
      }
    
      @Override
      public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
      }

    调用一下方法,这里面顾名思义,根据我我们解析出来的配置信息,拿到数据源,构建出事务对象,然后创建出默认的SqlSession

      private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
          final Environment environment = configuration.getEnvironment();
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          final Executor executor = configuration.newExecutor(tx, execType);
          return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
          closeTransaction(tx); // may have fetched a connection so lets call close()
          throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }

     sqlSession里面都是诸如此类的方法DefaultSqlSession做了实现

    4.获取Mapper接口对象  UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    我们去看下getMapper的实现,我们传入了接口类型,但是我们并没有具体的实现类代码,看看Mybatis是如何做的。

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

    再向下断点调试

     public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return 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);
        }
      }
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
    knownMappers这个就是一个HashMap,这个map是时候放进去值的呢?
    其实我们在解析xml的时候,MyBatis已经悄悄地放进去了,不信请看,这个有一点就是,此刻其实也已经把mapper.xml解析数据放入configuration里面了
      <!--映射器-->
        <mappers>
            <mapper resource="com/bjpowernode/mapper/UsersMapper.xml"/>
        </mappers>

    通过这种不知不觉的方法,就把我们的mapper接口类型放进了map里面

     所以我们最后一定会执行

    return mapperProxyFactory.newInstance(sqlSession);
    如果我们对于反射和动态代理有了解的话,是不是很熟悉,动态代理实例化。下面的代码也证实了这一点,如果对于动态代理不太了解

     我们先来看一下MapperProxy的方法

     

     上面有一个经典面试题:动态代理,投鞭断流

    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);

     我们直接看最后一个地方,前一个地方cachedMapperMethod(method),这个地方字面应该是缓存,略过

     这里根据mapper.xml里面的标签确定是什么操作,最后把结果集返回

    5.调用Mapper接口对象的方法操作数据库

    当我们调用mapper接口的方法,通过动态代理,底层自动给我们生成动态代理对象,执行exceute方法,返回结果集。

    6.获取结果,处理业务

    此时我们通过获取的是结果集封装到对象的数据,我们就可以处理我们的业务。

    这是大概的一个源码流程,后续做更进一步的讲解。

  • 相关阅读:
    面试中你能做到随机应变吗? 沧海
    QQ只是一场意外 沧海
    面 试 中 要 慎 言 沧海
    你会应对这些面试题吗? 沧海
    面 试 小 技 巧 沧海
    面试抓住最初三分钟至关重要 沧海
    面试的十二种高级错误 沧海
    几种有难度的面试 沧海
    面试技巧: 轻松过关10种方法 沧海
    面 试 细 节 一 点 通 沧海
  • 原文地址:https://www.cnblogs.com/zhaoletian/p/13271892.html
Copyright © 2011-2022 走看看