第三篇:Configuration对象构建-1

博客围绕MyBatis展开,提到MyBatis通过Builder构建SqlSessionFactory对象,构建时生成的Configuration对象很关键。重点对properties、settings、typeAliases三个节点进行解析说明,如settings是全局属性配置,typeAliases可将长名映射为短名方便使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如同前两篇所说,mybatis提供了一个Builder构建SqlSessionFactory对象

org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)

而在构建SqlSessionFactoryBuilder对象时,最重要的是生成Configuration对象,这个Configuration是mybatis的集大成者,基本所有重要的接口都在这个里面了

以下进行一些自己看源码的见解

Configuration对象的构建入口在

org.apache.ibatis.builder.xml.XMLConfigBuilder#parse

当然了你也完成可以自己new,但是这个是我们使用mybatis时一种比较常用的方式,会配合对mybatis-config.xml的解析步骤

XMLConfigBuilder采用了建造者设计模式来封装解析xml里面的Configuration对象,后面你就会发现mybatis经常使用这种建造者设计模式


先看下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(parser.evalNode("/configuration"));这个方法,接着往里面看



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


是不是一目了然呢,每个子方法去解析不同的xml标签数据,当然了这个解析也是需要api处理的,不过这部分我就没怎么研究了,有兴趣的可以看看对xml是怎么研究的





这一篇我主要是对properties、settings、typeAliases这三个节点的解析说明

首先是properties,看下里面的代码

properties

private void propertiesElement(XNode context) throws Exception {
    //如果context标签不为空
    if (context != null) {
      //首先先加载property的属性
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      //然后是加载配置的url或者resource配置的properties属性的文件
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      //最后是配置用户直接传输进来的
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      //设置到configuration的variables对象中去
      parser.setVariables(defaults);
      configuration.setVariables(defaults);

      //所以属性的优先级为,通过configuration直接输入的-> 配置文件中的 -> xml属性标题中的
    }
  }


这个是一些环境变量,后面mybatis可以使用的,当然了你也可以使用,只要拿到configuration的setVariables属性即可


配置方法有3中

1. 从mybatis-config.xml配置,格式如下


<properties>
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>


2. 从mybatis-config.xml配置,格式如下

<properties resource="org/mybatis/example/config.properties"></properties>


上面两种都是xml文件配置的,可以参考官网

https://mybatis.org/mybatis-3/zh/configuration.html#properties


3. 从代码层面添加

org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.util.Properties)

构建SqlSessionFactory的时候可以传入Properties,那个Properties就会自动放到configuration对象中




三种方法的优先级为

3 -> 2 -> 1


其实你看到源码的话你自然就明白优先级了....

settings

这个是全局的一些属性配置,其实就是Configuration对象的一些字段

来看下方法是咋做的

  private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    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).");
      }
    }
    return props;
  }


这个就是通过反射的机制去判断当前有没有对应值的set方法,没有的话就直接抛出异常了,这个就不细说了,比较简单,MetaClass 是mybatis封装的一个关于类元数据的对象,不属于本章内容


 settings的参数含义还是可以参考官网文档

https://mybatis.org/mybatis-3/zh/configuration.html#settings


这里说一个比较典型的参数


mapUnderscoreToCamelCase	: 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。


这个比较典型,就是否自动把 user_name的数据库字段自动映射到 userName的Java属性上,所以我们一般建表尽量要遵循规范,查询数据就不用定义别名了


不过奇怪的是这个属性默认是关闭的,你需要手工配置一下启动.......

typeAliases

别名处理器,主要功能是为了把一些比较长的名字通过短名字映射,然后你在后续使用中就可以使用短名字来使用,还是先来看看具体的源码解析

配置方式还是可以参考官网


https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases



主要有两种配置方式

1. 通过包配置,mybatis会自动把下面的所有类都进行注册别名

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>


2. 通过具体的指定配置

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
</typeAliases>


2.1 其中的alias可以不指定,不指定的话会通过类的最短名字来作为别名,也就是author
2.2 还可以在Author标记上一个注解@Alias来指定别名,也是mybatis提供的



好了下面是具体的解析源码



  private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //如果配置的是包会去加载所有的类
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
          //否则去获取数据
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
              //如果别名没配,就需要根据Java类型去做判断了,或者根据@Alias注解来解析
              typeAliasRegistry.registerAlias(clazz);
            } else {
              //两个都配了解析是最简单的,往里面注册就行
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }



其实还是挺好理解的,有兴趣的话可以看到里面的方法看他是怎么做的



1. 比如当你只配置type,没配置alias的时候是这样处理的


  public void registerAlias(Class<?> type) {
    //先获取类型的简单名称
    String alias = type.getSimpleName();
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      //如果类上面标记了Alias注解就用注解里面的,否则就是类型的简称
      alias = aliasAnnotation.value();
    } 
    //调用通用的注册方法
    registerAlias(alias, type);
  }



2. 又比如包的处理,这个就稍微复杂点,涉及到加载所有类了


  public void registerAliases(String packageName, Class<?> superType){
    //解析拿到包下面的所有类
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for(Class<?> type : typeSet){
      // Ignore inner classes and interfaces (including package-info.java)
      // Skip also inner classes. See issue #6
      //不是匿名内部类,不是接口,不是内部类等
      if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
        //等于执行你没配置alias属性的情况
        registerAlias(type);
      }
    }
  }


至于怎么拿到包下面所有类的,这里就不说了,因为我自己也没看进去



3. 具体的注册方法也可以简单看下,最终的注册其实都会到这里来

 
 public void registerAlias(String alias, Class<?> value) {
    //别名不能为空
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH);
    //如果已经有对应的别名存在了就抛出异常,不允许覆盖,这里可以提供个属性给用户自己定义是否覆盖???
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    //往这个类型放置一个别名,其实就是一个HashMap
    TYPE_ALIASES.put(key, value);
  }



至此,这个也说完了,其实也不复杂,很好理解

typeAliases初始别名

别名数据进行了两部分初始化


1. 在TypeAliasRegistry自己构造的时候初始化了很多对应关系,可以看到TypeAliasRegistry的构造器里面做了这些东西


 public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }




2. TypeAliasRegistry是被集成在Configuration对象使用的,里面一开始就创建了TypeAliasRegistry这个对象


  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();


然后Configuration的构造器里面也初始化了很多别名注册


  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);
  }


其中有一个别名是我经常使用到的


  typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);


这个是日志的打印器,配合前面的setting进行这样的配置


 <settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
   </settings>


那么sql就会打印到控制台了,调试的时候我经常用这个



当然这里能拿到是因为这个别名是在Configuration对象被创建的时候就加载了,所以可以拿到

其实这个源码下面并不复杂,如果单纯看的话

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值