Mybatis source code analysis – configuration file loading

Time:2020-2-7

stayIn mybatis source code analysis – environment building, our test code is as follows:

public static void main(String[] args) throws IOException { 
    String resource = "mybatis-config.xml";  
    InputStream inputStream = Resources.getResourceAsStream(resource);  
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  
    SqlSession sqlSession = sqlSessionFactory.openSession();  
    try {  
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);  
        List<Dept> deptList = deptMapper.getAllDept();  
        System.out.println(deptList);  
    } finally {  
        sqlSession.close();  
    }  
 }  

Mybatis first needs to load the configuration file, including the global configuration file and the mapping file. In the code, we use the getresourceasstream method of the resources class to get the InputStream object, so we can carry out the following operations. Therefore, we will study the resources class first.

This section focuses on the following code:

String resource = "mybatis-config.xml";  
InputStream inputStream = Resources.getResourceAsStream(resource);

Load profile

Before we study resources, let’s consider how to get an input stream, that is, to read a file?

  • Using FileInputStream
  • Getresourceasstream method using classloader
  • Getresourceasstream method using class. Class

Using FileInputStream
The limitation is very large, because the absolute path is generally not used, the second way of packing and executing jar package in the following code will lead to the error that the file cannot be found.

public static void main(String[] args) throws IOException {  
  
    //Or all the way  
    File file1 = new File("D:\\my-code\\mybatis\\src\\main\\resources\\mybatis-config.xml");  
    InputStream input1 = new FileInputStream(file1);  
    System.out.println(input1.available());  
  
    //Or remove the project name based on the full path  
    File file2 = new File("src\\main\\resources\\mybatis-config.xml");  
    InputStream input2 = new FileInputStream(file2);  
    System.out.println(input2.available());  
}

Getresourceasstream method using classloader

public static void main(String[] args) throws IOException {  
    InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("mappers/DeptMapper.xml");  
    System.out.println(inputStream.available());  
}

This method can get the configuration file under resources, but if the configuration file exists under the Java directory of the same level as resources, can it be obtained? We move the configuration file to the directory of Java code, and use the following code to get the file:

public static void main(String[] args) throws IOException {  
    InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("com/yefengyu/mybatis/mappers/DeptMapper.xml");  
    System.out.println(inputStream.available());  
}

The result is a null pointer exception and the related file could not be found.

Exception in thread "main" java.lang.NullPointerException
    at com.yefengyu.mybatis.Main.main(Main.java:22)

Special attention here: when using classloader to get files, they are all obtained from the class path, that is, the classes directory under the target directory generated after Maven install, rather than the source path. Using the idea development tool, the configuration files under the code directory will not be installed under classes. So if some people like to write the mapper configuration file to the Java directory instead of the resources directory, how to solve it?

Add the following configuration to pom.xml file: that is, load the configuration files under Java and resources into the classpath.

<build>
    <finalName>strategy-service</finalName>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
    </resources>
</build>

Getresourceasstream method using class. Class

public static void main(String[] args) throws IOException {  
    InputStream resource=Main.class.getResourceAsStream("mybatis-config.xml");  
    System.out.println(resource.available());  
}

Note that mybatis-config.xml is at the root of resources. Why can’t it be obtained?

Exception in thread "main" java.lang.NullPointerException
    at com.yefengyu.mybatis.Main.main(Main.java:22)

Check the following source code:

 public InputStream getResourceAsStream(String name) {  
    name = resolveName(name);  
    ClassLoader cl = getClassLoader0();  
    if (cl==null) {  
        // A system class.  
        return ClassLoader.getSystemResourceAsStream(name);  
    }  
    return cl.getResourceAsStream(name);  
}

It is found that this method also uses classloader to get files, so why is it not loaded to files? This is the main sentence:

name = resolveName(name);  

Here the file name is re resolved:

private String resolveName(String name) {  
    if (name == null) {  
        return name;  
    }  
    if (!name.startsWith("/")) {  
        Class<?> c = this;  
        while (c.isArray()) {  
            c = c.getComponentType();  
        }  
        String baseName = c.getName();  
        int index = baseName.lastIndexOf('.');  
        if (index != -1) {  
            name = baseName.substring(0, index).replace('.', '/')  
                +"/"+name;  
        }  
    } else {  
        name = name.substring(1);  
    }  
    return name;  
}

This is mainly to obtain the relative path of the calling class first, and then the file path we passed in. That is to say, we found it under the Java directory, but we couldn’t find it naturally.

If we put the mybatis-config.xml configuration file into the same level directory of the class where the main function is located, you can see that the file can be loaded after reinstalling.

How to implement mybatis?

Let’s look at the resources class. There are two methods for getting InputStream:

public static InputStream getResourceAsStream(String resource) throws IOException { 
    return getResourceAsStream((ClassLoader)null, resource);  
}  
  
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {  
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);  
    if (in == null) {  
        throw new IOException("Could not find resource " + resource);  
    } else {  
        return in;  
    }  
}

The first method calls the second method, passing only one parameter, which is the file name. Then call the second overloaded method to the empty ClassLoader object. The second method also uses classloaderwrapper to get the InputStream object.

Classloaderwrapper is an encapsulation of classloader. The main purpose is to try a variety of classloaders to load files. Related code:

public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {  
  return getResourceAsStream(resource, getClassLoaders(classLoader));  
}

This code is the method called by classloaderwrapper in the resources class. It also calls its overloaded method. Before looking at the overloaded method, let’s look at the getclassloaders method, which is to get as many classloaders as possible

ClassLoader[] getClassLoaders(ClassLoader classLoader) {  
  return new ClassLoader[]{  
      classLoader,  
      defaultClassLoader,  
      Thread.currentThread().getContextClassLoader(),  
      getClass().getClassLoader(),  
      systemClassLoader};  
}

Five classloaders are returned here. See the getresourceasstream method below:

InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
  for (ClassLoader cl : classLoader) {
    if (null != cl) {

      InputStream returnValue = cl.getResourceAsStream(resource);

      if (null == returnValue) {
        returnValue = cl.getResourceAsStream("/" + resource);
      }

      if (null != returnValue) {
        return returnValue;
      }
    }
  }
  return null;
}

Here we still use classloader to get InputStream object. One classloader does not get it, and the next classloader continues until all of them try to load the file, which is not too difficult.

For the resources class, in addition to getting InputStream, there are also URLs, properties, readers, files, etc. the code is the same, so there is no need to talk about it.

Harvest

  • Learn more about loading profiles
  • The elegant way to write overloaded methods: methods with fewer input parameters call methods with more input parameters, and those without parameters use default values. Finally, methods with the most parameters are processed uniformly.