My google-fu is failing me on this one.
I'm using Spring 4.2.4.RELEASE with java configuration. What I'm trying to do is register a custom property editor to convert from a String to a Map.
So I have a Java Configuration class that registers the appropriate BeanFactoryPostProcessor
#Bean
public static CustomEditorConfigurer customEditorConfigurer(){
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
Map<Class<?>, Class<? extends PropertyEditor>> customEditors = new HashMap<>();
customEditors.put(Map.class, DelimitedStringToMapPropertyEditor.class);
configurer.setCustomEditors(customEditors);
return configurer;
}
In the same configuration class I am also injecting the Environment
#Resource
private Environment environment;
However, when I try to get the String property (which is also unfortunately named environment) that I want converted to a map, I get an exception.
environment.getProperty("environment", Map.class, Collections.EMPTY_MAP)
Exception:
Caused by: java.lang.IllegalArgumentException: Cannot convert value [VAR=hello] from source type [String] to target type [Map]
at org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(PropertySourcesPropertyResolver.java:94)
at org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(PropertySourcesPropertyResolver.java:65)
at org.springframework.core.env.AbstractPropertyResolver.getProperty(AbstractPropertyResolver.java:143)
at org.springframework.core.env.AbstractEnvironment.getProperty(AbstractEnvironment.java:546)
BUT if I inject the property directly using the #Value annotation, it works just fine.
#Value("${environment}")
private Map<String, String> shellEnvironment;
So, what gives? Does the Environment object not take into account registered property editors? Do I need to create a custom Converter? Isn't the Environment abstraction the latest and greatest way to resolve properties that come from anywhere?
No; the Environment abstraction does not use registered Property Editors. It only resolves properties (and does basic Type Conversion).
Thanks to the comments from #M. Deinum I was able to come to a decent compromise.
Correct it is a way to resolve properties it isn't a way to convert properties. When using the #Value 2 things happen, first the property is resolved, second conversion is attempted (only basic conversion String to numbers/booleans is done through plain java not converters/editors.). You only do the resolution part not the conversion part. – M. Deinum
You have to kind of read between the lines in the reference manual to come to this understanding.
The Environment Abstraction chapter states:
The role of the Environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.
Notice it does not say anything about conversion/editors. (Although I did notice that it does actually handle simple Type Conversion because Environment inherits PropertyResolver.getPropertyAsClass)
So I decided to combine the two methods and settled on this method of injecting my properties into my configuration class:
#Resource
private Environment environment;
#Value("#{environment['environment']?:{:}}")
private Map<String, String> shellEnvironment;
Related
We are loading properties from an external file using #PropertySources. Now I want to enable/disable #Aspect based on a property. I tried using #ConditionalOnExpression which didn't work. I tried the same by creating a bean of propertyplaceholderconfig. Even in the same case, it didn't work. Then I tried #profile which also didn't work initially.
What I Figured out is that these variables are not initialized at the starting when propertysource or propertyplaceholder bean is used at startup. Some variables are always ignored like (logging.file). But #Value works fine. In order to set these variables, I've to pass them as JVM parameters.
So my questions are:
1. How can I make spring to always read specified property files at startup and respect all of them?
2. Which is the best way to enable/disable #Aspect. Using #profile or #ConditionalOnExpression or something else?
Currently, we are setting logging.file in the main method since this also behaves the same way. But you guys know that it's not the proper way as I may end up adding the properties one by one like this. I want to put all the properties into external files such that spring reads those files and sets its properties.
Our properties structure:
common.properties #This has all common properties
service.properties #Property specific to a service. This will also contain existing property from common.properties which will be overridden.
I understand that I can use profiles. But, we want to keep the properties outside such you need to restart service if you are changing the properties. I also don't want to pass the variables as JVM parameters then I've to pass most of the variables in this way. Passing -Dspring.config.location is also difficult as common.properties and service.properties are used and 'service.properties' filename varies for each service.
sample codes:
Mainclass:
#PropertySources({
#PropertySource(value = "file:${property_path}/common.properties", ignoreResourceNotFound = false),
#PropertySource(value = "file:${property_path}/service1.properties", ignoreResourceNotFound = true) })
public class MainClass {
static String logDirectory = ApplicationContext.getGlobalProperty("logging.file");
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(MainClass.class);
Properties properties = new Properties();
properties.put("logging.file", logDirectory);
springApplication.setDefaultProperties(properties);
springApplication.run(args);
}
}
Application Context:
#Configuration
#EnableAutoConfiguration
public class ApplicationContext implements EnvironmentAware {
private static Environment environment;
#Override
public void setEnvironment(Environment environment) {
ApplicationContext.environment = environment;
}
public static String getGlobalProperty(String propertyName) {
return environment.getProperty(propertyName);
}
}
Here you can see any way I've used environment to get property. Is there any way to set the property using the environment such that while spring boot initialization itself the properties are populated?
We can also implement ApplicationContextInitializer and override initialize method to read properties. But how can I make it read 2 property files and override the duplicate property with the latest value? Reference(I'm not sure how to implement my requirements in this way.). Even in this case doesn't sound like you are trying to kill a mosquito with a hammer?
Current working Solution:
#Aspect
#Profile("!production")
#Configuration
public class ControllerAspect {
#pointcut(....)
} //Here also I've to pass spring.profiles.active as JVM params.
//setting the value in common.properties or service1.properties is not working.
I'm a newbie to spring boot so please let me know for additional clarifications.
It seems Spring by default loads some properties at initialization and unless until you specifically write logic to overwrite them (like the one I wrote in MainClass.java) there is no option to override those. Some of these include (logging.file, key used in #ConditionalonExpression).
Some tricks with their own challenges:
Specify the properties in application.properties in your classpath. The variables loaded at the earlier stages are always read from this file. challenge: I've tight coupled all my properties into the jar and in order to change the values I've to recompile and relaunch the Jar.
Use profiles and define application.properties as application-profile.properties. challenge: I've to create so many profiles and still the previous challenge exists.
Pass the property value as JVM parameter as -Dproperty.key=value. challenge:seriously? How many properties am I supposed to send as JVM parameter?
Implement ApplicationContextInitialize and override initialize method.challenge:Overriding Spring's default behaviour is not recommended as well as isn't it an overkill to use this just for reading property file?
Solution:
Use -Dspring.config.location to specify the property files. In this case, always spring reads the properties only from the specified location(s). You can provide multiple property files as well. Refer this for much more details. It seems if you give property locations as Directories spring loads them in reverse order. But if you specify files it follows the order specified.
Note: All these can be combined together. To know about precedence refer this.
We are using Spring Boot 2.
We want to use the refresh mechanism of Spring Boot, but due to a bug we can't use #Configuration, hence we are forced to replace all theses by #Value in combination with #RefreshScope.
So we used that:
#Configuration
#RefreshScope
public class MyConfig {
#Value("${myMap}")
private Map<String, String> myMap;
}
With that YAML file for example:
myMaps:
key1: Value
key2: Another Value
But we get an error out of it:
Error creating bean with name 'scopedTarget.myMap': Unsatisfied dependency expressed through field 'mapMap'; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'java.util.Map'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Map': no matching editors or conversion strategy found
We found already other thread with a similar topic:
How to fill HashMap from java property file with Spring #Value
How to inject a Map using the #Value Spring Annotation?
But we can't use other kind of property bindings, as we are deploying this app into kubernetes and use the config maps of kubernetes.
So we wonder, if there is any other chance to get #Value working together with Map
Instead of: #Value("${myMap}"), you need to write like this #Value("#{${myMap}}").
'#' treat whats in the curly bracket after it as Spring Expression Language(SpEL).
I don't think Kubernetes should prevent you from using #ConfigurationProperties (example here) with #RefreshScope. (Or is something else blocking you from using this?)
You could set up a configmap containing your properties file and mount that as a volume.
How you trigger a refresh is another question (if you need hot loading instead of changing your configmap together with your app). One option is to use spring-cloud-kubernetes which uses a similar approach and has multiple reload-triggering options. I'd expect you could use #ConfigurationProperties just like in the example for that project. Alternatively, it's also possible to watch for changes but that would require introducing more code.
I found something, that might solve my problem, but perhaps there is a better solution than this:
Based on org.springframework.cloud.logging.LoggingRebinder.setLogLevels(LoggingSystem, Environment)
private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable
.mapOf(String.class, String.class);
protected void setLogLevels(LoggingSystem system, Environment environment) {
Map<String, String> levels = Binder.get(environment)
.bind("logging.level", STRING_STRING_MAP).orElseGet(Collections::emptyMap);
for (Entry<String, String> entry : levels.entrySet()) {
setLogLevel(system, environment, entry.getKey(), entry.getValue().toString());
}
}
I'm trying to write a Spring FactoryBean that will produce a list of Request objects. The number of requests and the values that go into them are configurable at runtime, so I want to use properties for these.
Each request comprises of a pair of ID values, so I need some way of providing the Factory Bean with a configurable list of these ID pairs (Call them A and B for now).
What I've got so far is to use a property that looks something like:
requests=1/2,3/4,5/6
which then defines three requests, one with A=1 and B=2, one with A=3 and B=4, and one with A=5 and B=6.
This is obviously a bit nasty to configure, and rather prone to errors. What would be much nicer would be to do something with the values split out over many properties, so the above could be something like:
requests.1.A=1
requests.1.B=2
requests.2.A=3
requests.2.B=4
requests.3.A=5
requests.3.B=6
Which just makes it a bit more obvious what is going on.
However, I can't find any way of having my FactoryBean configured to access all of the available properties, instead of just the specifically named property that is passed in from the context.
Am I missing something here? Or - even better - is there a better way of doing this kind of config that is easier supported and maintained?
You can inject an Environment bean into your FactoryBean instance, it is provided by the context and you do not have to configure it. I am not sure how you are configuring your beans, but I always favor Java config. So this example will use Java config.
#Configuration
class FactoryBeanConfig {
#Bean
public FactoryBean(final Environment env) {
return new MyFactoryBean(env);
}
}
The Environment instance will give you access to all the properties, because it is a PropertyResolver You can programmatically loop over the properties
int x = 1;
while(true) {
env.getRequiredProperty("requests." + x + ".A")
env.getRequiredProperty("requests." + x + ".B")
}
If you want to use that to create instances of a specific bean I would suggest using a PropertiesBeanDefinitionReader. Specify 2 property files to load (one well known to configure the defaults) and one to add beans.
core-request.properties
request.(class)=com.company.pkg.Request
requests.properties
requests1.A=1
requests1.B=2
requests2.A=3
requests2.B=4
requests3.A=5
requests3.B=6
Now you can use your FactoryBean to construct a BeanFactory and obtain all the beans and expose them as a list.
public class YourFactoryBean implements FactoryBean<List<Request>> {
#Autowired
private ResourceLoader rl;
public Object getObject() {
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory;
PropertiesBeanDefinitionLoader loader = new PropertiesBeanDefinitionLoader(beanRegistry);
Resource core = rl.getResource(core-location);
Resource requests = rl.getResource(requests-location);
loader.loadBeanDefinitions(core);
loader.setDefaultParentBean("request");
loader.loadBeanDefinitions(requests);
return beanRegistry.getBeansOfType(Request.class).values();
}
}
Something like that.
I'm a little bit lost in Spring's Property Replacement mechanism. Lets say I have this Java Config
#Configuration
#ComponentScan(basePackageClasses = Application.class)
#PropertySources({
#PropertySource("classpath:/config/default.properties")
})
public class ApplicationConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
return pspc;
}
Now I want to add a Spring-Data Annotation #EnableMongoRepositories and define a custom basepackage for scanning, using a custom placeholder like follows
#EnableMongoRepositories("${my.custom.repobasepackage}"). The Placeholder is defined in my default.properties.
However, this property cannot be resolved here. When diving deeper into Spring's property replacement, I can see that it tries to resolve the property, so it is possible to do so.
However, the underlying Environment class which is used to replace the placeholder does not know about my PropertyPlaceholderConfigurer, but only know about my SystemProperties and my VM-Props. :-(
I can see that in org.springframework.context.annotation.ClassPathBeanDefinitionScanner#getOrCreateEnvironment.java#339 (I'm using Spring 4.0.1) my "PropertyPlaceholder" is already in place, so its not a question of ordering in initialization, but is not used, because the used BeanDefinitionRegistry does not implement the Interface EnvironmentCapable. Here, my understanding of the Spring App-Context Bootstrapping is at the end.
Can anybody help me out here? Is there a BeanDefinitionRegistry out there which is capable of providing the Environment Instance which uses my Property Placeholder?
Any help is highly appreciated!! I got cookies for you! :-))
Cheers, Stefan
I imagine that the #PropertySources (you only have one by the way so you don't need the wrapper) are added to the Environment after the config classes are processed, so it will be too late to resolve on one of the annotations of those classes themselves. You can verify this by setting my.custom.repobasepackage as a System property.
As an alternative I encourage you to try out Spring Boot (where application.properties is added to the Environment before any configuration is processed).
I'm trying to set up my Spring app such that a different .properties files is read depending on the configuration profile. I'm using java config and so what I'm trying to do is this:
#Autowired
private static Environment env;
#Bean
public static PropertySourcesPlaceholderConfigurer properties(){
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
String[] profiles = env.getActiveProfiles();
String filestring = "environment."+profiles[0]+".properties";
ClassPathResource properties = new ClassPathResource( filestring );
Resource[] resources = new ClassPathResource[] { properties };
pspc.setLocations( resources );
return pspc;
}
However the env.getActiveProfiles() is giving me a NullPointerException, which I assume means that the environment hasn't been injected. Any one got any ideas how I can fix this? Or alternatively if this is dumb/impossible how I could go about this better?
Just to give you an alternate perspective on your approach (clearly everyone's business case may differ from project to project,) but the type of configuration you are pursuing might lead to other headaches down the road. Security comes to mind. Usually multiple environments means you are dealing with usernames and passwords for various connections to databases and such. Storing those values for a production environment in line with your other configurations could expose sensitive data to developers who need have no knowledge of such things. Rather, if you switch using SPeL expressions and referencing the environment directly, then you can still achieve your runtime configuration but move your settings for each environment to the server (or what-have-you) where those specific configs apply. Example:
<bean id="myDatabase" class="mypackage.MyDatabase" p:username="#{environment['DB_USERNAME']}" p:password="#{environment['DB_PASSWORD']}" .../>
Then on your server, you can pass in system properties OR set environment variables with your desired username and password, and they will be configured at runtime. (The environment expression resolves directly to your Environment instance.)
Just a thought. =)
As #kungfuters rightly suggested, the business case may differ from application to application. Here is another alternative that worked for my application.
Provide an implementation of following interface:
ApplicationContextInitializer<ConfigurableApplicationContext>
Provide implementation of the following method. The logic to identify the profile goes in this method.
initialize(ConfigurableApplicationContext ctx)
Based on the identification, set the active profile:
this.applicationContext.getEnvironment().setActiveProfiles(<<yourProfileName>>)