• 【MyBatis源码分析】Configuration加载(上篇)


    config.xml解析为org.w3c.dom.Document

    本文首先来简单看一下MyBatis中将config.xml解析为org.w3c.dom.Document的流程,代码为上文的这部分:

    1 static {
    2     try {
    3         reader = Resources.getResourceAsReader("mybatis/config.xml");
    4         ssf = new SqlSessionFactoryBuilder().build(reader);
    5     } 
    6     catch (IOException e) {
    7         e.printStackTrace();
    8     }
    9 }

    第3行的代码实现为:

    1 public static Reader getResourceAsReader(String resource) throws IOException {
    2     Reader reader;
    3     if (charset == null) {
    4       reader = new InputStreamReader(getResourceAsStream(resource));
    5     } else {
    6       reader = new InputStreamReader(getResourceAsStream(resource), charset);
    7     }
    8     return reader;
    9 }

    相当于就是将输入的路径转换为一个字符输入流并返回。

    接着继续看静态块第4行的代码,new SqlSessionFactoryBuilder().build(reader),把代码定位到SqlSessionFactoryBuilder类的builder方法,这里使用了多态,直接跟到build方法:

     1 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
     2     try {
     3       XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
     4       return build(parser.parse());
     5     } catch (Exception e) {
     6       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
     7     } finally {
     8       ErrorContext.instance().reset();
     9       try {
    10         reader.close();
    11       } catch (IOException e) {
    12         // Intentionally ignore. Prefer previous error.
    13       }
    14     }
    15 }

    解析config.xml的代码在第3行XMLConfigBuilder类的构造方法中,看一下XMLConfigBuilder类的构造方法做了什么:

     1 public XMLConfigBuilder(Reader reader, String environment, Properties props) {
     2     this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
     3 }

    这里的关键是第二行代码的第一个参数XPathParser,看一下实例化XPathParser类的代码:

    1 public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    2     commonConstructor(validation, variables, entityResolver);
    3     this.document = createDocument(new InputSource(reader));
    4 }

    第2行的代码commonConstructor方法没什么好看的,将validation、variables、entityResolver设置到XPathParser类的参数中而已,顺便再实例化一个javax.xml.xpath.XPath出来,XPath用于在XML文档中通过元素和属性进行导航,并对元素和属性进行遍历。

    接着看第3行的createDocument方法:

     1 private Document createDocument(InputSource inputSource) {
     2     // important: this must only be called AFTER common constructor
     3     try {
     4       DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
     5       factory.setValidating(validation);
     6 
     7       factory.setNamespaceAware(false);
     8       factory.setIgnoringComments(true);
     9       factory.setIgnoringElementContentWhitespace(false);
    10       factory.setCoalescing(false);
    11       factory.setExpandEntityReferences(true);
    12 
    13       DocumentBuilder builder = factory.newDocumentBuilder();
    14       builder.setEntityResolver(entityResolver);
    15       builder.setErrorHandler(new ErrorHandler() {
    16         @Override
    17         public void error(SAXParseException exception) throws SAXException {
    18           throw exception;
    19         }
    20 
    21         @Override
    22         public void fatalError(SAXParseException exception) throws SAXException {
    23           throw exception;
    24         }
    25 
    26         @Override
    27         public void warning(SAXParseException exception) throws SAXException {
    28         }
    29       });
    30       return builder.parse(inputSource);
    31     } catch (Exception e) {
    32       throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    33     }
    34 }

    看一下第5行~第11行的代码设置DocumentBuilderFactory中参数的含义:

    • setValidating表示是否验证xml文件,这个验证是DTD验证
    • setNamespaceAware表示是否支持xml命名空间
    • setIgnoringComments表示是否忽略注释
    • setIgnoringElementContentWhitespace表示是否忽略元素中的空白
    • setCoalescing表示是否将CDATA节点转换为Text节点,并将其附加到相邻(如果有)的Text节点
    • setExpandEntityReferences表示是否扩展实体引用节点

    第13行的代码由设置的参数从DocumentBuilderFactory中获取一个DocumentBuilder实例DocumentBuilderImpl,并由第14行的代码设置一个实体解析器,由第15行~第29行的代码设置一个错误处理器。

    最后看一下第30行的代码parse方法:

     1 public Document parse(InputSource is) throws SAXException, IOException {
     2         if (is == null) {
     3             throw new IllegalArgumentException(
     4                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
     5                 "jaxp-null-input-source", null));
     6         }
     7         if (fSchemaValidator != null) {
     8             if (fSchemaValidationManager != null) {
     9                 fSchemaValidationManager.reset();
    10                 fUnparsedEntityHandler.reset();
    11             }
    12             resetSchemaValidator();
    13         }
    14         domParser.parse(is);
    15         Document doc = domParser.getDocument();
    16         domParser.dropDocumentReferences();
    17         return doc;
    18 }

    看过Spring配置文件解析源码的朋友应该对这一段代码比较熟悉,一样的,使用DocumentBuilder将解析InputSource成org.w3c.dom.Document并将Document存储到XPathParser中。

    Document转换为Configuration

    前面的代码将config.xml转换为了org.w3c.dom.Document,下一步就是将org.w3c.dom.Document中的内容转换为Java对象了,其中最主要的一个对象就是org.apache.ibatis.session.Configuration,还是回到之前的SqlSessionFactoryBuilder的build方法:

     1 public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
     2     try {
     3       XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
     4       return build(parser.parse());
     5     } catch (Exception e) {
     6       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
     7     } finally {
     8       ErrorContext.instance().reset();
     9       try {
    10         reader.close();
    11       } catch (IOException e) {
    12         // Intentionally ignore. Prefer previous error.
    13       }
    14     }
    15 }

    先看一下第4行的parse方法,parse方法是XMLConfigBuilder中的,之前重点分析了它的属性XPathParser,看一下XMLConfigBuilder的parse方法是如何实现的:

    1 public Configuration parse() {
    2     if (parsed) {
    3       throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    4     }
    5     parsed = true;
    6     parseConfiguration(parser.evalNode("/configuration"));
    7     return configuration;
    8 }

    这里看一下第6行,可以使用XPathParser的evalNode方法解析标签,后面解析标签会大量用到此方法,此方法将标签解析为XNode,像config.xml(可见上一篇文章的示例)解析完之后的XNode,toString()方法输出的内容是这样的:

    <configuration>
    <properties resource="properties/db.properties"/>
    <settings>
    <setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="useGeneratedKeys" value="true"/>
    </settings>
    <typeAliases>
    <typeAlias alias="Mail" type="org.xrq.mybatis.pojo.Mail"/>
    </typeAliases>
    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
    <property name="driver" value="${driveClass}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${userName}"/>
    <property name="password" value="${password}"/>
    </dataSource>
    </environment>
    </environments>
    <mappers>
    <mapper resource="mybatis/mail.xml"/>
    </mappers>
    </configuration>

    可见xml文件中<configuration>中所有内容都已经被成功解析并放在XNode中了,剩下的只要调用XNode的方法获取自己想要的内容即可。

    最后扫一眼parseConfiguration方法,之所以说扫一眼,因为之后要分析里面的一些常用的和重点的内容,这里只是列一下代码而已:

     1 private void parseConfiguration(XNode root) {
     2     try {
     3       Properties settings = settingsAsPropertiess(root.evalNode("settings"));
     4       //issue #117 read properties first
     5       propertiesElement(root.evalNode("properties"));
     6       loadCustomVfs(settings);
     7       typeAliasesElement(root.evalNode("typeAliases"));
     8       pluginElement(root.evalNode("plugins"));
     9       objectFactoryElement(root.evalNode("objectFactory"));
    10       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    11       reflectorFactoryElement(root.evalNode("reflectorFactory"));
    12       settingsElement(settings);
    13       // read it after objectFactory and objectWrapperFactory issue #631
    14       environmentsElement(root.evalNode("environments"));
    15       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    16       typeHandlerElement(root.evalNode("typeHandlers"));
    17       mapperElement(root.evalNode("mappers"));
    18     } catch (Exception e) {
    19       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    20     }
    21 }

    这里就是逐个解析<configuration>标签下的子标签,并将数据设置到对应的属性中,这里要一个一个看一下。

    settings解析

    首先看settingsAsPropertiess(root.evalNode("settings"))这句代码,显而易见这句话获取了<configuration>下的<settings>节点。跟一下代码的实现:

     1 private Properties settingsAsPropertiess(XNode context) {
     2     if (context == null) {
     3       return new Properties();
     4     }
     5     Properties props = context.getChildrenAsProperties();
     6     // Check that all settings are known to the configuration class
     7     MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
     8     for (Object key : props.keySet()) {
     9       if (!metaConfig.hasSetter(String.valueOf(key))) {
    10         throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
    11       }
    12     }
    13     return props;
    14 }

    第5行将节点解析成键值对的形式(Properties是Hashtable的子类),看一下props的toString方法打印的内容:

    {useGeneratedKeys=true, lazyLoadingEnabled=true, cacheEnabled=true}

    可见settings里面的数据已经被解析成了Properties了。之后还有一步,<settings>标签下的每个<setting>中的name属性不是随便填写的,都是MyBatis支持的配置,因此需要对Properties里面的Key做一个校验,校验的代码就是第7行至第12行的代码,其中有一个name不是MyBatis支持的就会抛出异常,MyBatis初始化整体失败。

    至于具体校验的是哪些Key,这就要跟一下第7行的代码了,首先是MetaClass.forClass(Configuration.class, localReflectorFactory),第二个实参是XMLConfigBuilder里面直接new出来的,它的实际类型为DefaultReflectorFactory,看一下forClass方法实现:

     1 public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
     2     return new MetaClass(type, reflectorFactory);
     3 }

    看一下new MetaClass做了什么事:

    1 private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
    2     this.reflectorFactory = reflectorFactory;
    3     this.reflector = reflectorFactory.findForClass(type);
    4 }

    显而易见,继续跟一下第3行的代码DefaultRelectorFactory的findForClass方法:

     1 public Reflector findForClass(Class<?> type) {
     2     if (classCacheEnabled) {
     3             // synchronized (type) removed see issue #461
     4       Reflector cached = reflectorMap.get(type);
     5       if (cached == null) {
     6         cached = new Reflector(type);
     7         reflectorMap.put(type, cached);
     8       }
     9       return cached;
    10     } else {
    11       return new Reflector(type);
    12     }
    13 }

    不管怎么样都会执行new Reflector(type)这一句代码,看一下此时做了什么事,注意传入的参数是Configuration的class对象:

     1 public Reflector(Class<?> clazz) {
     2     type = clazz;
     3     addDefaultConstructor(clazz);
     4     addGetMethods(clazz);
     5     addSetMethods(clazz);
     6     addFields(clazz);
     7     readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
     8     writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
     9     for (String propName : readablePropertyNames) {
    10       caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    11     }
    12     for (String propName : writeablePropertyNames) {
    13       caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    14     }
    15 }

    这么多方法至于具体要看哪个,要注意的是之前XMLConfigBuilder里面对于Key的判断是"!metaConfig.hasSetter(String.valueOf(key))",代码的意思是判断的是否Key有set方法,那么显而易见这里要继续跟第5行的addSetMethods方法:

     1 private void addSetMethods(Class<?> cls) {
     2     Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
     3     Method[] methods = getClassMethods(cls);
     4     for (Method method : methods) {
     5       String name = method.getName();
     6       if (name.startsWith("set") && name.length() > 3) {
     7         if (method.getParameterTypes().length == 1) {
     8           name = PropertyNamer.methodToProperty(name);
     9           addMethodConflict(conflictingSetters, name, method);
    10         }
    11       }
    12     }
    13     resolveSetterConflicts(conflictingSetters);
    14 }

    到这里应该很明显了,结论就是:<setting>的name属性对应的值,必须在Configuration类有相应的Setter,比如设置了一个属性useGenerateKeys方法,那么必须在Configuration类中有setUseGenerateKeys方法才行

    顺便说一下第13行有一个resolveSetterConflicts方法,其作用是:Setter有可能在类中被重载导致有多个,此时取Setter中方法参数只有一个且参数类型与Getter一致的Setter

    properties解析

    接着看一下propertiesElement(root.evalNode("properties"))方法,这句读取的是<configuration>下的<properties>节点,代码实现为:

     1 private void propertiesElement(XNode context) throws Exception {
     2     if (context != null) {
     3       Properties defaults = context.getChildrenAsProperties();
     4       String resource = context.getStringAttribute("resource");
     5       String url = context.getStringAttribute("url");
     6       if (resource != null && url != null) {
     7         throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
     8       }
     9       if (resource != null) {
    10         defaults.putAll(Resources.getResourceAsProperties(resource));
    11       } else if (url != null) {
    12         defaults.putAll(Resources.getUrlAsProperties(url));
    13       }
    14       Properties vars = configuration.getVariables();
    15       if (vars != null) {
    16         defaults.putAll(vars);
    17       }
    18       parser.setVariables(defaults);
    19       configuration.setVariables(defaults);
    20     }
    21 }

    看到第4行~第7行的代码指定了MyBatis的<properties>标签下不能同时指定"resource"属性和"url"属性。

    接着第9行~第13行的代码将.properties资源解析为Properties类,最后将Properties类设置到XPathParser和Configuration的variables属性中,variables是一个Propreties变量。

    类型别名解析

    跳过loadCustomVfs(settings)直接看typeAliasesElement(root.evalNode("typeAliases"))这行,因为前者我也没看懂干什么用的,后者是用于定义类型的别名的,解析的是<configuration>下的<typeAliases>标签,用过MyBatis的应该很熟悉。看一下源码实现:

     1 private void typeAliasesElement(XNode parent) {
     2     if (parent != null) {
     3       for (XNode child : parent.getChildren()) {
     4         if ("package".equals(child.getName())) {
     5           String typeAliasPackage = child.getStringAttribute("name");
     6           configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
     7         } else {
     8           String alias = child.getStringAttribute("alias");
     9           String type = child.getStringAttribute("type");
    10           try {
    11             Class<?> clazz = Resources.classForName(type);
    12             if (alias == null) {
    13               typeAliasRegistry.registerAlias(clazz);
    14             } else {
    15               typeAliasRegistry.registerAlias(alias, clazz);
    16             }
    17           } catch (ClassNotFoundException e) {
    18             throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
    19           }
    20         }
    21       }
    22     }
    23 }

    从源码实现中我们可以知道两点,<typeAliases>标签下可以定义<package>和<typeAlias>两种标签,但是看第4行和第7行的判断,这是一段if...else...,因此可以知道<package>标签和<typeAlias>标签只能定义其中的一种。首先看一下解析<package>标签的代码,第6行的registerAliases方法:

     1 public void registerAliases(String packageName, Class<?> superType){
     2     ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
     3     resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
     4     Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
     5     for(Class<?> type : typeSet){
     6       // Ignore inner classes and interfaces (including package-info.java)
     7       // Skip also inner classes. See issue #6
     8       if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
     9         registerAlias(type);
    10       }
    11     }
    12 }

    第3行根据路径packageName寻找它下面的".class"文件拿到所有的".class"文件对应的类的Class,然后遍历所有的Class,做了三层判断

    • 必须不是匿名类
    • 必须不是接口
    • 必须不是成员类

    此时此Class对应的类符合条件,会进行注册,通过registerAlias方法进行注册,看一下方法实现:

    1 public void registerAlias(Class<?> type) {
    2     String alias = type.getSimpleName();
    3     Alias aliasAnnotation = type.getAnnotation(Alias.class);
    4     if (aliasAnnotation != null) {
    5       alias = aliasAnnotation.value();
    6     } 
    7     registerAlias(alias, type);
    8 }

    第2行获取Class的simpleName,simpleName指的是移除了包名的名称,比如aa.bb.cc.Mail,getSimpleName()获取的就是Mail。

    第3行获取类上面的注解Alias,如果Alias注解中有定义value属性且指定了值,那么第4行~第6行的判断优先取这个值作为Class的别名。

    第7行注册别名:

     1 public void registerAlias(String alias, Class<?> value) {
     2     if (alias == null) {
     3       throw new TypeException("The parameter alias cannot be null");
     4     }
     5     // issue #748
     6     String key = alias.toLowerCase(Locale.ENGLISH);
     7     if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
     8       throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
     9     }
    10     TYPE_ALIASES.put(key, value);
    11 }

    其实就做了两步操作:

    1. 将alias全部小写
    2. 将alias以及Class对象放到TYPE_ALIASES中,TYPE_ALIASES是一个HashMap

    这样一个流程,就将<package>标签name属性路径下的Class(如果符合要求),全部放到了HashMap中以供使用。

    接着看一下<typeAlias>标签的解析,也就是前面说的else部分:

     1 String alias = child.getStringAttribute("alias");
     2 String type = child.getStringAttribute("type");
     3 try {
     4   Class<?> clazz = Resources.classForName(type);
     5   if (alias == null) {
     6     typeAliasRegistry.registerAlias(clazz);
     7   } else {
     8     typeAliasRegistry.registerAlias(alias, clazz);
     9   }
    10 } catch (ClassNotFoundException e) {
    11   throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
    12 }

    这里先解析<typeAlias>中的alias属性,再解析<typeAlias>中的type属性,当然alias也可以不定义,不定义走的就是第6行的registerAlias方法,定义走的就是第8行的registerAlias方法,这两个重载的registerAlias方法前面也都说过了,就不说了。

    默认typeAlias

    上面说的是自定义typeAlias,MyBatis本身也默认提供给开发者了一些typeAlias定义,在两处地方。第一处地方在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);
    
        ...
      }

    第二处地方是在TypeAliasRegistry的构造方法中:

     1 public TypeAliasRegistry() {
     2     registerAlias("string", String.class);
     3 
     4     registerAlias("byte", Byte.class);
     5     registerAlias("long", Long.class);
     6     registerAlias("short", Short.class);
     7     registerAlias("int", Integer.class);
     8     registerAlias("integer", Integer.class);
     9     registerAlias("double", Double.class);
    10     registerAlias("float", Float.class);
    11     registerAlias("boolean", Boolean.class);
    12 
    13     registerAlias("byte[]", Byte[].class);
    14     registerAlias("long[]", Long[].class);
    15     registerAlias("short[]", Short[].class);
    16     registerAlias("int[]", Integer[].class);
    17     registerAlias("integer[]", Integer[].class);
    18     registerAlias("double[]", Double[].class);
    19     registerAlias("float[]", Float[].class);
    20     registerAlias("boolean[]", Boolean[].class);
    21 
    22     registerAlias("_byte", byte.class);
    23     registerAlias("_long", long.class);
    24     registerAlias("_short", short.class);
    25     registerAlias("_int", int.class);
    26     registerAlias("_integer", int.class);
    27     registerAlias("_double", double.class);
    28     registerAlias("_float", float.class);
    29     registerAlias("_boolean", boolean.class);
    30 
    31     registerAlias("_byte[]", byte[].class);
    32     registerAlias("_long[]", long[].class);
    33     registerAlias("_short[]", short[].class);
    34     registerAlias("_int[]", int[].class);
    35     registerAlias("_integer[]", int[].class);
    36     registerAlias("_double[]", double[].class);
    37     registerAlias("_float[]", float[].class);
    38     registerAlias("_boolean[]", boolean[].class);
    39 
    40     registerAlias("date", Date.class);
    41     registerAlias("decimal", BigDecimal.class);
    42     registerAlias("bigdecimal", BigDecimal.class);
    43     registerAlias("biginteger", BigInteger.class);
    44     registerAlias("object", Object.class);
    45 
    46     registerAlias("date[]", Date[].class);
    47     registerAlias("decimal[]", BigDecimal[].class);
    48     registerAlias("bigdecimal[]", BigDecimal[].class);
    49     registerAlias("biginteger[]", BigInteger[].class);
    50     registerAlias("object[]", Object[].class);
    51 
    52     registerAlias("map", Map.class);
    53     registerAlias("hashmap", HashMap.class);
    54     registerAlias("list", List.class);
    55     registerAlias("arraylist", ArrayList.class);
    56     registerAlias("collection", Collection.class);
    57     registerAlias("iterator", Iterator.class);
    58 
    59     registerAlias("ResultSet", ResultSet.class);
    60 }

    对于这些数据,我们可以直接使用registerAlias方法的第一个参数对应的字符串而不需要定义这些typeAlias。

  • 相关阅读:
    centos 新增用户, 然后他在主目录添加网站403Forbbiden
    linux 把用户加入一个组&从这个组中移除
    [THINKPHP] 温故知新之getFieldBy
    php 获取指定月份的开始结束时间
    apache 占用内存总量与每个apache进程的平均内存占用量计算
    网站并发300就很慢
    centos定时备份数据库超简单示例
    php导出excel时间错误(同一个时间戳,用date得到不同的时间)
    设置iframe 载入页面的效果跟直接打开这个页面一样
    node基础09:第2个node web服务器
  • 原文地址:https://www.cnblogs.com/xrq730/p/6802027.html
Copyright © 2020-2023  润新知