Spring Boot : How to get program args with ConfigurationProperties - java

I'm trying to get some properties from the command line when I start my app. I really try a lot of solution but nothings is working. I'll only use #Value as a last solution
java -jar myjar.jar --test.property=something
Here's some classes
Main class
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(TestApplication.class);
builder.run(args);
}
}
Config class
#Configuration
#EnableConfigurationProperties(StringProperties.class)
public class StringConfiguration {
#Autowired
private StringProperties stringProperties;
#Bean(name = "customBeanName")
public List<String> properties() {
List<String> properties = new ArrayList<>();
properties.add(stringProperties.getString());
return properties;
}
}
Properties class
#Component
#ConfigurationProperties("test")
public class StringProperties {
private String property;
public String getString() {
return property;
}
}

Ops.....I have tested your code.It is setProperty that missed in StringProperties.
public void setProperty(String property) {
this.property = property;
}

Related

#ConfigurationProperties and #EnableConfigurationProperties cannot bind any properties in .yml or .properties

I want to new a project like mybatis-spring-boot-starter, I use springboot 2.3.2, but something wrong.
First of all, I define a BatisProperties.java like following:
#ConfigurationProperties(prefix = BatisProperties.MYBATIS_PREFIX)
public class BatisProperties {
public static final String MYBATIS_PREFIX = "batis";
private String MyClassName;
...
}
then, a BatisAutoConfiguration.java
#Configuration
#ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
#EnableConfigurationProperties(BatisProperties.class)
public class BatisAutoConfiguration implements InitializingBean {
private final BatisProperties properties;
#Override
public void afterPropertiesSet() throws Exception {
checkConfigFileExists();
}
private void checkConfigFileExists() {
System.out.println(this.properties.getMyClassName());//null here
if (this.properties.isCheckConfigLocation() && //code from mybatis-spring-booot-starter
StringUtils.hasText(this.properties.getConfigLocation())) {
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
Assert.state(resource.exists(),
"Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
}
}
And in /resource/META_INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xxx.BatisAutoConfiguration
above are all in a starter, I compile it to a jar file and use this jar file in another project(project TWO), in project TWO, I define .properties or .yml in /resource directory, contents are:
batis.my-class-name=xxxx.xxxx
Finally, a DemoApplication.java or Test.java like following:
DemoApplication.java
#SpringBootApplication
public class DemoApplication {
#Autowired
private BatisProperties properties;
private static BatisProperties batisProperties;
#PostConstruct
public void init() {
batisProperties = properties;
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
System.out.println(batisProperties.toString());//xxxx.BatisProperties#436390f4
System.out.println(batisProperties.getMyClassName());//null
}
}
Test.java
#SpringBootTest
class Test {
#Autowired
private BatisProperties properties;
#Test
void contextLoads() {
System.out.println(properties);// some bean in Spring IOC
System.out.println(properties.getDatasourceConfigProviderClassName());// null
}
}
Above comments are the results: We can find BatisProperties Bean in Spring IOC, but all properties are null.
So anybody can help? I don't know whether it is caused by the version of SpringBoot
I would improve the BatisProperties class to be as:
#Configuration
#ConfigurationProperties(prefix = "batis")
public class BatisProperties {
private String MyClassName;
...
// Make sure you have getters and setter for properties!
// If you use Lombok, put #Data on this class.
...
}
There is no need to explicitly have BatisAutoConfiguration.java. Just let Spring initialize it for you. Completely remove the class BatisAutoConfiguration.java. Also do not use static fields for the property class.
Try the following:
#SpringBootApplication
#EnableAutoConfiguration
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Or also more explicit, but not needed:
#SpringBootApplication
#ComponentScan(basePackages = {"my.app.org"}) // prefix, where BatisProperties is
#EnableAutoConfiguration
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
or:
#SpringBootApplication
#EnableAutoConfiguration
#Import(BatisProperties.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
The BatisProperties should be available anywhere in your App.

In Spring Boot which class would be initialize first #Component or #Configuration

I have a class annotated with #Component which is use to initialze application.yml config properties. Service classe is using configuration property. But sometime my Service class instance created before the Configuration class and I get null property value in service class, Its random not specific pattern.
Configuration Initializer class..
#Component
public class ConfigInitializer implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(ConfigInitializer.class);
#Autowired
ProxyConfig proxyConfig;
/*#PostConstruct
public void postConstruct(){
setProperties();
}
*/
#Override
public void afterPropertiesSet() {
setProperties();
}
private void setSystemProperties(){
log.debug("Setting properties...");
Properties props = new Properties();
props.put("PROXY_URL", proxyConfig.getProxyUrl());
props.put("PROXY_PORT", proxyConfig.getProxyPort());
System.getProperties().putAll(props);
}
}
#Component
#ConfigurationProperties(prefix = "proxy-config")
public static class ProxyConfig {
private String proxyUrl;
private String proxyPort;
public String getProxyUrl() {
return proxyUrl;
}
public void setProxyUrl(String proxyUrl) {
this.proxyUrl = proxyUrl;
}
public String getProxyPort() {
return proxyPort;
}
public void setProxyPort(String proxyPort) {
this.proxyPort = proxyPort;
}
}
Service Class..
#Service("receiverService")
public class ReceiverService {
private static final Logger logger = LoggerFactory.getLogger(ReceiverService.class);
private ExecutorService executorService = Executors.newSingleThreadExecutor();
#Autowired
public ReceiverService() {
initClient();
}
private void initClient() {
Future future = executorService.submit(new Callable(){
public Object call() throws Exception {
String value = System.getProperty("PROXY_URL"); **//Here I am getting null**
logger.info("Values : " + value);
}
});
System.out.println("future.get() = " + future.get());
}
}
Above Service class get null values String value = System.getProperty("PROXY_URL")
When I use #DependsOn annotation on Service class, it works fine.
In my little knowledge, I know Spring does not have specific order of bean creation.
I want to know If I use #Configuration instead of #Component on ConfigInitializer class like below, Will spring initialize ConfigInitializer
class before other beans ?.
#Configuration
public class ConfigInitializer implements InitializingBean {
//code here
}

Own configuration properties

Following chapter 5 of Spring in action fifth edition I have tried to establish my own confuguration variables with spring boot:
#Component
#ConfigurationProperties(prefix = "myprops")
public class MyClass {
private int myvar1;
// Getters and setters...
}
Written in file application.properties only this:
myprops.myvar1=3333
MyClass.getMyvar1() should return 3333 now but it still returns the default int value: 0.
#SpringBootApplication
public class Demo1Application {
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
#Bean
public CommandLineRunner foo(ApplicationContext ctx) {
return args -> {
MyClass mc = new MyClass();
int x = mc.getMyvar1();
System.out.println(x);
};
}
}
Add #EnableConfigurationProperties(MyClass.class) to Demo1Application
If we don’t use #Configuration in the POJO, then we need to add #EnableConfigurationProperties(ConfigProperties.class) in the main Spring application class to bind the properties into the POJO:

#PropertySource java.io.FileNotFoundException

I am testing Spring's #Conditional in which I load the bean depending on the value present in the .properties file. So I created a .properties file in src/main/resources/application-config.properties and I have the configuration class as:
#Configuration
#PropertySource(value = {"classpath:application-config.properties"}, ignoreResourceNotFound = false)
#ComponentScan(basePackages = {"com.app.test"})
public class ApplicationContextConfig {...}
I have 2 Condition implementations as below:
public class ConditionalBeanOne implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String name= conditionContext.getEnvironment().getProperty("condition.name");
return name.equalsIgnoreCase("condition_one");
}
}
public class ConditionalBeanTwo implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String name= conditionContext.getEnvironment().getProperty("condition.name");
return name.equalsIgnoreCase("condition_two");
}
}
I have respective POJO classes as:
#Component
#Conditional(value = ConditionalBeanOne.class)
public class BeanOne implements ServiceBean {}
#Component
#Conditional(value = ConditionalBeanTwo.class)
public class BeanTwo implements ServiceBean {}
When I run the application, I get following exception:
Caused by: java.io.FileNotFoundException: class path resource [application-config.properties] cannot be opened because it does not exist
I am running this through main method as following:
public class ConditionalMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApplicationContextConfig.class);
.....
}
}
I couldn't reproduce your problem so I created a complete working example based on your use case, which is also available on my GitHub. I noticed that your conditions are really the same, only the values are different, so you don't really need to duplicate the code there. Other than that, it's pretty much what you did.
I'd say that you're reinventing the wheel here. Spring Boot already has a ConditionalOnProperty which does this.
Application.java:
public class Application {
public static void main(String[] args) {
try (AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(ApplicationConfig.class)) {
ApplicationConfig.GreeterService greeterService =
applicationContext.getBean(ApplicationConfig.GreeterService.class);
String actual = greeterService.greeting();
System.out.printf("Greeting: %s.\n", actual);
}
}
}
ApplicationConfig.java:
#Configuration
// The / doesn't matter, but I prefer being explicit
#PropertySource("classpath:/application.properties")
#ComponentScan
public class ApplicationConfig {
#FunctionalInterface
public static interface GreeterService {
String greeting();
}
#Bean
#ConditionalOnProperty("hello")
public GreeterService helloService() {
return () -> "hello";
}
#Bean
#ConditionalOnProperty("hi")
public GreeterService hiService() {
return () -> "hi";
}
}
ConditionalOnProperty.java:
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Conditional(OnPropertyCondition.class)
public #interface ConditionalOnProperty {
String value() default "";
}
OnPropertyCondition.java:
public class OnPropertyCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Map<String, Object> attributes = annotatedTypeMetadata
.getAnnotationAttributes(ConditionalOnProperty.class.getName());
String value = (String) attributes.get("value");
String name = conditionContext.getEnvironment().getProperty("greeting");
return !isEmpty(name) && name.equalsIgnoreCase(value);
}
}
application.properties:
greeting=hello
Run normally:
Output:
Greeting: hello.
Run with -Dgreeting=hi:
Output:
Greeting: hi.

Spring PropertySourcesPlaceholderConfigurer does not use custom PropertySource for #Value

I have been trying to get a very basic example of a custom PropertySource running in a Spring Application.
This is my PropertySource:
public class RemotePropertySource extends PropertySource{
public RemotePropertySource(String name, Object source) {
super(name, source);
}
public RemotePropertySource(String name) {
super(name);
}
public Object getProperty(String s) {
return "foo"+s;
}
}
It gets added to the ApplicationContext via an ApplicationContextInitializer:
public class RemotePropertyApplicationContextInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
public void initialize(GenericApplicationContext ctx) {
RemotePropertySource remotePropertySource = new RemotePropertySource("remote");
ctx.getEnvironment().getPropertySources().addFirst(remotePropertySource);
System.out.println("Initializer registered PropertySource");
}
}
Now I created a simple Unit-Test to see if the PropertySource is used correctly:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = RemotePropertySourceTest.ContextConfig.class, initializers = RemotePropertyApplicationContextInitializer.class)
public class RemotePropertySourceTest {
#Autowired
private UnderTest underTest;
#Autowired
Environment env;
#Test
public void testContext() {
assertEquals(env.getProperty("bar"),"foobar");
assertEquals(underTest.getFoo(),"footest");
}
#Component
protected static class UnderTest {
private String foo;
#Autowired
public void setFoo(#Value("test")String value){
foo=value;
}
public String getFoo(){
return foo;
}
}
#Configuration
#ComponentScan(basePackages = {"test.property"})
protected static class ContextConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
return configurer;
}
}
}
Accessing the value via the Environment gives me the correct result ("foobar"), but using the #Value-Annotation fails. As far as I have read in the documentation the PropertySourcesPlaceholderConfigurer in my Configuration should automatically pick up my PropertySource from the environment but apparently it does not. Is there something I am missing?
I know that accessing properties explicitly via the environment is preferrable but the existing application uses #Value-Annotations a lot.
Any help is greatly appreciated. Thanks!
To get value from property source with #Value you have to use ${} syntax:
#Autowired
public void setFoo(#Value("${test}")String value){
foo=value;
}
Take a look at official documentation.

Categories