I’d like to print the consolidated list of properties set in our application on startup. What is the best way to do this?
Thanks
This is my implementation:
public class CustomPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer implements InitializingBean{
public void afterPropertiesSet(){
try{
Properties loadedProperties = this.mergeProperties();
for(Entry<Object, Object> singleProperty : loadedProperties.entrySet()){
logger.info("LoadedProperty: "+singleProperty.getKey()+"="+singleProperty.getValue());
}
}catch(Exception ex){
ex.printStackTrace();
}
}
}
Use a custom PropertyPlaceholderConfigurer implementation that overrides the resolve... methods and logs the placeholder name. You may also need/want to override the convert... methods, but resolve... should handle it.
Here is a concrete example of printing all properties :
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.web.context.support.StandardServletEnvironment;
public class PropertiesLoaderConfigurer
extends PropertySourcesPlaceholderConfigurer {
private static final String ENVIRONMENT_PROPERTIES = "environmentProperties";
#Override
public void postProcessBeanFactory(
final ConfigurableListableBeanFactory beanFactory)
throws BeansException {
super.postProcessBeanFactory(beanFactory);
final StandardServletEnvironment propertySources =
(StandardServletEnvironment) super.getAppliedPropertySources().get(ENVIRONMENT_PROPERTIES).getSource();
propertySources.getPropertySources().forEach(propertySource -> {
if (propertySource.getSource() instanceof Map) {
// it will print systemProperties, systemEnvironment, application.properties and other overrides of
// application.properties
System.out.println("#######" + propertySource.getName() + "#######");
final Map<String, String> properties = mapValueAsString((Map<String, Object>) propertySource.getSource());
System.out.println(properties);
}
});
}
private Map<String, String> mapValueAsString(
final Map<String, Object> map) {
return map.entrySet().stream()
.collect(Collectors.toMap(entry -> entry.getKey(), entry -> toString(entry.getValue())));
}
private String toString(
final Object object) {
return Optional.ofNullable(object).map(value -> value.toString()).orElse(null);
}
}
Related
I am trying to use HashMap by putting some key-value pair (very very basic). But I am facing a weird issue. The key that I am putting there is not visible when I iterate over it rather it creates the key with the class name. Here is the code snippet:
#Configuration
public class AppConfig {
#Bean
public Map<String, Command> allCommands(Command command1, Command command2) {
Map<String, Command> map = new HashMap<>();
map.put("first", command1);
map.put("second", command2);
return map;
}
#Bean
public Map<String, RouterStrategy> allStrategies(KafkaRouter kafkaRouter) {
Map<String, RouterStrategy> map = new HashMap<>();
map.put("kafka", kafkaRouter);
return map;
}
}
The class which is using this bean:
#Service
public class Postman {
private Map<String, RouterStrategy> allStrategies;
#Autowired
public Postman(Map<String, RouterStrategy> allStrategies) {
this.allStrategies = allStrategies;
}
public void process(String strategy,Envelope envelope) throws Exception{
allStrategies.forEach((k,v) -> logger.info(k + " -> " + v));
RouterStrategy routerStrategy = allStrategies.get(strategy);
routerStrategy.routeMessage(envelope);
}
}
The method which calls this process() method is as below:
public class SomeCommand implements Command {
Postman postman;
public SomeCommand(Postman postman) {
this.postman = postman;
}
#Override
public void execute(Envelope envelope) throws Exception {
postman.process("kafka",envelope);
}
}
Now, the problem here is if I run the code, process() method of the Postman class should print the strategy map with "kafka" as one of the keys. But it prints below output-
applog.msg=kafkaRouter -> KafkaRouter{kafkaTemplate=org.springframework.kafka.core.KafkaTemplate#79708}
Pretty weird, whatever the key I am setting is simply ignored rather it creates this Map by using characters of the class name (KafkaRouter -> kafkaRouter). This thing is same if I add an additional key-value pair in the strategy map. For example, if I modify the strategy map like mentioned below-
#Bean
public Map<String, RouterStrategy> allStrategies(KafkaRouter kafkaRouter, RestRouter restRouter) {
Map<String, RouterStrategy> map = new HashMap<>();
map.put("kafka", kafkaRouter);
map.put("rest", restRouter);
return map;
}
Strategy interface-
public interface RouterStrategy {
void routeMessage(Envelope envelope) throws Exception;
}
Strategy implementation-
#Service
public class KafkaRouter implements RouterStrategy{
private KafkaTemplate<String, String> kafkaTemplate;
#Autowired
public KafkaRouter(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
#Override
public void routeMessage(Envelope envelope) throws Exception {
// Logic to get additional details like Kafka topic name, etc.
sendMessage(envelope.getContent());
}
#Override
public String toString() {
return "KafkaRouter{" +
"kafkaTemplate=" + kafkaTemplate + '}';
}
// Kafka publisher logic
private void sendMessage(String content) throws Exception {
}
}
In this case, two keys are printed kafkaRouter and restRouter. Since it can't "find the key", it throws NullPointerException. Anything I am missing? TIA
Other details:
Springboot version - 2.3.10.RELEASE
JDK - OpenJDK Runtime Environment Zulu11.40+15-NV (build 11.0.7.0.101+5-LTS)
I set environment variables
MY_APP_MY_MAP_A1=a
MY_APP_MY_MAP_A2=b
MY_APP_JUSTMAP_A1=a
MY_APP_JUSTMAP_A2=b
to configure my Spring Boot (2.1.7.RELEASE) application via #ConfigurationProperties:
#SpringBootApplication
#EnableConfigurationProperties(MyApp.MyProperties.class)
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
#Bean
public CommandLineRunner cmd(MyProperties props) {
return args -> {
System.out.println("myMap: " + props.getMyMap());
System.out.println("justmap: " + props.getJustmap());
};
}
#ConfigurationProperties(prefix = "my.app")
#Getter
#Setter
static class MyProperties {
private Map<String, String> myMap;
private Map<String, String> justmap;
}
}
Setting a Map<String,String> doesn't work when the variable name contains an upper letter (is in camelcase), otherwise everything works fine:
myMap: null
justmap: {a1=a, a2=b}
Is there a way how to do it?
If you have following env. variables passed
MY_APP_MYMAP_A1=a
MY_APP_MYMAP_A2=b
MY_APP_JUSTMAP_A1=a
MY_APP_JUSTMAP_A2=b
The below code prints what you are expecting
#SpringBootApplication
#EnableConfigurationProperties(TestSpringBootApplication.MyProperties.class)
public class TestSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(TestSpringBootApplication.class, args);
}
#Bean
public CommandLineRunner cmd(MyProperties props) {
return args -> {
System.out.println("myMap: " + props.getMyMap());
System.out.println("justmap: " + props.getJustmap());
};
}
#ConfigurationProperties(prefix = "my.app")
static class MyProperties {
private Map<String, String> myMap;
private Map<String, String> justmap;
public Map<String, String> getMyMap() {
return myMap;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public Map<String, String> getJustmap() {
return justmap;
}
public void setJustmap(Map<String, String> justmap) {
this.justmap = justmap;
}
}
}
The output is below
2019-09-04 16:00:07.336 INFO 21204 --- [ main] c.e.demo.TestSpringBootApplication : Started TestSpringBootApplication in 1.012 seconds (JVM running for 2.219)
myMap: {a1=a, a2=b}
justmap: {a1=a, a2=b}
For more details on the rules around this relaxed binding check the documentation here
Elaborating on the Shailendra's answer here's a relevant quote from docs:
To convert a property name in the canonical-form to an environment variable name you can follow these rules:
Replace dots (.) with underscores (_).
Remove any dashes (-).
Convert to uppercase.
I refered Spring Boot - inject map from application.yml for injecting map from application.yml file
My application.yml snippet is below
easy.app.pairMap:
test1: 'value1'
test2: 'value2'
Properties file is like below
#Component
#Configuration
#ConfigurationProperties("easy.app")
#EnableConfigurationProperties
public class TestProperties {
private Map<String, String> pairMap= new HashMap<String, String>();
public void setPairMap(Map<String, String> pairMap) {
this.pairMap= pairMap;
}
}
The above given code works .Map is not read from application.yml file when the 'pairMap' is set as static as below.
#Component
#Configuration
#ConfigurationProperties("easy.app")
#EnableConfigurationProperties
public class TestProperties {
private static Map<String, String> pairMap= new HashMap<String, String>();
public static void setPairMap(Map<String, String> pairMap) {
TestProperties .pairMap= pairMap;
}
}
PS : The issue is only when injecting map , but not on injecting string. Why is this behaviour?
ie the following injection of string in the following configuration works , but not the map injection
easy.app.key1: 'abc'
easy.app.pairMap:
test1: 'value1'
test2: 'value2'
Properties file like below
#Component
#Configuration
#ConfigurationProperties("easy.app")
#EnableConfigurationProperties
public class TestProperties {
private Map<String, String> pairMap= new HashMap<String, String>();
private static String key1;
public static void setPairMap(Map<String, String> pairMap) {
this.pairMap= pairMap;
}
public static void setKey1(String key1) {
TestProperties.key1= key1;
}
public String getKey1(){
return key1;
}
Fix with this:
easy:
app:
pairMap:
test1: value1
test2: value2
#CompileStatic
#Component
#EnableConfigurationProperties
class ConfigHolder {
#Value(value = '${easy.app.pairMap.test1}')
String test1Valse;
#Value(value = '${easy.app.pairMap.test2}')
String test2Valse;
}
#CompileStatic
#Configuration
#EnableConfigurationProperties
public class TestProperties {
#Autowired
ConfigHolder configHolder;
private Map<String, String> pairMap= new HashMap<String, String>();
public void setPairMap(Map<String, String> pairMap) {
if(pairMap != null && !pairMap.isNotEmpty()) {
this.pairMap = pairMap;
} else {
this.pairMap.put("test 1", ${configHolder.test1Valse});
this.pairMap.put("test 2", ${configHolder.test2Valse});
}
}
}
With reference with below links, i want my spring boot app to replace bean at runtime in applicationcontext.
Add Bean
Remove Bean
Below is my try,
MainClass.java
#SpringBootApplication
public class MainClass {
public static void main(String[] args) {
SpringApplication.run(
MainClass.class, args);
new Thread(new MyThread()).run();
}
}
ApplicationContextProvider.java
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext(){
return context;
}
#Override
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
context = arg0;
}
public Object getBean(String name){
return context.getBean(name, Object.class);
}
public void addBean(String beanName, Object beanObject){
ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
beanFactory.registerSingleton(beanName, beanObject);
}
public void removeBean(String beanName){
BeanDefinitionRegistry reg = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
reg.removeBeanDefinition(beanName);
}
}
Config.java
#Configuration
#ComponentScan(value="com.en.*")
public class Config {
#Bean
#Qualifier("myMap")
public MapBean myMap(){
MapBean bean = new MapBean();
Map<String, String> mp = new HashMap<>();
mp.put("a", "a");
bean.setMp(mp);
return bean;
}
#Bean
ApplicationContextProvider applicationContextProvider(){
return new ApplicationContextProvider();
}
}
MapBean.java
import java.util.Map;
public class MapBean {
private Map<String, String> mp;
public Map<String, String> getMp() {
return mp;
}
public void setMp(Map<String, String> mp) {
this.mp = mp;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("MapBean [mp=");
builder.append(mp);
builder.append("]");
return builder.toString();
}
}
MyThread.java
import java.util.HashMap;
import java.util.Map;
import com.en.model.MapBean;
public class MyThread implements Runnable{
static ApplicationContextProvider appCtxPrvdr = new ApplicationContextProvider();
public void run(){
try {
Thread.sleep(5000);
if(ApplicationContextProvider.getApplicationContext().containsBean("myMap")){
System.out.println("AppCtx has myMap");
MapBean newM = (MapBean) ApplicationContextProvider.getApplicationContext().getBean("myMap", MapBean.class);
System.out.println(newM);
appCtxPrvdr.removeBean("myMap");
System.out.println("Removed myMap from AppCtx");
}
MapBean bean1 = new MapBean();
Map<String, String> mp = new HashMap<>();
mp.put("b", "b");
bean1.setMp(mp);
appCtxPrvdr.addBean("myMap", bean1);
System.out.println("myMap added to AppCtx");
if(ApplicationContextProvider.getApplicationContext().containsBean("myMap")){
System.out.println("AppCtx has myMap");
MapBean newM = (MapBean) ApplicationContextProvider.getApplicationContext().getBean("myMap", MapBean.class);
System.out.println(newM);
appCtxPrvdr.removeBean("myMap");
System.out.println("Removed myMap from AppCtx");
}
MapBean bean2 = new MapBean();
Map<String, String> map2 = new HashMap<>();
map2.put("c", "c");
bean2.setMp(map2);
appCtxPrvdr.addBean("myMap", bean2);
System.out.println("myMap added to AppCtx");
MapBean newM = (MapBean) ApplicationContextProvider.getApplicationContext().getBean("myMap", MapBean.class);
System.out.println(newM);
} catch (Exception e) {
e.printStackTrace();
}
}
}
The output i am getting is as below,
AppCtx has myMap
MapBean [mp={a=a}]
Removed myMap from AppCtx
myMap added to AppCtx
AppCtx has myMap
MapBean [mp={b=b}]
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myMap' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.removeBeanDefinition(DefaultListableBeanFactory.java:881)
at com.en.config.ApplicationContextProvider.removeBean(ApplicationContextProvider.java:47)
at com.en.config.MyThread.run(MyThread.java:36)
at java.lang.Thread.run(Unknown Source)
at com.en.MainClass.main(MainClass.java:77)
So as per my understanding below things are happening.
In Config class, it is adding myMap to appctx.
In Mythread class, it is able to find myMap in appctx.
It is able to print and then remove from appctx.
It is able to add new myMap to appctx.
When above step is done. It is not able to remove it again.
Please advice on how to add and remove it multiple time.
BeanDefinitions and beans are totally different things in spring. When BeanDefinition is removed, the bean still exists in ApplicationContext.
Hence I can't really understand the implementation of ApplicationContextProvider in your example.
Now the thing you're asking for is very unusual, it could be great if you could provide more information on why do you need such a logic in runtime.
I personally don't think you should remove the beans when the application starts.
It's possible or at least kind of "conventional" to do the following:
Conditionally load the bean when the application context Starts with the help of #Conditional annotation (there are many of those) / #Profile annotation
Alter the bean during the runtime to give it additional functionality, for this use BeanPostProcessor
Alter Bean definition by means of defining BeanFactoryPostProcessor (used in extremely rare cases)
Now, if you're aware of all these mechanisms and none of them suits your needs, try the following:
Define an internal state in the singleton bean and check the state every time the bean's method is called.
This can be implemented right inside the bean, with wrapper / decorator or in any other way, but the logic is the same.
Example:
public class MySingleton {
private boolean shouldWork = true;
public void stop() {
shouldWork = false;
}
public void start() {
shouldWork = true;
}
public void doSomething() {
if(shouldWork) {
// do real logic
}
else {
// do nothing, or some minimal thing to not break anything
}
}
}
Well your logic is pretty wired and if you are really trying to do something like refresh the bean with different configurations at runtime or sort of a thing,
Please consider looking at externalized configurations and refresh configs on the fly
But if still you are not happy with this and you need to stick with what you have done above, I guess the problem is with your method :
public void addBean(String beanName, Object beanObject){
ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
beanFactory.registerSingleton(beanName, beanObject);
}
Because since it does not register a bean definition, spring context will not know that its really there.
Would suggest to try adding :
BeanDefinitionRegistry reg = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
reg.registerBeanDefinition(beanName,beanDefinition);
so basically your addBean method should change as follows,
public void addBean(String beanName, Object beanObject){
ConfigurableListableBeanFactory beanFactory = ((ConfigurableApplicationContext)context).getBeanFactory();
beanFactory.registerSingleton(beanName, beanObject);
BeanDefinition beanDefinition = beanFactory.getBeanDefinition( beanName );
BeanDefinitionRegistry reg = (BeanDefinitionRegistry) context.getAutowireCapableBeanFactory();
reg.registerBeanDefinition(beanName,beanDefinition);
}
I need to read all properties in application.properties file in a map
In the code below the property test has the respective value but the map is empty. How can I fill "map" with the values in the application.properties file without adding a prefix to the properties.
This is my application.properties file
AAPL=25
GDDY=65
test=22
I'm using #ConfigurationProperties like this
#Configuration
#ConfigurationProperties("")
#PropertySource("classpath:application.properties")
public class InitialConfiguration {
private HashMap<String, BigInteger> map = new HashMap<>();
private String test;
public HashMap<String, BigInteger> getMap() {
return map;
}
public void setMap(HashMap<String, BigInteger> map) {
this.map = map;
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
This can be achieved using the PropertiesLoaderUtils and #PostConstruct
Please check the sample below:
#Configuration
public class HelloConfiguration {
private Map<String, String> valueMap = new HashMap<>();
#PostConstruct
public void doInit() throws IOException {
Properties properties = PropertiesLoaderUtils.loadAllProperties("application.properties");
properties.keySet().forEach(key -> {
valueMap.put((String) key, properties.getProperty((String) key));
});
System.err.println("valueMap -> "+valueMap);
}
public Map<String, String> getValueMap() {
return valueMap;
}
public void setValueMap(Map<String, String> valueMap) {
this.valueMap = valueMap;
}
}
You can't do it with #ConfigurationProperties as far as I'm aware, those require a prefix to be able to load those properties within the bean.
However, if your goal is to obtain "value Y" for "property X" programmatically, you can always inject Environment and use the getProperty() method to find certain property, for example:
#Configuration
public class InitialConfiguration {
#Autowired
private Environment environment;
#PostConstruct
public void test() {
Integer aapl = environment.getProperty("AAPL", Integer.class); // 25
Integer gddy = environment.getProperty("GDDY", Integer.class); // 65
Integer test = environment.getProperty("test", Integer.class); // 22
}
}
In spring boot, if you need to get a single value from the application.proprties, you just need to use the #Value annotation with the given name
So to get AAPL value just add a class level property like this
#Value("${AAPL}")
private String aapl;
And if you need to load a full properties file as a map, I'm using the ResourceLoader to load the full file as a stream and then parse it as follows
#Autowired
public loadResources(ResourceLoader resourceLoader) throws Exception {
Resource resource = resourceLoader.getResource("classpath:myProperties.properties"));
BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()));
String line;
int pos = 0;
Map<String, String> map = new HashMap<>();
while ((line = br.readLine()) != null) {
pos = line.indexOf("=");
map.put(line.substring(0, pos), line.substring( pos + 1));
}
}
Indeed you can use #ConfigurationProperties without prefix to get entire properties known to Spring application i.e. application, system and environment properties etc.
Following example creates a fully populated map as a Spring bean. Then wire / inject this bean wherever you need it.
#Configuration
class YetAnotherConfiguration {
#ConfigurationProperties /* or #ConfigurationProperties("") */
#Bean
Map<String, String> allProperties() {
return new LinkedHashMap<>();
}
}
#Autowire
void test(Map<String, String> allProperties) {
System.out.println(allProperties.get("AAPL")); // 25
...
}
#PropertySource("classpath:config.properties")
public class GlobalConfig {
public static String AAPL;
#Value("${AAPL}")
private void setDatabaseUrl(String value) {
AAPL = value;
}
}
You have to use #Value to get value from application.properties file