Contents: 0. Preface
- Treatment scheme
- A simple example
preface
Sometimes, you may need to put some configuration in the Spring environment, but it cannot be written to the configuration file, only put in at runtime. So, what to do at this point?
Apollo is engaged in configuration, so naturally encountered this problem, how did he deal with it?
Treatment scheme
First, what is a configured data structure in the Spring environment?
The abstract class PropertySource
contains a key value structure. This T can be of any type, depending on the design of the subclass.
Subclasses can get the configuration by overriding the getProperty abstract method.
Spring’s own org. Springframework. Core. The env. MapPropertySource will rewrite the method.
public class MapPropertySource extends EnumerablePropertySource<Map<String.Object>> {
public MapPropertySource(String name, Map<String, Object> source) {
super(name, source);
}
@Override
public Object getProperty(String name) {
return this.source.get(name);
}
@Override
public boolean containsProperty(String name) {
return this.source.containsKey(name);
}
@Override
public String[] getPropertyNames() {
return StringUtils.toStringArray(this.source.keySet()); }}Copy the code
As you can see, his generic type is Map, and the getProperty method is taken from Map.
Apollo uses this class directly.
Two different subclasses, different refresh logic. We don’t care about their differences for the moment.
Both of these classes are combined by RefreshableConfig and added to the Spring environment.
import org.springframework.core.env.ConfigurableEnvironment;
public abstract class RefreshableConfig {
@Autowired
private ConfigurableEnvironment environment; / / Spring environment
@PostConstruct
public void setup(a) {
// omit the code
for (RefreshablePropertySource propertySource : propertySources) {
propertySource.refresh();
// Note: After a successful refresh, place it in the Spring environment
environment.getPropertySources().addLast(propertySource);
}
// omit the code
Copy the code
When getting the configuration from the Spring environment, the code looks like this:
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
for(PropertySource<? > propertySource :this.propertySources) {
/ / note: this is called propertySource. The getProperty method, subclasses just rewrite method
Object value = propertySource.getProperty(key);
// omit extraneous code........
return convertValueIfNecessary(value, targetValueType);
}
return null;
}
Copy the code
Spring maintains a collection of PropertySource, and the combination is sequential, that is, the first in line has the highest priority (traversal starts at subscript 0).
The user can maintain a configuration dictionary (Map) within PropertySource, which is a data structure similar to a two-dimensional array.
Therefore, configurations can be duplicated, using the previous configuration in PropertySource. So, Spring leaves a few apis:
- addFirst(PropertySource
propertySource) - addLast(PropertySource
propertySource) - addBefore(String relativePropertySourceName, PropertySource
propertySource) - addAfter(String relativePropertySourceName, PropertySource
propertySource)
As the name suggests, with these apis we can insert the propertySource where we specify it. In this way, you can manually control the configured priority.
Spring has a CompositePropertySource class that internally aggregates a PropertySource Set that is iterated over when getProperty(String Name) is invoked, The getProperty(name) method of the propertySource is then called. It’s a 3 dimensional array.
The general design is like this:
There are multiple PS (PropertySource for short) in an environment, and each PS can either contain configuration directly or wrap another layer of PS.
A simple example
Here is a simple example. The requirement is: the application has a configuration, but it cannot be written to the configuration file. It can only be configured during the application startup process, and then injected into the Spring environment, so that Spring can use the configuration in the IOC.
The code is as follows:
@SpringBootApplication
public class DemoApplication {
@Value("${timeout:1}")
String timeout;
public static void main(String[] args) throws InterruptedException {
ApplicationContext c = SpringApplication.run(DemoApplication.class, args);
for(; ;) { Thread.sleep(1000); System.out.println(c.getBean(DemoApplication.class).timeout); }}}Copy the code
Application.properties configuration file
timeout=100
Copy the code
In the above code, we define a property timeout in the bean, write a value of 100 in the local configuration file, and give a default value of 1 in the expression.
So now the printed value is the value in the configuration file: 100.
However, this is not what we want, so we need to change the code.
Let’s add a class:
@Component
class Test implements EnvironmentAware.BeanFactoryPostProcessor {
@Override
public void setEnvironment(Environment environment) {
((ConfigurableEnvironment) environment).getPropertySources()
// Here is addFirst, which takes precedence over the application.properties configuration
.addFirst(new PropertySource<String>("timeoutConfig"."12345") {
/ / the key
@Override
public Object getProperty(String s) {
if (s.equals("timeout")) {//
return source;// Return source :12345 in the constructor
}
return null; }}); }@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
// NOP}}Copy the code
After running, the result is 12345
2018-07-02 15:26:54.315 INFO 43393 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2018-07-02 15:26:54.327 INFO 43393 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.991 seconds (JVM running for 1.49)
12345
12345
Copy the code
Why does adding this class replace properties in the configuration file? Explain what this class does.
All we need to do is insert a custom PS object into the Spring environment so that when the container gets it, it can get the configuration using the getProperty method.
So, to get the Spring environment object, we also need to create a PS object and rewrite the getProperty method, and note that our PS configuration needs to take precedence over the container configuration file, just to be safe.
The first argument to the PS constructor is just an identifier, and the second argument is source, and it can be any type, String, Map, whatever, so in this case, it’s just a String, and it returns that value, and if it’s a Map, it calls the Map get method.
Why implement the BeanFactoryPostProcessor interface? The purpose of implementing the BeanFactoryPostProcessor interface is to allow the Bean to load earlier than the target Bean’s initialization. Otherwise, the timeout properties in the target Bean are injected and the subsequent operations are meaningless.
conclusion
In plain English, don’t want to write configuration files!!
And I don’t want to change the code of the old project, which can still use the configuration center even if the configuration file is deleted!
This requires familiarity with Spring’s configuration loading logic and property fetching logic.
Now, we know that we just need to get the Spirng environment objects, add custom PS objects to the environment, and override the PS getProperty method to get the configuration (note the priority).
It is also important to note that the bean that loads this configuration has a high priority as well. Usually implementing the BeanFactoryPostProcessor interface is sufficient, but if not, you need to do something special.