Home >Java >javaTutorial >Introduction to the method of reading .properties and replacing source code with placeholders ${...}
This article mainly introduces the relevant knowledge of .properties file reading and placeholder ${...} replacement source code analysis, which has a good reference value. Let’s take a look at it with the editor
Preface
We often encounter a scenario in development. Some parameters in the Bean are relatively fixed. This kind of Configuration is usually used to configure these parameters in the .properties file, and then when the bean is instantiated, the parameters configured in the .properties file are read in by replacing the placeholder "${}" with Spring. And set it to the corresponding parameters of the Bean.
The most typical example of this approach is JDBC configuration. This article will study the source code for reading .properties files and replacing placeholders "${}". First, start with the code, define a DataSource, and simulate Let’s look at the four JDBC parameters:
public class DataSource { /** * 驱动类 */ private String driveClass; /** * jdbc地址 */ private String url; /** * 用户名 */ private String userName; /** * 密码 */ private String password; public String getDriveClass() { return driveClass; } public void setDriveClass(String driveClass) { this.driveClass = driveClass; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "DataSource [driveClass=" + driveClass + ", url=" + url + ", userName=" + userName + ", password=" + password + "]"; } }
Define a db.properties file:
driveClass=0 url=1 userName=2 password=3
Define a properties.xml file:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="properties/db.properties"></property> </bean> <bean id="dataSource" class="org.xrq.spring.action.properties.DataSource"> <property name="driveClass" value="${driveClass}" /> <property name="url" value="${url}" /> <property name="userName" value="${userName}" /> <property name="password" value="${password}" /> </bean> </beans>
Write a test code:
public class TestProperties { @Test public void testProperties() { ApplicationContext ac = new ClassPathXmlApplicationContext("spring/properties.xml"); DataSource dataSource = (DataSource)ac.getBean("dataSource"); System.out.println(dataSource); } }
The running results will not be posted. Obviously, let's analyze how Spring reads the properties in the properties file and replaces the "${}" placeholder.
PropertyPlaceholderConfigurer class analysis
In the properties.xml file we see a class PropertyPlaceholderConfigurer, as the name suggests it is a property placeholder configurator, take a look at this class InheritanceRelationship diagram:
##Seeing from this diagram, the most important point we can analyze is that PropertyPlaceholderConfigurer is the BeanFactoryPostProcessorinterface implementation class, imagine that Spring context must replace the placeholder "${}" all at once through the postProcessBeanFactory method after all Bean definitions are loaded and before the Bean is instantiated.
.properties file reading source code analysis
Let’s take a look at the postProcessBeanFactory method implementation:public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { try { Properties mergedProps = mergeProperties(); // Convert the merged properties, if necessary. convertProperties(mergedProps); // Let the subclass process the properties. processProperties(beanFactory, mergedProps); } catch (IOException ex) { throw new BeanInitializationException("Could not load properties", ex); } }Follow the mergeProperties method on line 3:
protected Properties mergeProperties() throws IOException { Properties result = new Properties(); if (this.localOverride) { // Load properties from file upfront, to let local properties override. loadProperties(result); } if (this.localProperties != null) { for (Properties localProp : this.localProperties) { CollectionUtils.mergePropertiesIntoMap(localProp, result); } } if (!this.localOverride) { // Load properties from file afterwards, to let those properties override. loadProperties(result); } return result; }The method in line 2 new creates a Properties named result. This result will be passed in with the subsequent code, and the data in the .properties file will be written into the result. OK, let’s see how the code enters line 17 and loads the .properties file through the file:
protected void loadProperties(Properties props) throws IOException { if (this.locations != null) { for (Resource location : this.locations) { if (logger.isInfoEnabled()) { logger.info("Loading properties file from " + location); } InputStream is = null; try { is = location.getInputStream(); String filename = null; try { filename = location.getFilename(); } catch (IllegalStateException ex) { // resource is not file-based. See SPR-7552. } if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) { this.propertiesPersister.loadFromXml(props, is); } else { if (this.fileEncoding != null) { this.propertiesPersister.load(props, new InputStreamReader(is, this.fileEncoding)); } else { this.propertiesPersister.load(props, is); } } } catch (IOException ex) { if (this.ignoreResourceNotFound) { if (logger.isWarnEnabled()) { logger.warn("Could not load properties from " + location + ": " + ex.getMessage()); } } else { throw ex; } } finally { if (is != null) { is.close(); } } } } }On line 9, the configuration of PropertyPlaceholderConfigurer can be passed in the path list (of course, only the path list is passed here) (a db.properties), line 3 traverses the list, line 9 obtains the binary data corresponding to .properties through an input byte stream InputStream, and then the code on line 23 parses the binary in the InputStream and writes it into the first parameter In Properties, Properties is the JDK's native tool for reading .properties files. In such a simple process, the data in .properties is parsed and written into the result (result is a new Properties in the mergeProperties method).
Placeholder "${...}" replaces source code analysis
We have seen the .properties file reading process above, and then you should replace "${} "Placeholder, let's go back to the postProcessBeanFactory method:public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { try { Properties mergedProps = mergeProperties(); // Convert the merged properties, if necessary. convertProperties(mergedProps); // Let the subclass process the properties. processProperties(beanFactory, mergedProps); } catch (IOException ex) { throw new BeanInitializationException("Could not load properties", ex); } }Line 3 merges the .properties files (it's called merging because multiple .properties files may have the same Key). Line 6 converts the merged Properties if necessary, but I don’t see any use. The placeholder "${...}" begins to be replaced on line 9. It is necessary to declare one point in advance:
The postProcessBeanFactory method of the BeanFactoryPostProcessor class is called after the Bean definition is parsed, so The current beanFactory parameters already have all the Bean definitions . Friends who are familiar with the Bean parsing process should know this very well. Follow the processProperties method on line 9:
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException { StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props); BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver); String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames(); for (String curName : beanNames) { // Check that we're not parsing our own bean definition, // to avoid failing on unresolvable placeholders in properties file locations. if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) { BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName); try { visitor.visitBeanDefinition(bd); } catch (Exception ex) { throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage()); } } } // New in Spring 2.5: resolve placeholders in alias target names and aliases as well. beanFactoryToProcess.resolveAliases(valueResolver); // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes. beanFactoryToProcess.addEmbeddedValueResolver(valueResolver); }Line 4 creates a new PlaceholderResolvingStringValueResolver and passes in Properties. As the name suggests, this is a
string value parser that holds the .properties file configuration. .
Line 5 BeanDefinitionVistor, pass in the above StringValueResolver, as the name suggests, this is a Bean definition access tool, holding a string value parser, imagineYou can access the Bean definition through BeanDefinitionVistor, when encountering When the string that needs to be parsed is used, the StringValueResolver passed in to the constructor is used to parse the string .
Line 7 gets the names of all Bean definitions through BeanFactory. Start traversing the names of all Bean definitions on line 8, pay attention to the first judgment on line 11"!(curName.equals(this.beanName)" , this.beanName refers to PropertyPlaceholderConfigurer, which means that PropertyPlaceholderConfigurer itself will not parse the placeholder "${...}" .
着重跟14行的代码,BeanDefinitionVistor的visitBeanDefinition方法,传入BeanDefinition:
public void visitBeanDefinition(BeanDefinition beanDefinition) { visitParentName(beanDefinition); visitBeanClassName(beanDefinition); visitFactoryBeanName(beanDefinition); visitFactoryMethodName(beanDefinition); visitScope(beanDefinition); visitPropertyValues(beanDefinition.getPropertyValues()); ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues(); visitIndexedArgumentValues(cas.getIndexedArgumentValues()); visitGenericArgumentValues(cas.getGenericArgumentValues()); }
看到这个方法轮番访问60e23eb984d18edbb092da6b8f295aba定义中的parent、class、factory-bean、factory-method、scope、property、constructor-arg属性,但凡遇到需要"${...}"就进行解析。我们这里解析的是property标签中的"${...}",因此跟一下第7行的代码:
protected void visitPropertyValues(MutablePropertyValues pvs) { PropertyValue[] pvArray = pvs.getPropertyValues(); for (PropertyValue pv : pvArray) { Object newVal = resolveValue(pv.getValue()); if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) { pvs.add(pv.getName(), newVal); } } }
获取属性数组进行遍历,第4行的代码对属性值进行解析获取新属性值,第5行判断新属性值与原属性值不等,第6行的代码用新属性值替换原属性值。因此跟一下第4行的resolveValue方法:
protected Object resolveValue(Object value) { if (value instanceof BeanDefinition) { visitBeanDefinition((BeanDefinition) value); } else if (value instanceof BeanDefinitionHolder) { visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition()); } else if (value instanceof RuntimeBeanReference) { RuntimeBeanReference ref = (RuntimeBeanReference) value; String newBeanName = resolveStringValue(ref.getBeanName()); if (!newBeanName.equals(ref.getBeanName())) { return new RuntimeBeanReference(newBeanName); } } else if (value instanceof RuntimeBeanNameReference) { RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value; String newBeanName = resolveStringValue(ref.getBeanName()); if (!newBeanName.equals(ref.getBeanName())) { return new RuntimeBeanNameReference(newBeanName); } } else if (value instanceof Object[]) { visitArray((Object[]) value); } else if (value instanceof List) { visitList((List) value); } else if (value instanceof Set) { visitSet((Set) value); } else if (value instanceof Map) { visitMap((Map) value); } else if (value instanceof TypedStringValue) { TypedStringValue typedStringValue = (TypedStringValue) value; String stringValue = typedStringValue.getValue(); if (stringValue != null) { String visitedString = resolveStringValue(stringValue); typedStringValue.setValue(visitedString); } } else if (value instanceof String) { return resolveStringValue((String) value); } return value; }
这里主要对value类型做一个判断,我们配置文件里面配置的是字符串,因此就看字符串相关代码,即34行的判断进去,其余的差不多,可以自己看一下源码是怎么做的。第35~第36行的代码就是获取属性值,第38行的代码resolveStringValue方法解析字符串:
protected String resolveStringValue(String strVal) { if (this.valueResolver == null) { throw new IllegalStateException("No StringValueResolver specified - pass a resolver " + "object into the constructor or override the 'resolveStringValue' method"); } String resolvedValue = this.valueResolver.resolveStringValue(strVal); // Return original String if not modified. return (strVal.equals(resolvedValue) ? strVal : resolvedValue); }
继续跟第6行的方法,valueResolver前面说过了,是传入的一个PlaceholderResolvingStringValueResolver,看一下resolveStringValue方法实现:
public String resolveStringValue(String strVal) throws BeansException { String value = this.helper.replacePlaceholders(strVal, this.resolver); return (value.equals(nullValue) ? null : value); }
第2行的replacePlaceholders方法顾名思义,替换占位符,它位于PropertyPlaceholderHelper类中,跟一下这个方法:
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { Assert.notNull(value, "Argument 'value' must not be null."); return parseStringValue(value, placeholderResolver, new HashSet<String>()); }
继续跟第3行的parseStringValue方法,即追踪到了替换占位符的核心代码中:
protected String parseStringValue( String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { StringBuilder buf = new StringBuilder(strVal); int startIndex = strVal.indexOf(this.placeholderPrefix); while (startIndex != -1) { int endIndex = findPlaceholderEndIndex(buf, startIndex); if (endIndex != -1) { String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex); if (!visitedPlaceholders.add(placeholder)) { throw new IllegalArgumentException( "Circular placeholder reference '" + placeholder + "' in property definitions"); } // Recursive invocation, parsing placeholders contained in the placeholder key. placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // Now obtain the value for the fully resolved key... String propVal = placeholderResolver.resolvePlaceholder(placeholder); if (propVal == null && this.valueSeparator != null) { int separatorIndex = placeholder.indexOf(this.valueSeparator); if (separatorIndex != -1) { String actualPlaceholder = placeholder.substring(0, separatorIndex); String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); if (propVal == null) { propVal = defaultValue; } } } if (propVal != null) { // Recursive invocation, parsing placeholders contained in the // previously resolved placeholder value. propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); if (logger.isTraceEnabled()) { logger.trace("Resolved placeholder '" + placeholder + "'"); } startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length()); } else if (this.ignoreUnresolvablePlaceholders) { // Proceed with unprocessed value. startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); } else { throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'"); } visitedPlaceholders.remove(placeholder); } else { startIndex = -1; } } return buf.toString(); }
过一下此流程:
获取占位符前缀"${"的位置索引startIndex
占位符前缀"${"存在,从"${"后面开始获取占位符后缀"}"的位置索引endIndex
如果占位符前缀位置索引startIndex与占位符后缀的位置索引endIndex都存在,截取中间的部分placeHolder
从Properties中获取placeHolder对应的值propVal
如果propVal不存在,尝试对placeHolder使用":"进行一次分割,如果分割出来有结果,那么前面一部分命名为actualPlaceholder,后面一部分命名为defaultValue,尝试从Properties中获取actualPlaceholder对应的value,如果存在则取此value,如果不存在则取defaultValue,最终赋值给propVal
返回propVal,就是替换之后的值
流程很长,通过这样一整个的流程,将占位符"${...}"中的内容替换为了我们需要的值。
【相关推荐】
1. Java免费视频教程
2. JAVA教程手册
3. 全面解析Java注解
The above is the detailed content of Introduction to the method of reading .properties and replacing source code with placeholders ${...}. For more information, please follow other related articles on the PHP Chinese website!