源码探究之配置文件解析详解

2023-10-07 源码探究Mybatis

接下来研究第二行代码,探究mybatis获取到配置文件输入流后,是如何进行解析并转换为可操作的javaBean对象。

# 剖析build方法是如何解析配置文件的

// 2. (1)解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

接下来分析第二行代码,第二行代码的第一个功能就是将配置文件转换为全局的Configuration对象,这个对象记录全局的一些配置信息;下面是整体的类图,配合后面的解析进行

build类图源码解析

点击展开Configuration.java
public class Configuration {

    protected Environment environment;

    // 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。默认为false
    protected boolean safeRowBoundsEnabled;
    // 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false
    protected boolean safeResultHandlerEnabled = true;
    // 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN
    // 到经典 Java 属性名 aColumn 的类似映射。默认false
    protected boolean mapUnderscoreToCamelCase;
    // 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。默认值false (true in ≤3.4.1)
    protected boolean aggressiveLazyLoading;
    // 是否允许单一语句返回多结果集(需要兼容驱动)。
    protected boolean multipleResultSetsEnabled = true;
    // 允许 JDBC 支持自动生成主键,需要驱动兼容。这就是insert时获取mysql自增主键/oracle sequence的开关。
    // 注:一般来说,这是希望的结果,应该默认值为true比较合适。
    protected boolean useGeneratedKeys;
    // 使用列标签代替列名,一般来说,这是希望的结果
    protected boolean useColumnLabel = true;
    // 是否启用缓存
    protected boolean cacheEnabled = true;
    // 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,
    // 这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。
    protected boolean callSettersOnNulls;
    // 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的工程必须采用Java 8编译,
    // 并且加上-parameters选项。(从3.4.1开始)
    protected boolean useActualParamName = true;
    //当返回行的所有列都是空时,MyBatis默认返回null。 当开启这个设置时,MyBatis会返回一个空实例。
    // 请注意,它也适用于嵌套的结果集 (i.e. collectioin and association)。(从3.4.2开始)
    // 注:这里应该拆分为两个参数比较合适, 一个用于结果集,一个用于单记录。
    // 通常来说,我们会希望结果集不是null,单记录仍然是null
    protected boolean returnInstanceForEmptyRow;

    protected boolean shrinkWhitespacesInSql;

    // 指定 MyBatis 增加到日志名称的前缀。
    protected String logPrefix;
    // 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。一般建议指定为slf4j或log4j
    protected Class<? extends Log> logImpl;
    // 指定VFS的实现, VFS是mybatis提供的用于访问AS内资源的一个简便接口
    protected Class<? extends VFS> vfsImpl;
    protected Class<?> defaultSqlProviderType;
    // MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。
    // 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。
    // 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
    protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
    // 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,
    // 多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。
    protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
    // 指定对象的哪个方法触发一次延迟加载。
    protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
    // 设置超时时间,它决定驱动等待数据库响应的秒数。默认不超时
    protected Integer defaultStatementTimeout;
    // 为驱动的结果集设置默认获取数量。
    protected Integer defaultFetchSize;
    // SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);
    // BATCH 执行器将重用语句并执行批量更新。
    protected ResultSetType defaultResultSetType;

    // 默认执行器类型
    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
    // 指定 MyBatis 应如何自动映射列到字段或属性。
    // NONE 表示取消自动映射;
    // PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。
    // FULL 会自动映射任意复杂的结果集(无论是否嵌套)。
    protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
    // 指定发现自动映射目标未知列(或者未知属性类型)的行为。这个值应该设置为WARNING比较合适
    protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
    // settings下的properties属性
    protected Properties variables = new Properties();
    // 默认的反射器工厂,用于操作属性、构造器方便
    protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    // 对象工厂, 所有的类resultMap类都需要依赖于对象工厂来实例化
    protected ObjectFactory objectFactory = new DefaultObjectFactory();
    // 对象包装器工厂,主要用来在创建非原生对象,比如增加了某些监控或者特殊属性的代理类
    protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

    // 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态
    protected boolean lazyLoadingEnabled = false;
    // 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。MyBatis 3.3+使用JAVASSIST
    protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

    // MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
    protected String databaseId;
    /**
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
   */
    protected Class<?> configurationFactory;

    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

    // mybatis插件列表
    protected final InterceptorChain interceptorChain = new InterceptorChain();
    protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);

    // 类型注册器, 用于在执行sql语句的出入参映射以及mybatis-config文件里的各种配置
    // 比如<transactionManager type="JDBC"/><dataSource type="POOLED">时使用简写
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
    protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
        .conflictMessageProducer((savedValue, targetValue) ->
                                 ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
    protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
    protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
    protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
    protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

    protected final Set<String> loadedResources = new HashSet<>();
    protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

    protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
    protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
    protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
    protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

    /*
   * A map holds cache-ref relationship. The key is the namespace that
   * references a cache bound to another namespace and the value is the
   * namespace which the actual cache is bound to.
   */
    protected final Map<String, String> cacheRefMap = new HashMap<>();

    public Configuration(Environment environment) {
        this();
        this.environment = environment;
    }
}

# build方法

public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
    }
    // 主要逻辑
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // 1.创建XPathParser解析器对象,根据inputStream解析成了document对象 2.创建全局配置对象Configuration对象
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // parser.parse():使用XPATH解析XML配置文件,将配置文件封装到Configuration对象
            // 返回DefaultSqlSessionFactory对象,该对象拥有Configuration对象(封装配置文件信息)
            // parse():配置文件就解析完成了
            return build(parser.parse());
        }
    }
}

# XMLConfigBuilder构造函数









 














public class XMLConfigBuilder extends BaseBuilder {
    private boolean parsed; // 是否解析过了
    private final XPathParser parser; // 存储的核心数据
    private String environment; // 数据库环境配置有多个,记录当前环境是哪一个(xml文件environments的default属性内容)
    
    // 上一段代码先调用这个方法
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        // XPathParser基于 Java XPath 解析器,用于解析 MyBatis中的配置文件
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }
    
    // 然后上一行调用这个构造函数
    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        //  创建Configuration对象,并通过TypeAliasRegistry注册一些Mybatis内部相关类的别名
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }
}
第15行调用的new Configuration()会进行别名注册,这就是为什么配置文件填写JDBC就能找到类的原因。
public Configuration() {
    //TypeAliasRegistry(类型别名注册器)
    // 注册事务工厂的别名
    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);
}

# XPathParser解析XML文档为Document对象

public class XPathParser {
    private final Document document; // 第一步解析的内容存放到这里,这里是sqlMapConfig.xml文件的内容
    private boolean validation;
    private EntityResolver entityResolver;
    private Properties variables;
    private XPath xpath;

    public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
        commonConstructor(validation, variables, entityResolver);
        // 解析XML文档为Document对象
        this.document = createDocument(new InputSource(inputStream));
    }
}
createDocument解析XML文档为Document对象
private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        // 进行dtd或者Schema校验
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        factory.setValidating(validation);

        factory.setNamespaceAware(false);
        // 设置忽略注释为true
        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 {
                // NOP
            }
        });
        // 通过dom解析,获取Document对象
        return builder.parse(inputSource);
    } catch (Exception e) {
        throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
}

# parse()方法解析配置文件

第一步是判断是否解析过了,如果解析过抛出提示异常;parseConfiguration方法从根节点开始解析,并封装到

public class XMLConfigBuilder extends BaseBuilder {  
    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;

        // parser.evalNode("/configuration"):通过XPATH解析器,解析configuration根节点
        // 从configuration根节点开始解析,最终将解析出的内容封装到Configuration对象中
        parseConfiguration(parser.evalNode("/configuration"));

        return configuration;
    }
}

parser.evalNode返回解析后的XNode对象,通过xNode对象可以更方便的遍历获取xml的内容;再来看parseConfiguration方法,他就是用来解析标签,并根据标签内容进行对应初始化操作。























 






 







public class XMLConfigBuilder extends BaseBuilder {  
    private void parseConfiguration(XNode root) {
        try {
            // issue #117 read properties first
            // 解析</properties>标签
            propertiesElement(root.evalNode("properties"));
            // 解析</settings>标签
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            loadCustomVfs(settings);
            loadCustomLogImpl(settings);
            // 解析</typeAliases>标签
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析</plugins>标签
            pluginElement(root.evalNode("plugins"));
            // 解析</objectFactory>标签
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析</objectWrapperFactory>标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析</reflectorFactory>标签
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // 解析</environments>标签
            environmentsElement(root.evalNode("environments"));
            // 解析</databaseIdProvider>标签
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析</typeHandlers>标签
            typeHandlerElement(root.evalNode("typeHandlers"));

            // 解析</mappers>标签 加载映射文件流程主入口
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        } 
    }
}

# 剖析environments解析过程

配置文件关于environments配置
<environments default="development" >
    <environment id="development">
        <!-- 使用jdbc事务管理 -->
        <transactionManager type="JDBC" />
        <!-- 数据库连接池 -->
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver" />
            <property name="url"
                      value="jdbc:mysql:///qq-card?useSSL=false&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC" />
            <property name="username" value="root" />
            <property name="password" value="root" />
        </dataSource>
    </environment>
</environments>

该方法主要用于解析<environments>节点,获取指定的环境配置信息,并创建对应的事务工厂和数据源对象,最后将Environment对象设置到Configuration中,用于后续的数据库操作。

  1. 首先判断是否存在<environments>节点,如果存在则进行解析,否则跳过。
  2. 如果未指定environment参数,则从<environments>节点的"default"属性中获取默认的环境名称。
  3. 遍历<environments>节点的子节点,即<environment>节点。
  4. 对每个<environment>节点,获取其"id"属性值,用于指定具体的环境。
  5. 判断当前解析的环境是否与指定的环境相匹配,如果匹配则继续解析,否则跳过。
  6. 解析<transactionManager>节点,创建对应的事务工厂对象。
  7. 解析<dataSource>节点,创建对应的数据源对象。
  8. 使用解析得到的事务工厂对象和数据源对象构建Environment.Builder对象。
  9. 将构建的Environment对象设置到Configuration中。
public class XMLConfigBuilder extends BaseBuilder { 
    private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
            // 方法参数如果没有传递environment,则解析sqlMapConfig.xml中的
            if (environment == null) {
                // 获取default属性值,用于指定默认的环境
                environment = context.getStringAttribute("default");
            }

            // 遍历解析environment节点
            for (XNode child : context.getChildren()) {
                // 获得id属性值,用于指定具体的环境
                String id = child.getStringAttribute("id");
                // 判断id和environment值是否相等,用于确定是否是指定的环境
                if (isSpecifiedEnvironment(id)) {
                    // 解析<transactionManager>节点,创建对应的事务工厂对象
                    TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                    
                    // 解析<dataSource>节点,创建对应的数据源对象
                    DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                    DataSource dataSource = dsFactory.getDataSource();
                    
                    // 创建Environment.Builder对象,用于构建Environment对象
                    Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);

                    // 将创建的Environment对象设置到Configuration中
                    configuration.setEnvironment(environmentBuilder.build());
                    break;
                }
            }
        }
    }
}

# 剖析mappers解析过程

配置文件关于mappers配置,使用不同的方式,代码执行流程也不一样
<mappers>
    <mapper resource="com/xk857/mapper/UserMapper.xml"></mapper>
    <!--<mapper class="com.xk857.mapper.UserMapper"></mapper>-->
    <!--    <package name="com.xk857.mapper"/>-->
</mappers>

该方法主要用于解析<mappers>节点,获取mapper接口和mapper映射文件的配置信息,并将其添加到Configuration对象中,用于后续的数据库操作。

  1. 首先判断是否存在<mappers>节点,如果存在则进行解析,否则跳过。
  2. 遍历<mappers>节点的子节点。
  3. 如果子节点是<package>标签,获取其"name"属性值,表示mapper接口和mapper映射文件对应的package包名。
    • 将指定包下所有的mapper接口以及它的代理工厂对象存储到一个Map集合中。
  4. 如果子节点是<mapper>标签,获取其"resource"、"url"和"class"属性值。
    • 如果只指定了"resource"属性,则根据该属性值获取对应的输入流,创建XMLMapperBuilder对象,用于解析mapper映射文件。
    • 如果只指定了"url"属性,则根据该属性值获取对应的输入流,创建XMLMapperBuilder对象,用于解析mapper映射文件。
    • 如果只指定了"class"属性,则根据该属性值获取mapper接口类型,并将该接口以及其代理对象存储到一个Map集合中。
  5. 如果同时指定了多个属性,则抛出异常。
public class XMLConfigBuilder extends BaseBuilder { 
    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            // 遍历<mappers>节点的子节点
            for (XNode child : parent.getChildren()) {
                // 如果子节点是<package>标签
                if ("package".equals(child.getName())) {
                    // 获取mapper接口和mapper映射文件对应的package包名
                    String mapperPackage = child.getStringAttribute("name");
                    // 将包下所有的mapper接口以及它的代理工厂对象存储到一个Map集合中
                    configuration.addMappers(mapperPackage);
                } else if ("<mapper>标签") {
                    // 获取<mapper>子节点的resource属性
                    String resource = child.getStringAttribute("resource");
                    // 获取<mapper>子节点的url属性
                    String url = child.getStringAttribute("url");
                    // 获取<mapper>子节点的class属性
                    String mapperClass = child.getStringAttribute("class");

                    // 根据不同属性进行解析
                    if (resource != null && url == null && mapperClass == null) {
                        // 根据resource属性获取输入流
                        try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
                            // 创建XMLMapperBuilder对象,用于解析mapper映射文件
                            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                            // 解析mapper映射文件
                            mapperParser.parse();
                        }
                    } else if (resource == null && url != null && mapperClass == null) {
                        // 根据url属性获取输入流
                        try (InputStream inputStream = Resources.getUrlAsStream(url)) {
                            // 创建XMLMapperBuilder对象,用于解析mapper映射文件
                            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                            // 解析mapper映射文件
                            mapperParser.parse();
                        }
                    } else if (resource == null && url == null && mapperClass != null) {
                        // 根据class属性获取mapper接口类型
                        Class<?> mapperInterface = Resources.classForName(mapperClass);
                        // 将指定mapper接口以及它的代理对象存储到一个Map集合中
                        configuration.addMapper(mapperInterface);
                    } else {
                        throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                    }
                }
            }
        }
    }
}
上次更新: 4 个月前