Remember to use it once in development@PropertySourceAnnotations to loadymlfile

Background: The service needs to create multiple configuration classes. The YML file has a simple hierarchical structure. Therefore, the YML configuration file is selected. However, in real development, I encountered problems with loading yML configuration files using @propertysource annotations.

Analysis process:

First let’s take a look at the @propertysource annotation:

Public @interface PropertySource {/** Name of the loaded resource */ String Name () default""; /** * The path to load the resource, use classpath, for example: *"classpath:/config/test.yml"* If there are multiple file paths in {}, use', 'Separated by signs, for example: * {"classpath:/config/test1.yml"."classpath:/config/test2.yml"} * In addition to using classpath, you can also use the address of the file, such as: *"file:/rest/application.properties"*/ String[] value(); /** This attribute indicates whether an error is reported if the file cannot be found based on the resource path. The default value is yesfalse */
    boolean ignoreResourceNotFound() default false; /** This is the encoding of the file to read. You are advised to use the encoding if Chinese characters are included in the configuration'utf-8' */
    String encoding() default ""; /** * key: this is the project class to read the resource file, default: *'PropertySourceFactory.class'
     */
    Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}

Copy the code

PropertySourceFactory interface = PropertySourceFactory interface = PropertySourceFactory interface

public interface PropertySourceFactory { PropertySource<? > createPropertySource(@Nullable String var1, EncodedResource var2) throws IOException; }Copy the code

Finding that there is only one method to create the attribute resource interface, we find the class that implements this method:

public class DefaultPropertySourceFactory implements PropertySourceFactory {
    public DefaultPropertySourceFactory() { } public PropertySource<? > createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {return name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource);
    }
}
Copy the code

In this class we found its returns an object ResourcePropertySource, find DefaultPropertySourceFactory class USES two ResourcePropertySource class constructor:

    public ResourcePropertySource(String name, EncodedResource resource) throws IOException {
        super(name, PropertiesLoaderUtils.loadProperties(resource));
        this.resourceName = getNameForResource(resource.getResource());
    }

    public ResourcePropertySource(EncodedResource resource) throws IOException {
        super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource));
        this.resourceName = null;
    }
Copy the code

In the code above, two construction methods have used PropertiesLoaderUtils loadProperties () this property, the method of the point continuously, will find that a piece of code:

static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)
			throws IOException {

		InputStream stream = null;
		Reader reader = null;
		try {
			String filename = resource.getResource().getFilename();
			// private static final String XML_FILE_EXTENSION = ".xml";
			if(filename ! = null && filename.endsWith(XML_FILE_EXTENSION)) { stream = resource.getInputStream(); persister.loadFromXml(props, stream); }else if (resource.requiresReader()) {
				reader = resource.getReader();
				persister.load(props, reader);
			}
			else {
				stream = resource.getInputStream();
				persister.load(props, stream);
			}
		}
		finally {
			if(stream ! = null) { stream.close(); }if(reader ! = null) { reader.close(); }}}Copy the code

The @propertysource annotation can also be used to load XML files. If you click on the persister.load(props, stream) method, you will find the following code:

private void load0 (LineReader lr) throws IOException {
        char[] convtBuf = new char[1024];
        int limit; int keyLen; int valueStart; char c; boolean hasSep; boolean precedingBackslash; /** ** reads one line at a time */while ((limit = lr.readLine()) >= 0) {
            c = 0;
            keyLen = 0;
            valueStart = limit;
            hasSep = false;

            //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
            precedingBackslash = false; /** * traverses each character of a line * if a character occurs'=',':',' ','\t','\f'Then out of the loop */while (keyLen < limit) {
                c = lr.lineBuf[keyLen];
                //need check ifEscaped. // If the current traversal character is'='':'Break out of the loopif ((c == '=' ||  c == ':') && !precedingBackslash) {
                    valueStart = keyLen + 1;
                    hasSep = true;
                    break; } // If the current traversal character is' ''\t''\f'Break out of the loop, // But keep iterating through the next loop until you find it'='':'
                else if ((c == ' ' || c == '\t' ||  c == '\f') && !precedingBackslash) {
                    valueStart = keyLen + 1;
                    break; } // Check for escapeif (c == '\ \') { precedingBackslash = ! precedingBackslash; }else {
                    precedingBackslash = false; } // for each loop, keyLen++ 1; } /** * Determine if valueStart(the starting subscript of the value) is less than the length of the read row, if so, the loop */while (valueStart < limit) { c = lr.lineBuf[valueStart]; // Determine if the current character is equal to a space, TAB, or page feed. Neither is equal to entering the cycleif(c ! =' '&& c ! ='\t'&& c ! ='\f') {// When hasSep isfalseThe hour stands for the lastwhile (keyLen < limit) loop breaks with c as a space or TAB or page feed // Continue loop here until found'='or':'// This is visible in the configuration file'='':'The number can be preceded by Spaces, tabs, and page feedsif(! hasSep && (c =='=' ||  c == ':')) {
                        hasSep = true;
                    } else {
                        break; }} // for each loop,valueStart + 1 valueStart++; } // Get key,value from config file and save String key = loadConvert(lr.linebuf, 0, keyLen, convtBuf); String value = loadConvert(lr.lineBuf, valueStart,limit- valueStart, convtBuf); put(key, value); }}Copy the code

The load0 method above reads one line at a time and then retrieves keys and values based on ‘=’ or ‘:’, whereas yML, with its distinct hierarchy, cannot read from this method.

From the above analysis,@PropertySourceThe key to reading properties files with annotations isPropertySourceFactoryThe interfacecreatePropertySourceMethod, so we want to implement@PropertySourceAnnotations to readymlThe file needs to be implementedcreatePropertySourceMethods,@PropertySourceNote that it is passedDefaultPropertySourceFactoryClass to implement this method, we just need to inherit this class and rewrite itcreatePropertySourceMethod, the implementation code is as follows:

@Override public PropertySource<? > createPropertySource(String name, EncodedResource resource) throws IOException {if (resource == null){
            returnsuper.createPropertySource(name, resource); } List<PropertySource<? >> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());return sources.get(0);
    }
Copy the code

Note: The loading class corresponding to YML and YAML in Spring Boot is YamlPropertySourceLoader.

test

@Component
@PropertySource(value = "test.yml", encoding = "utf-8", factory = TestFactory.class)
@ConfigurationProperties(prefix = "com.test") public class IdCardServerConfig { private String serverCode; . }Copy the code