国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

MyBatis 源碼解析(一):初始化和動態代理

娣辯孩 / 3138人閱讀

摘要:最終解析出的和依然是設置到中。到這里,初始化部分就結束了。總結的初始化流程主要是解析配置文件,將相關信息保存在中,同時對每個代表的生成代理對象工廠。

簡介

MyBatis 是 Java 開發中非常流行的 ORM 框架,其封裝了 JDBC 并且解決了 Java 對象與輸入參數和結果集的映射,同時又能夠讓用戶方便地手寫 SQL 語句。MyBatis 的行為類似于以下幾行代碼:

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, usr, password);
PraparedStatement st = conn.prepareStatement(sql);
st.setInt(0, 1);
st.execute();
ResultSet rs = st.getResultSet();
while (rs.next()) {
    String result = rs.getString(colname);
}

上面是 JDBC 的使用流程,MyBatis 其實就是對上面的代碼進行分解包裝。本文將對 MyBatis 的代碼進行分析,探究其中的邏輯。

基本用法

首先從 MyBatis 的基本用法開始,下面是 MyBatis 官網的入門示例:

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

其中 mabatis-config.xml 是 MyBatis 的核心配置文件,其中包括數據源、事務管理器、別名以及 SQL 對應的 Mapper 文件等,如下所示:




  
    
      
      
        
        
        
        
      
    
  
  
    
  

有了 SqlSessionFactory 后就可以創建 SqlSession 來調用 select 以及 update 等方法請求數據了:

try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
} finally {
  session.close();
}
配置文件解析

我們按照上面的代碼流程開始分析源碼,首先是配置文件的解析:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream) SqlSessionFactoryBuilder 顯然是為了構建 SqlSessionFactory,而且是從配置文件的輸入流構建,代碼如下:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 創建 XMLConfigBuilder
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // parse.parse() 進行解析
      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 對象,它是用來解析 Config 文件的。XMLConfigBuilder 繼承自 BaseBuilderBaseBuilder 中有個 Configuration 類型的變量,這個類需要重點關注,Config 文件中解析出來的所有信息都保存在這個變量中。

創建了 XMLConfigBuilder 后調用了其 parse 方法:

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 在這個函數中解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

這里主要邏輯在 parseConfiguration 中:

private void parseConfiguration(XNode root) {
    try {
      // 解析 properties
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first
      // 解析 type alias
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));      
      // 解析 setting
      settingsElement(root.evalNode("settings"));
      // 解析 environment 
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析 mapper
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

這里解析了 config 文件中所有的標簽,包括 propertiessettings 以及 mappers 等,下面挑幾個看一下。

settings

settings 是對 MyBatis 的一些配置項,包括緩存的開啟以及是否使用駝峰轉換(mapUnderscoreToCamelCase)等,代碼如下:

private void settingsElement(XNode context) throws Exception {
   if (context != null) {
   // 將配置項保存到 Properties 中
     Properties props = context.getChildrenAsProperties();
     // Check that all settings are known to the configuration class
     MetaClass metaConfig = MetaClass.forClass(Configuration.class);
     for (Object key : props.keySet()) {
       if (!metaConfig.hasSetter(String.valueOf(key))) {
         throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
       }
     }
     configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
     // 默認開啟緩存
     configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
     configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));      
     configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
     configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
     configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
     configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
     configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
     configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
     configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
     configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
     configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
     configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
     configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
     configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
     configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
     configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
     configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
     configuration.setLogPrefix(props.getProperty("logPrefix"));
     configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
   }
 }

可以看出,settings 的子節點保存在 Properties 中,然后校驗是否有不合法的子節點,最后提取出其中的屬性保存到 Configuration 中,上面提到這個類專門用于保存 Config 文件解析出的信息。

從上面也可以看到 MyBatis 的一些默認屬性,例如一級緩存如果沒有配置,那么默認是開啟的。

environments

environments 包含了數據源(dataSource) 和事務管理器(transactionManager) 的配置,代碼如下:

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          // 解析 transactionManager
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          // 解析 dataSource
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          // 設置 environment 到 configuration
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      // 通過反射實例化
      TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a TransactionFactory.");
  }

  private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      // 通過反射實例化
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }

其中主要是兩部分,第一部分解析 transactionManager,第二部分解析 dataSource。從 transactionManagerElementdataSourceElement 中可以看出通過對應 Class 文件的 newInstance 實例化出對應的工廠對象。最終解析出的 transactionManagerdataSource 依然是設置到 Configuration 中。

mappers

mappers 對應了具體的 SQL Mapper 文件,也是我們要分析的重點。

mappers 標簽可以多種子標簽,上面的示例中是 mapper 配合 resource

 
    
  

我們下面看一下此種形式在源碼中的解析:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // 這個分支解析 resource 形式的標簽
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 創建 XMLMapperBuilder 
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 進行解析
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

resource 標簽解析的對應分支是 (resource != null && url == null && mapperClass == null),其中創建了一個 XMLMapperBuilder 對象然后調用 parse 方法進行解析:

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 解析 mapper 下面的標簽,包括 namespace、cache、parameterMap、resultMap 等
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

 private void configurationElement(XNode context) {
    try {
      // namespace 對應 Mapper 對應接口的全名(包名 + 類名)
      String namespace = context.getStringAttribute("namespace");
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      // 解析生成 ParameterMap
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析生成 ResultMap
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      // 每一個 sql 語句生成一個 MappedStatement
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

configurationElement 用于解析具體的子標簽,如 namespacecacheparameterMapresultMap 以及 select|insert|update|delete 等。

namespace 對應了 Mapper 接口類的包名 + 類名,通過 namespace 可以唯一定位一個 Class 文件,解析的 namespace 保存在 builderAssistant 中,后面會用到。

parameterMapresultMap 解析會生成 ParameterMapResultMap 對象。每個 SQL 語句解析會生成 MappedStatement

在上面的 parse 方法中,解析完標簽后調用了 bindMapperForNamespace,這個實現了加載 namespace 對應的 Class,并且為每個 Class 創建了代理類工廠對象(MapperProxyFactory)。

MapperProxyFactory

MapperProxyFactory 用于為 Mapper 接口類創建代理對象,代理對象指的是
BlogMapper mapper = session.getMapper(BlogMapper.class) 生成的對象。

下面從 bindMapperForNamespace 開始:

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class boundType = null;
      try {
        // 加載類
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          // 添加 mapper 和 MapperProxyFactory
          configuration.addMapper(boundType);
        }
      }
    }
  }

其中先從 builderAssistant 取出 namespace,然后加載對應的 Class(boundType = Resources.classForName(namespace))。最后調用 configuration.addMapper(boundType) 添加到 configuration 中。 configuration.addMapper(boundType) 很關鍵,看代碼:

public  void addMapper(Class type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 添加到  Map, MapperProxyFactory> 中
        knownMappers.put(type, new MapperProxyFactory(type));
        // It"s important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won"t try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

關鍵的一行是 knownMappers.put(type, new MapperProxyFactory(type)),其中 knownMappers 的類型是 Map, MapperProxyFactory>,即 key 是 Class,value 是 MapperProxyFactory。這里的 MapperProxyFactory 即是動態代理對象的工廠,下面是其 newInstance 方法的代碼:

  protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

從中可以看出,這里用的是 Java 的動態代理,Proxy.newProxyInstance 方法生成指定接口的代理對象,這個方法的第三個參數是用于方法攔截的對象,這里是 MapperProxy 的實例。

由此可以知道,具體的執行 SQL 語句的操作是由這個類攔截并且執行的,看看這個類的 invoke 方法:

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

如果是 Object 類中聲明的方法,則直接執行,否則調用 MapperMethodexecute,其中便是 JDBC 相關的邏輯了。限于篇幅,具體內容留到下一篇文章再看。

在分析完配置文件的解析后,再回到 XMLConfigBuilder 中:

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 構建 SqlSessionFactory
      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.
      }
    }
  }

parser.parse() 執行完后,生成一個 Configuration 對象,最后調用 build 構建 SqlSessionFactory,代碼如下:

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

可以看到,最終創建的是 DefaultSqlSessionFactory,這個類內部持有 Configuration,并且提供了多個重載的 openSession 方法用于創建 SqlSession

到這里,初始化部分就結束了。

總結

MyBatis 的初始化流程主要是解析配置文件,將相關信息保存在 Configuration 中,同時對每個 namespace 代表的 Class 生成代理對象工廠。最后,利用 Configuration 生成了一個 DefaultSqlSessionFactory,通過這個對象可以創建 SqlSession 執行 SQL 請求,相關內容將在下一篇(MyBatis 源碼解析(二):SqlSession 執行流程)分析。

如果我的文章對您有幫助,不妨點個贊支持一下(^_^)

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72677.html

相關文章

  • Mybatis源碼分析

    摘要:我認為學習框架源碼分為兩步抓住主線,掌握框架的原理和流程理解了處理思路之后,再去理解面向對象思想和設計模式的用法目前第一步尚有問題,需要多走幾遍源碼,加深下理解,一起加油 這篇文章我們來深入閱讀下Mybatis的源碼,希望以后可以對底層框架不那么畏懼,學習框架設計中好的思想; 架構原理 架構圖 showImg(https://segmentfault.com/img/remote/...

    lindroid 評論0 收藏0
  • MyBatis 源碼解析(二):SqlSession 執行流程

    摘要:簡介上一篇文章源碼解析一初始化和動態代理分析了解析配置文件以及動態代理相關的源碼,這一篇接著上一篇探究的執行流程,另外了解一下中的緩存。總結本文主要分析了的執行流程,結合上一篇文章基本了解了的運行原理。 簡介 上一篇文章(MyBatis 源碼解析(一):初始化和動態代理)分析了 MyBatis 解析配置文件以及 Mapper 動態代理相關的源碼,這一篇接著上一篇探究 SqlSessio...

    Dionysus_go 評論0 收藏0
  • 【深入淺出MyBatis筆記】MyBatis解析運行原理

    摘要:的解析和運行原理構建過程提供創建的核心接口。在構造器初始化時會根據和的方法解析為命令。數據庫會話器定義了一個對象的適配器,它是一個接口對象,構造器根據配置來適配對應的對象。它的作用是給實現類對象的使用提供一個統一簡易的使用適配器。 MyBatis的解析和運行原理 構建SqlSessionFactory過程 SqlSessionFactory提供創建MyBatis的核心接口SqlSess...

    bitkylin 評論0 收藏0
  • 開源框架解析,手寫MyBatis細節思路

    摘要:基本綱要組成動態配置配置核心源碼分析源碼解析源碼解析源碼解析源碼解析手寫框架是什么本質是一種半自動的框架,前身是其源于和的組合,除了和映射關系之外,還需要編寫語句映射三要素映射規則快速入門加入的依賴添加的配置文件場景介紹編寫實體類接口以及文 showImg(https://segmentfault.com/img/bVblrnC); Mybatis基本綱要 Mybatis組成 · 動態...

    paulli3 評論0 收藏0
  • 源碼的角度解析Mybatis的會話機制

    摘要:從源碼的角度分析源碼分析從哪一步作為入口呢如果是看過我之前寫的那幾篇關于的源碼分析,我相信你不會在源碼前磨磨蹭蹭,遲遲找不到入口。 微信公眾號「后端進階」,專注后端技術分享:Java、Golang、WEB框架、分布式中間件、服務治理等等。 老司機傾囊相授,帶你一路進階,來不及解釋了快上車! 坐在我旁邊的鐘同學聽說我精通Mybatis源碼(我就想不通,是誰透漏了風聲),就順帶問了我一個...

    DevWiki 評論0 收藏0

發表評論

0條評論

娣辯孩

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<