源码配置解读

配置文件加载入口解释:

public void setUpBeforeClass() throws Exception {
        //全局配置文件名称
		String resource="SqlMapConfig.xml";
		//写入流中
        InputStream inputStream=Resources.getResourceAsStream(resource);
        //通过建造者模式来获得SqlSession 我们的源码学习就是通过该入口进入
		sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
		
	}

首先:

  1. 创建SqlSessionFactoryBuilder对象
    通过源码可以知道我们如果想创建SqlSessionFactory对象 必须通过SqlSessionFactoryBuilder这个对象来创建
    通过字面意思可以看出来这是一个用于创建SqlSessionFactory工厂对象的方法类。

    我们通过解读下面的代码片段即可知道如何获得SqlSessionFactory对象的
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        //1.全局配置文件解析器
      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 他继承与BaseBuilder那么 BaseBuilder这个父类中一共有多少子类呢。我们看下他们的关系图

java aop 配置文件存放路径 java配置文件在哪放着_配置文件


通过这张图我们可以清楚的发现 BaseBuilder中一共有四个子类

1.XMLConfigBuilder :是用来解析全局配置文件

2.XMLMapperBuilder: 用来解析映射文件

3.XMLStatementBuilder :用来解析MappedStatement 语句

4.MapperBuilderAssistant:是一个辅助方法类 用来辅助解析映射文件并胜场MappedStatement对象、

我们打开他们的父类 即抽象类BaseBuilder:

public abstract class BaseBuilder {
    //1.Configuration 是用于存储全局配置文件
  protected final Configuration configuration;
    //2.别名注册器
  protected final TypeAliasRegistry typeAliasRegistry;
    //3.类型注册器
  protected final TypeHandlerRegistry typeHandlerRegistry;

我们回到刚才的地方

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
     //通过Xpath来解析节点然后调用内部方法XMLConfigBuilder
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
    //内部方法
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

我们会发现在构建XmlConfigBuilder对象时候会初始化Configuation对象

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

会把一些基础配置加载到别名注册器中。

build(parser.parse());//用来构建SqlSessionFactory的主要方法

public Configuration parse() {
    //如果已经存在XmlConfigBuilder对象就会抛出异常 只能唯一
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //解析获得所有配置文件中的节点
    parseConfiguration(parser.evalNode("/configuration"));
    //返回Configuration对象
    return configuration;
  }

我们主要看看如何依次解析文件中的节点的:

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      //解析properties配置
      propertiesElement(root.evalNode("properties"));
      //解析settings
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //加载Vfs
      loadCustomVfs(settings);
      //解析typeAliases
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析plugins配置
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // settings 中的信息设置到 Configuration 对象中
      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);
    }
  }

上面的代码比较核心我们现在一次分析

2.1 properties 节点

propertiesElement(root.evalNode("properties"));

private void propertiesElement(XNode context) throws Exception {
    //如果节点不为空
    if (context != null) {
       //加载里面所有的子节点。
      Properties defaults = context.getChildrenAsProperties();
       //获得节点属性 Resource & url
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
          //可以看出 我们只能选择俩种方式 要么用resource 要么用Url
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
        //依次加载把配置文件中的内容加载到Properties中
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
        //我们既然可以通过xml也可以通过java方式配置 我们在这里可以看出 会覆盖掉前面相同的配置。那么说明 优先级为:
        //java>外部配置文件>Xml子节点方式配置
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
        //把信息重新加载到Xml解析器中
      parser.setVariables(defaults);
        //把信息加载到Configuration中
      configuration.setVariables(defaults);
    }
  }

我们可以大致的认为:properties这个节点可以分成3个部分。

  1. 获得子节点 解析到Properties中
  2. 从外部文件 以及java 读取配置文件到Propeties ,配置相同可以覆盖
  3. 把信息加载到Configuration中

2.2settings 解析过程

Properties settings = settingsAsProperties(root.evalNode("settings"));

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
    //创建 Configuration 类的“元信息”对象
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
          // 检测 Configuration 中是否存在相关属性,不存在则抛出异常
      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;
  }

目前看这段代码很简单。但是我们发现一个MetaClass的类,从字面意思以及在这的作用可以发现。我们是根据反射获得所有的configuration对象中的属性。
然后通过hasSetter()方法判断Configuration中是否存在设置全局XML中setting中的属性。

MetaClass类
public class MetaClass {

  private ReflectorFactory reflectorFactory;
  private Reflector reflector;
   //私有构造方法。所以必须设置个静态方法供外面访问
  private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
	this.reflectorFactory = reflectorFactory;
      //根据类型创建reflector
    this.reflector = reflectorFactory.findForClass(type);
  }

  public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
    return new MetaClass(type, reflectorFactory);
  }
findForClass方法:
public Reflector findForClass(Class<?> type) {
    //是否缓存了信息
    if (classCacheEnabled) {
            // synchronized (type) removed see issue #461
        //从缓存中获得Reflector对象
      Reflector cached = reflectorMap.get(type);
        //如果Map中没有对应的Key 
      if (cached == null) {
          //new一个Reflector
        cached = new Reflector(type);
          //放到缓存中,方便下次使用
        reflectorMap.put(type, cached);
      }
        //返回对应的Reflector
      return cached;
    } else {
      return new Reflector(type);
    }
  }
Reflector 源码分析

本小节,我们来看一下 Reflector 的源码。Reflector 这个类的用途主要是是通过反射获取目标类的 getter 方法及其返回值类型,setter 方法及其参数值类型等元信息。并将获取到的元信息缓存到相应的集合中,供后续使用。Reflector 本身代码比较多,这里不能一一分析。本小节,我将会分析三部分逻辑,分别如下

  1. Reflector 构造方法及成员变量分析
  2. getter 方法解析过程
  3. setter 方法解析过程
  • Reflector 构造方法及成员变量分析
public class Reflector {

  private static final String[] EMPTY_STRING_ARRAY = new String[0];

  private Class<?> type;
  private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
  private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
  private Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
  private Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
  private Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
  private Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
  private Constructor<?> defaultConstructor;

  private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();

  public Reflector(Class<?> clazz) {
    type = clazz;
    //解析目标类的默认构造方法 并且赋值给
    addDefaultConstructor(clazz);
    //获得get方法 放到getMethods中
    addGetMethods(clazz);
    //获得set方法 放到setMethods中
    addSetMethods(clazz);
    //解析属性名 放到对应的get setMethods中
    addFields(clazz);
    //从getMethods中获得可读的属性名 放到数组中
    readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
    //从setMethods中获得可写的属性名 放到数组中
    writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
    //将所有属性值易键大写的形式放到 caseInsensitivePropertyMap中 
    for (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writeablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
  }

变量名

类型

作用

readablePropertyNames

String[]

存放可以读取的属性名称,是getter方法的属性名称

writeablePropertyNames

String[]

存放可以写入的属性名称,是setter方法的属性名称

setMethods

Map<String, Invoker>

保存属性名称到Invoker的映射,setter会被封装到MethodInvoker对象中。

getMethods

Map<String, Invoker>

同上

setTypes

Map<String, Class<?>>

用于保存setter对应的属性名与返回值类型的映射

getTypes

Map<String, Class<?>>

同上

caseInsensitivePropertyMap

Map<String, String>

存放大写的属性名称为Key与属性名之间的映射。

现在分析addGetMethods方法
private void addGetMethods(Class<?> cls) {
    Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
    Method[] methods = getClassMethods(cls);
    for (Method method : methods) {
      if (method.getParameterTypes().length > 0) {
        continue;
      }
      String name = method.getName();
      if ((name.startsWith("get") && name.length() > 3)
          || (name.startsWith("is") && name.length() > 2)) {
        name = PropertyNamer.methodToProperty(name);
        addMethodConflict(conflictingGetters, name, method);
      }
    }
    resolveGetterConflicts(conflictingGetters);
  }

上面很简单

  1. 创建一个属性为Key List为值的空Map
  2. 获得所有的属性值对应的方法
  3. 遍历获得get set方法
  4. 只要get方法。因为set方法参数长度一定不为空
  5. 加入冲突属性名称
  6. 解决冲突
addMethodConflict方法为加入冲突
private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
    List<Method> list = conflictingMethods.get(name);
    if (list == null) {
      list = new ArrayList<Method>();
      conflictingMethods.put(name, list);
    }
    list.add(method);
  }

上面也很简单很好理解。就是找到对应的Map中的Key 然后获得List集合。判断如果为空 就新建立List放进去 在加入Map中 如果不位空 就加载进List 在放到对应的Key中在下面进行解决
resolveGetterConflicts 方法为解决冲突

private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
    //便利Map
    for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
     //看名字都能看懂 胜利者。即解决冲突中的获胜者
      Method winner = null;
      //获得属性名称
      String propName = entry.getKey();
      //便利属性名称中所有的Method方法
      for (Method candidate : entry.getValue()) {
         //如果没有获胜者。即第一次进来没有冲突属性值的话 就把当前这个方法作为获胜者返回。
        if (winner == null) {
          winner = candidate;
          continue;
        }
        //取得获胜者的返回值类型
        Class<?> winnerType = winner.getReturnType();
        //竞争者的返回值类型
        Class<?> candidateType = candidate.getReturnType();
        //如果俩个人的返回值都一样。如果竞争者不是Boolean 就会抛出异常
        if (candidateType.equals(winnerType)) {
          if (!boolean.class.equals(candidateType)) {
            throw new ReflectionException(
                "Illegal overloaded getter method with ambiguous type for property "
                    + propName + " in class " + winner.getDeclaringClass()
                    + ". This breaks the JavaBeans specification and can cause unpredictable results.");
          } else if (candidate.getName().startsWith("is")) {
              //如果竞争者是Boolean 则把胜利者淘汰
            winner = candidate;
          }
        //如果竞争者是胜利者的父类。也就是胜利者是子类。那么我们优先子类 而不选取父类属性的get
        } else if (candidateType.isAssignableFrom(winnerType)) {
          // OK getter type is descendant
        } else if (winnerType.isAssignableFrom(candidateType)) {
          //相反就要用竞争者为子类就要用竞争者
          winner = candidate;
        } else {
            //都不满足就抛出异常
          throw new ReflectionException(
              "Illegal overloaded getter method with ambiguous type for property "
                  + propName + " in class " + winner.getDeclaringClass()
                  + ". This breaks the JavaBeans specification and can cause unpredictable results.");
        }
      }
      addGetMethod(propName, winner);
    }
  }
addGetMethod 方法
private void addGetMethod(String name, Method method) {
    //一些验证 即 name不以$开头 并且 不等于 serialVersionUID 并且 不等于class 
    if (isValidPropertyName(name)) {
        //放到Map中
      getMethods.put(name, new MethodInvoker(method));
        //解析返回值类型
      Type returnType = TypeParameterResolver.resolveReturnType(method, type);
      //把对应的返回类型 转换成Class
      getTypes.put(name, typeToClass(returnType));
    }
  }
下面都是addSetMethods方法的讲述
private void addSetMethods(Class<?> cls) {
    Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
    Method[] methods = getClassMethods(cls);
    for (Method method : methods) {
      String name = method.getName();
      if (name.startsWith("set") && name.length() > 3) {
        if (method.getParameterTypes().length == 1) {
          name = PropertyNamer.methodToProperty(name);
          addMethodConflict(conflictingSetters, name, method);
        }
      }
    }
    resolveSetterConflicts(conflictingSetters);
  }

基本步骤都是一样的。但是有一点解决冲突的方法变了。具体如下代码片段

private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
    for (String propName : conflictingSetters.keySet()) {
      List<Method> setters = conflictingSetters.get(propName);
      Class<?> getterType = getTypes.get(propName);
      Method match = null;
      ReflectionException exception = null;
      for (Method setter : setters) {
        Class<?> paramType = setter.getParameterTypes()[0];
          //用getter的返回值类型与setter的参数类型 比较 如果相等就推出
        if (paramType.equals(getterType)) {
          // should be the best match
          match = setter;
          break;
        }
        if (exception == null) {
          try {
              //进行比较
            match = pickBetterSetter(match, setter, propName);
          } catch (ReflectionException e) {
            // there could still be the 'best match'
            match = null;
            exception = e;
          }
        }
      }
      if (match == null) {
        throw exception;
      } else {
        addSetMethod(propName, match);
      }
    }
  }
pickBetterSetter方法的如下分析
private Method pickBetterSetter(Method setter1, Method setter2, String property) {
    //如果一为空 则直接返回竞争者 即二
    if (setter1 == null) {
      return setter2;
    }
    Class<?> paramType1 = setter1.getParameterTypes()[0];
    Class<?> paramType2 = setter2.getParameterTypes()[0];
    
    //如果setter1是setter2的父类 就选择参数二
    if (paramType1.isAssignableFrom(paramType2)) {
      return setter2;
        //否则选择参数一
    } else if (paramType2.isAssignableFrom(paramType1)) {
      return setter1;
    }
    throw new ReflectionException("Ambiguous setters defined for property '" + property + "' in class '"
        + setter2.getDeclaringClass() + "' with types '" + paramType1.getName() + "' and '"
        + paramType2.getName() + "'.");
  }

然后

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

最后调用metaClass中的hasSetter方法

public boolean hasSetter(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      if (reflector.hasSetter(prop.getName())) {
        MetaClass metaProp = metaClassForProperty(prop.getName());
        return metaProp.hasSetter(prop.getChildren());
      } else {
        return false;
      }
    } else {
      return reflector.hasSetter(prop.getName());
    }
  }

PropertyTokenizer 处理更为复杂的属性 解析例如数组,或者字符串以.分割的进行 循环判断 然后不能重复 然后 把Properties返回

至此setting的 解析已经分析完毕了。

2.3解析TypeAliases配置项

typeAliases标签的作用及为Mybatis中, 我们可以使用类名称用自己定义的的别名,无需把全限定类名称写出来。Mybatis提供俩种配置选择。一种是批量 <package>标签 、一种是<typeAlias>标签。俩者区别在一个扫描整个包下。另一个单个申明

<typeAliases>
		<!-- 批量 -->
		<!-- <package name="com.ly.entity"/> -->
		<!-- 单个 -->
		<typeAlias type="com.ly.entity.User" alias="user"/>
	</typeAliases>

我们通过分析对应的源码来看看Mybatis是如何加载别名的

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        //遍历<typeAliases>下面所有的节点为子节点
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
            //说明是<package>下面 找到name属性 然后获得路径
          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) {
               //别名为空
              typeAliasRegistry.registerAlias(clazz);
            } else {
                //别名不为空
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }
registerAliases方法
public void registerAliases(String packageName){
    registerAliases(packageName, Object.class);
  }

  public void registerAliases(String packageName, Class<?> superType){
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    //这个是找到这个包路径下 父类为object的所有的类。
    //通过找到这些类然后加载到缓存内部集合中
    //通过 IsA中的matches方法进行判断 
    // matches.add((Class<T>) type);
    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()) {
        registerAlias(type);
      }
    }
  }

  public void registerAlias(Class<?> type) {
    //获得类的简写名称
    String alias = type.getSimpleName();
    //获得注解@Alias
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    //如果注解为空
    if (aliasAnnotation != null) {
       //就用类名称
      alias = aliasAnnotation.value();
    } 
    registerAlias(alias, type);
  }
   
  public void registerAlias(String alias, Class<?> value) {
      //如果名称为空就抛出异常
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    //把别名专换成大写然后为Key
    String key = alias.toLowerCase(Locale.ENGLISH);
      //如果别名存在  (包含这个key 并且key的Value不为空。这个key与value不一致)抛出异常
    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() + "'.");
    }//加入到别名Map中
    TYPE_ALIASES.put(key, value);
  }

其实殊途同归到 跟到最后的源码都引用的一样的方法

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

加上 上面我们曾经写道过的Configuration中的

其实Mybatis在加载的时候就会在别名注册中心注册一些默认的别名进去

2.4 pluginElement方法来加载plugin标签

此处引用
作者:田小波
链接:https://www.imooc.com/article/48573?block_id=tuijian_wz 来源:慕课网
插件是 MyBatis 提供的一个拓展机制,通过插件机制我们可在 SQL 执行过程中的某些点上做一些自定义操作。实现一个插件需要比简单,首先需要让插件类实现Interceptor接口。然后在插件类上添加@Intercepts和@Signature注解,用于指定想要拦截的目标方法。MyBatis 允许拦截下面接口中的一些方法:

  • Executor: update 方法,query 方法,flushStatements 方法,commit 方法,rollback 方法, getTransaction 方法,close 方法,isClosed 方法
  • ParameterHandler: getParameterObject 方法,setParameters 方法
  • ResultSetHandler: handleResultSets 方法,handleOutputParameters 方法
  • StatementHandler: prepare 方法,parameterize 方法,batch 方法,update 方法,query 方法
    比较常见的插件有分页插件、分表插件等,有兴趣的朋友可以去了解下。本节我们来分析一下插件的配置的解析过程,先来了解插件的配置。如下:
<plugins>
    <!-- com.github.pagehelper为PageHelper类所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
        <property name="param1" value="value1"/>
	</plugin>
</plugins>
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        //还是遍历节点
      for (XNode child : parent.getChildren()) {
        
        String interceptor = child.getStringAttribute("interceptor");
          //找到interceptor 属性
          
        Properties properties = child.getChildrenAsProperties();
          //解析拦截器属性并创建拦截器 里面代码过于简单不在此处说明了。。一看newInstance 就是创建了。。
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        //设置属性
        interceptorInstance.setProperties(properties);
          //添加拦截器进入configuration中
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
    for (XNode child : getChildren()) {
      String name = child.getStringAttribute("name");
      String value = child.getStringAttribute("value");
      if (name != null && value != null) {
        properties.setProperty(name, value);
      }
    }
    return properties;
  }

值得看的也就这块了。其他真的没什么可说的。

public <T> Class<T> resolveAlias(String string) {
    try {
      if (string == null) {
        return null;
      }
      // issue #748
      String key = string.toLowerCase(Locale.ENGLISH);
      Class<T> value;
      if (TYPE_ALIASES.containsKey(key)) {
        value = (Class<T>) TYPE_ALIASES.get(key);
      } else {
          //可以理解为类加载器
        value = (Class<T>) Resources.classForName(string);
      }
      return value;
    } catch (ClassNotFoundException e) {
      throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
    }
  }

2.5objectFactoryElement

说句心里话本身抱着学习源码的精神就学习一下。不过没什么蛋用。。你用Mybatis实际生产中难道还需要重写他工厂方法么。。真的蛋疼。。

2.6objectWrapperFactoryElement|reflectorFactoryElement

其实上面在写setting中的时候已经涉及了这方面的只是了。就是反射。反射。框架都是反射用的多。我觉得这三个可以不用深入了解。除非公司需要。一般大多数日常生产中 并不需要关注

2.7environmentsElement

在 MyBatis 中,事务管理器和数据源是配置在 environments 中的。它们的配置大致如下:

<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${db.driver}" />
				<property name="url" value="${db.url}" />
				<property name="username" value="${db.username}" />
				<property name="password" value="${db.password}" />
			</dataSource>
		</environment>
	</environments>
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)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

上面的代码一眼就看到头。。本着分析源码的态度。

  1. default=“development” 默认使用
  2. 判断id是否有值 判断environment是否有 没有都返回false 然后判断这俩个是不是一样
  3. 然后就是加载事务工厂。数据源工厂。然后通过构造者模式 。加载者俩种进Environment
  4. 添加进Configuration

2.8settingsElement

这个本身应该衔接setting那快进行分析。但是忘记了。。现在补一下。其实很简单就是把对应的配置文件加载到Configuration中

private void settingsElement(Properties props) throws Exception {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    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"), false));
    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.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), 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")));
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
    configuration.setDefaultEnumTypeHandler(typeHandler);
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    @SuppressWarnings("unchecked")
    Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }

resolveClass我们上面在alias中也讲述过了。。就是别名注册那块

2.9 databaseIdProviderElement

多数据源配置

<databaseIdProvider type="DB_VENDOR"> 
    <property name="MySQL" value="mysql" /> 
    <property name="Oracle" value="oracle" /> 
</databaseIdProvider>
private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
      String type = context.getStringAttribute("type");
      // awful patch to keep backward compatibility
      if ("VENDOR".equals(type)) {
          type = "DB_VENDOR";
      }
        //获得属性
      Properties properties = context.getChildrenAsProperties();
      //创建实例
        databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
      //属性赋值
        databaseIdProvider.setProperties(properties);
    }
    //获得environments对象
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
        //这个里面有逻辑大家自行点击。我总结一下。就是他根据DataSource 数据源 然后获得是什么数据库而已。然后加载进configuration
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
      configuration.setDatabaseId(databaseId);
    }
  }

篇幅已经很大了。下一篇继续分析typeHandlerElement|mapperElement