The first part of Spring source code is open and complete! How is the configuration file loaded?

Posted by one on Mon, 15 Jun 2020 05:11:04 +0200

Last week, I put my words out. It seems that all my friends are looking forward to it. In fact, SongGe can't wait to start a new series.

However, the current Spring Security series is still in the process of serialization and has not been completed yet. It's a series of events. Keep your spirits up, and then you'll lose three times. It must be done once. If Spring Security is put down this time, it's hard to pick it up again.

So the current update is mainly Spring Security. At the same time, the Spring source code interpretation shall be updated at least once a week. After the Spring Security series is updated, the Spring source code will be updated with full power. In fact, Spring Security also has a lot of things in common with Spring. Take a look at the article of Spring Security, brother song will not let you down!

1. Where to start

Where does Spring start? I've been thinking about this for a long time.

Because the Spring source code is too complex, we must choose a suitable entry point, otherwise we will muddle up our little partners as soon as we come up, and we will not want to read the rest of the article.

After thinking about it for a long time, I decided to start with the configuration file loading, which is also the first problem we encounter when using Spring. Let's talk about this topic today.

2. Simple cases

Let's start with a simple case, let's feel it, and then let's start with the case.

First, we create a common Maven project and introduce the spring beans dependency:

<dependency>
    <groupid>org.springframework</groupid>
    <artifactid>spring-beans</artifactid>
    <version>5.2.6.RELEASE</version>
</dependency>

Then we create an entity class and add a simple configuration file:

public class User {
    private String username;
    private String address;
    //Omit getter/setter
}

Create the configuration file in the resources directory:

<!--?xml version="1.0" encoding="UTF-8"?-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="org.javaboy.loadxml.User" id="user" />
</beans>

Then load the configuration file:

public static void main(String[] args) {
    XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
    User user = factory.getBean(User.class);
    System.out.println("user = " + user);
}

In order to show the data reading process, I first load the expired XmlBeanFactory, which does not affect our reading of the source code.

The above is a very simple case of Spring introduction. I believe that many young partners may write this Demo when they first contact Spring.

In the above code execution process, the first thing to do is to load the XML configuration file into memory, then parse it, and then.....

Step by step, let's see how XML files are added to memory.

3. File reading

File reading is very common in Spring, which is also a basic function. Besides, the file loading method provided by Spring can be used not only in the Spring framework, but also in other file loading requirements in the project.

First of all, Spring uses the Resource interface to encapsulate the underlying resources. The Resource interface itself is implemented from the InputStreamSource interface:

Let's look at the definitions of these two interfaces:

public interface InputStreamSource {
	InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
	boolean exists();
	default boolean isReadable() {
		return exists();
	}
	default boolean isOpen() {
		return false;
	}
	default boolean isFile() {
		return false;
	}
	URL getURL() throws IOException;
	URI getURI() throws IOException;
	File getFile() throws IOException;
	default ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}
	long contentLength() throws IOException;
	long lastModified() throws IOException;
	Resource createRelative(String relativePath) throws IOException;
	@Nullable
	String getFilename();
	String getDescription();

}

The code is not difficult. Let me explain it a little bit:

  1. InputStreamSource class only provides a getInputStream method, which returns an InputStream. That is to say, InputStreamSource will encapsulate the incoming File and other resources into an InputStream and return again.
  2. The Resource interface implements the InputStreamSource interface and encapsulates the underlying resources that Spring may use, such as File, URL and classpath.
  3. The exists method is used to determine whether a resource exists.
  4. The isReadable method is used to determine whether the resource is readable.
  5. The isOpen method is used to determine whether the resource is open or not.
  6. The isFile method is used to determine whether a resource is a file.
  7. getURL/getURI/getFile/readableChannel respectively means to get the corresponding URL/URI/File of the resource and to turn the resource into ReadableByteChannel channel.
  8. contentLength indicates the size of the acquired resource.
  9. lastModified indicates the last modified time to get the resource.
  10. createRelative means to create a relative resource based on the current resource.
  11. getFilename means get filename.
  12. getDescription means to print out the error file in detail when the resource fails.

When we load different resources, we correspond to different implementation classes of resources. Let's see the inheritance relationship of resources:

As you can see, there are different implementations for different types of data sources. Let's focus on the implementation of ClassPathResource.

ClassPathResource has a long source code. I will select some key parts to share with you:

public class ClassPathResource extends AbstractFileResolvingResource {

	private final String path;

	@Nullable
	private ClassLoader classLoader;

	@Nullable
	private Class<!--?--> clazz;

	public ClassPathResource(String path) {
		this(path, (ClassLoader) null);
	}
	public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
		Assert.notNull(path, "Path must not be null");
		String pathToUse = StringUtils.cleanPath(path);
		if (pathToUse.startsWith("/")) {
			pathToUse = pathToUse.substring(1);
		}
		this.path = pathToUse;
		this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
	}
	public ClassPathResource(String path, @Nullable Class<!--?--> clazz) {
		Assert.notNull(path, "Path must not be null");
		this.path = StringUtils.cleanPath(path);
		this.clazz = clazz;
	}
	public final String getPath() {
		return this.path;
	}
	@Nullable
	public final ClassLoader getClassLoader() {
		return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
	}
	@Override
	public boolean exists() {
		return (resolveURL() != null);
	}
	@Nullable
	protected URL resolveURL() {
		if (this.clazz != null) {
			return this.clazz.getResource(this.path);
		}
		else if (this.classLoader != null) {
			return this.classLoader.getResource(this.path);
		}
		else {
			return ClassLoader.getSystemResource(this.path);
		}
	}
	@Override
	public InputStream getInputStream() throws IOException {
		InputStream is;
		if (this.clazz != null) {
			is = this.clazz.getResourceAsStream(this.path);
		}
		else if (this.classLoader != null) {
			is = this.classLoader.getResourceAsStream(this.path);
		}
		else {
			is = ClassLoader.getSystemResourceAsStream(this.path);
		}
		if (is == null) {
			throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
		}
		return is;
	}
	@Override
	public URL getURL() throws IOException {
		URL url = resolveURL();
		if (url == null) {
			throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
		}
		return url;
	}
	@Override
	public Resource createRelative(String relativePath) {
		String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
		return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) :
				new ClassPathResource(pathToUse, this.classLoader));
	}
	@Override
	@Nullable
	public String getFilename() {
		return StringUtils.getFilename(this.path);
	}
	@Override
	public String getDescription() {
		StringBuilder builder = new StringBuilder("class path resource [");
		String pathToUse = this.path;
		if (this.clazz != null &amp;&amp; !pathToUse.startsWith("/")) {
			builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
			builder.append('/');
		}
		if (pathToUse.startsWith("/")) {
			pathToUse = pathToUse.substring(1);
		}
		builder.append(pathToUse);
		builder.append(']');
		return builder.toString();
	}
}
  1. First of all, there are four methods to construct ClassPathResource. I don't list an expired method here. For the other three, we usually call one parameter, that is, the path of the incoming file. It internally calls another overloaded method to assign a value to the classloader (because later we need to read the file through the classloader).
  2. During ClassPathResource initialization, the StringUtils.cleanPath Method to clean up the incoming path. The so-called path cleaning is to process the relative address in the path and the \ \ change to / under Windows system.
  3. The getPath method is used to return the file path, which is a relative path and does not contain a classpath.
  4. The resolveURL method represents the URL of the returned resource. It is preferred when returning Class.getResource Load before using ClassLoader.getResource Loading, about Class.getResource And ClassLoader.getResource I can write another article. I'll talk about it here, Class.getResource In the end, it will call ClassLoader.getResource , but Class.getResource The path is processed first.
  5. getInputStream reads the resource and returns the InputStream object.
  6. The createRelative method creates another relative resource based on the current resource.

This is ClassPathResource, another FileSystemResource that you may come into contact with. You can check its source code by yourself, which is simpler than ClassPathResource.

If we don't use Spring, we just want to load the resources under the resources directory ourselves. We can also use this method:

ClassPathResource resource = new ClassPathResource("beans.xml");
InputStream inputStream = resource.getInputStream();

After getting the IO stream, you can parse it by yourself.

In the Spring framework, after the Resource object is constructed, the Resource object will be converted to EncodedResource. Here, the Resource will be encoded. The encoding is mainly reflected in the getReader method. When obtaining the Reader object, if there is encoding, the encoding format will be given:

public Reader getReader() throws IOException {
	if (this.charset != null) {
		return new InputStreamReader(this.resource.getInputStream(), this.charset);
	}
	else if (this.encoding != null) {
		return new InputStreamReader(this.resource.getInputStream(), this.encoding);
	}
	else {
		return new InputStreamReader(this.resource.getInputStream());
	}
}

After all this is done, the next step is to load the Resource through XmlBeanDefinitionReader.

4. Summary

Well, today I'd like to share with you the problem of resource loading in Spring. This is the starting point for container startup. In the next article, we'll look at the parsing of XML files.

If you feel that you have gained something, please remember to watch and encourage brother song

Topics: Programming Spring xml encoding REST