How to load an external configuration with Spring Boot? - java

I'm currently learning how to work with Spring Boot. Until now I never used Frameworks like Spring and used files directly (FileInputStream, etc.)
So here is the case: I have some dynamic configuration values like OAuth tokens. I want to use them inside of my application but I have no clue how to realize this with Spring.
Here is some code to make clear what I'm searching for:
#Config("app.yaml")
public class Test {
#Value("app.token")
private String token;
private IClient client;
public Test(String token) {
this.client = ClientFactory.build(token).login();
}
}
Sure, this example is very plain. Here I want to get the value "token" dynamically from a YAML configuration file. This file must be accessible for the user and not included in the JAR file.
I also found that doc: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html but I have now idea how to apply this to my project.
How can I achive this? Thank you in advance :)
Edit:
Here are some parts of my code:
WatchdogBootstrap.java
package de.onkelmorph.watchdog;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
#SpringBootApplication
#ImportResource("classpath:Beans.xml")
public class WatchdogBootstrap {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(WatchdogBeans.class);
app.setBannerMode(Mode.OFF);
app.setWebEnvironment(false);
app.run(args);
}
}
Beans.xml (Located in default package)
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config></context:annotation-config>
</beans>
Watchdog.java
package de.onkelmorph.watchdog;
// Imports ...
#Component
#PropertySource("file:/watchdog.yml")
public class Watchdog {
// ...
// Configuration
#Value("${watchdog.token}")
private String token;
public Watchdog() {
System.out.println(this.token);
System.exit(0);
}
// ...
}
watchdog.yml (Located in src/main/resources)
watchdog:
token: fghaepoghaporghaerg

First of all your Test class should be annotated with #Component in order for it to be registered as a bean by spring (also make sure all your classes are under your main package - the main package is where a class that is annotated with #SpringBootApplication reside).
Now you should either move all your properties to application.yml (src/main/resources/application.yml), that is picked automatically by spring boot (note that it should be .yml instead of .yaml or register a custom PropertySourcesPlaceholderConfigurer.
Example for PropertySourcesPlaceholderConfigurer:
#Bean
public static PropertySourcesPlaceholderConfigurer PropertySourcesPlaceholderConfigurer() throws IOException {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
MutablePropertySources propertySources = new MutablePropertySources();
Resource resource = new DefaultResourceLoader().getResource("classpath:application.yml");
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
PropertySource<?> yamlProperties = sourceLoader.load("yamlProperties", resource, null);
propertySources.addFirst(yamlProperties);
configurer.setPropertySources(propertySources);
return configurer;
}
Now your properties should be loaded to spring's environment and they will be available for injection with #Value to your beans.

You basically got three easy options.
Use application.properties which is Springs internal configuration file.
Load your own configuration file using --spring.config.name as VM parameter.
You can use #PropertySource to load either an internal or external configuration. #PropertySource only works with .properties config files. There is currently an open Jira ticket to implement yaml support. You can follow the progress here: https://jira.spring.io/browse/SPR-13912
Notice, if you are using multiple yaml and/or properties files which contain common keys, the it will always use the definition of the key which was loaded last. This is why the below example uses two different keys. If it used the same key, then it would print out PROPERTIES FILE twice.
Short simple code snippet:
#Component
#PropertySource("file:/path/to/config/app.properties")
class Address{
#Value("${addr.street}")
private String street;
#Value("${addr.city}")
private String city;
}
app.properties
addr.street=Abbey Road
addr.city=London
Extensive Example
DemoApplication.java
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//Call class with properties
context.getBean(WatchdogProperties.class).test();
//Call class with yaml
context.getBean(WatchdogYaml.class).test();
}
//Define configuration file for yaml
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("watchdog.yml"));
propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
return propertySourcesPlaceholderConfigurer;
}
}
WatchdogProperties.java
#Component
//PropertySource only works for .properties files
#PropertySource("classpath:watchdog.properties")
public class WatchdogProperties{
//Notice the key name is not the same as the yaml key
#Value("${watchdog.prop.token}")
private String token;
public void test(){
System.out.println(token);
}
}
WatchdogYaml.java
#Component
class WatchdogYaml{
//Notice the key name is not the same as the properties key
#Value("${watchdog.token}")
private String token;
public void test(){
System.out.println(token);
}
}
Properties and Yaml files
Both of these files are located in src/main/resources
watchdog.yml:
watchdog:
token: YAML FILE
watchdog.properties:
watchdog.prop.token=PROPERTIES FILE
Output
PROPERTIES FILE
YAML FILE

Related

How to use #ConstructorBinding and #PropertySource together with #ConfigurationProperties with in Spring Boot 2.2.4?

I am new to Spring Boot. Currently, I am trying to create a POJO class (SystemProperties.class) to read the value in a properties file (parameter.properties separate from application.properties but still under the same directory /src/main/resources. The issue happens when I am using the #ConstructorBinding in the class in order for it to be immutable.
#ConstructorBinding needs to be used with #EnableConfigurationProperties or #ConfigurationPropertiesScan.
#ConfigurationPropertiesScan will ignore #Configuration annotation which is needed when using #PropertySource to specify external
*.properties file.
A) SystemProperties.class
#Configuration
#PropertySource("classpath:parameter.properties")
#ConstructorBinding
#ConfigurationProperties(prefix = "abc")
public class SystemProperties {
private final String test;
public SystemProperties (
String test) {
this.test = test;
}
public String getTest() {
return test;
}
B) parameter.properties
abc.test=text1
I have tried to remove the #PropertySource annotation but the value cannot be retrieved unless it is from the application.properties. Any help is greatly appreciated!
The way to solve this is to split the class into two classes with two different concerns. With such solution, you retain the SystemProperties class you created and additionally add another class simply for loading the properties file parameters to make them available to your application.
The solution would be as follows:
#ConstructorBinding
#ConfigurationProperties(prefix = "abc")
public class SystemProperties {
private final String test;
public SystemProperties(
String test) {
this.test = test;
}
public String getTest() {
return test;
}
}
Notice that I have omitted the #Configuration and #PropertySource annotations.
#Configuration
#PropertySource("classpath:parameter.properties")
public class PropertySourceLoader {
}
Notice I have simply added this annotations on a new class, solely created to load the properties file.
Finally, you can add #ConfigurationPropertiesScan on your main application class to enable the property mapping mechanism.

Externalize properties and logback Spring

I'm using Spring (without spring-boot). I want to build standalone application that can be run with default configuration (logback.xml and application.properties in resource folder) or with -Dconfig.folder=/path/to/custom/external/directory
(logback.xml and application.properties in /path/to/custom/external/directory). When application will be run with -Dconfig.folder param AppConfig should load both logback and properties from external directory.
Is there anyway to make external folder act like a resource folder?
If not, what is a common solution for this?
My current implementation (using default resource folder only):
App.java
public class App {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
SampleAction p = context.getBean(SampleAction.class);
p.performTask();
}
}
AppConfig.java
#ComponentScan
#PropertySource("classpath:application.properties")
class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
SampleAction.java
#Component
public class SampleAction {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Value("${sample.prop}")
private String sampleProp;
public void performTask(){
logger.debug(sampleProp);
}
}
logback.xml and application.properties are not relevant to the problem
Unlike the other answer suggests, if you use file prefix in #PropertySource, you're screwed because it won't be able to load the default application.properties from the jar. What you should do is the following:
#PropertySource("${config.folder:'classpath:'}/application.properties")
public class AppConfig
For logback.xml:
#Value("${config.folder}:")
private String configFolder;
InputStream = Optional.of(new ClassPathResource(configFolder + "/logback.xml"))
.filter(r -> r.exists())
.orElse(new ClassPathResource("classpath:/logback.xml"))
.getInputStream();
In both cases, I gave preference to the command line argument over the default packaged files. Of course, I didn't compile the above, so there may be typos or minor errors, but you get the idea.
Edit:
Since OP claims to not understand where to run the above code -
public class AppConfig {
#PostConstruct
void init() {
// init logback here
}
}
For log4j.xml
-Dlog4j.configuration=C:\neon\log4j.xml as VM argument
In main() method:
String filename = System.getProperty("log4j.configuration");
DOMConfigurator.configure(filename);
For external properties file:
-Dext.prop.dir=C:\neon as VM argument
Change in your AppConfig class will be like
#PropertySource("file:///${ext.prop.dir}/application.properties")
public class AppConfig{
}
Run App class with VM arguments as below and both file will be use from external location
-Dlog4j.configuration=C:\neon\log4j.xml -Dext.prop.dir=C:\neon

IOC containers: de-duplicating the configuration code

I am using spring framework for 2 different applications. Let's say both of the applications talk to one single MongoDB database. Following is how I configure MongoDB in both the applications:
#Configuration
#PropertySource("file:/etc/x/y/mongodb.properties")
public class MongoConfiguration {
#Autowired
private Environment env;
#Bean
public UserCredentials mongoCredentials() {
String mongoUserName = env.getProperty("mongodb.username");
String mongoPassword = env.getProperty("mongodb.password");
UserCredentials credentials = new UserCredentials(mongoUserName, mongoPassword);
return credentials;
}
#Bean
public MongoClient mongoClient() throws Exception {
String mongoUrl = env.getProperty("mongodb.url");
String mongoPort = env.getProperty("mongodb.port");
MongoClient mongo = new MongoClient(mongoUrl, Integer.valueOf(mongoPort));
return mongo;
}
#Bean(name="mongoTemplate")
public MongoTemplate mongoTemplate() throws Exception {
String mongoDatabaseName = env.getProperty("mongodb.databasename");
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient(), mongoDatabaseName, mongoCredentials());
return mongoTemplate;
}
Now, this piece of code is duplicated in two different application configurations. How do I avoid doing this configuration at two different places?
Treat it the same as a util class that you don't want to duplicate: move you config file to a separate project and make both your applications include that projects.
If you need to add additional project-specific configuration, Spring provides the #Import annotation that allows you to import configuration from separate classes, so you can create two project specific configuration classes that both import the generic configuration from the shared lib and supply their own individual beans and property sources, e.g.:
#Configuration
#PropertySource("classpath:/com/appspecific/app.properties")
#Import(com.genericlib.BaseConfig.class)
public class AppConfig {
#Inject BaseConfig baseConfig;
#Bean
public MyBean myBean() {
// reference the base config context
return new MyBean(baseConfig.getSomething());
}
}
Use Spring Boot, and optionally include the #PropertySource to add to the environment. It will collect all the MongoDB information and configure a client and template for you.

How to read properties file in Controller using annotations only? Spring MVC

How to read properties file in Controller using annotations only?
Properties file contains (env.properties):
document.portal.path=http://flana.gost.com/service
Spring Controller:
#Controller
#RequestMapping("/kap/*")
#SessionAttributes({"user", "KapForm", "activity"})
public class KapController {
#Value("${document.portal.path}")
private String URL;
}
Nothing else is done. In XML, we use to use placeholder, which i am not getting how to introduce in it. So I am getting exception.
Injection of autowired dependencies failed;
You can achieve it in two ways
Option 1
In config class put #PropertySource and define a bean for PropertySourcesPlaceholderConfigurer as below -
#Configuration
#PropertySource("classpath:someFile.properties")
public class SampleConfig {
// other configs...
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Option 2
In config class directly specify the bean for PropertyPlaceholderConfigurer and supply the name of property file as ClassPathResource
#Configuration
public class SampleConfig {
// other configs...
#Bean
public static PropertyPlaceholderConfigurer placeHolderConfigurer(){
PropertyPlaceholderConfigurer placeHolderConfigurer = new PropertyPlaceholderConfigurer();
ClassPathResource[] cpResources = new ClassPathResource[]
{ new ClassPathResource( "someFile.properties" ) };
placeHolderConfigurer.setLocations(cpResources);
placeHolderConfigurer.setIgnoreUnresolvablePlaceholders(true);
return placeHolderConfigurer;
}
}
Do note that the bean definition for place holder need to be static as per java docs (excerpts below)
Special consideration must be taken for #Bean methods that return Spring BeanFactoryPostProcessor (BFPP) types. Because BFPP objects must be instantiated very early in the container lifecycle, they can interfere with processing of annotations such as #Autowired, #Value, and #PostConstruct within #Configuration classes. To avoid these lifecycle issues, mark BFPP-returning #Bean methods as static.
Another way out found is
import org.springframework.context.MessageSource;
#Autowired
private MessageSource messageSource;
cutiePie = messageSource.getMessage("cutie.pie.property", new Object[] {},"cutie.pie.property", LocaleContextHolder.getLocale());

Spring Boot REST Controller doesn't work after setting attribute with custom xml bean

I am learning Spring framework (more generally Java EE).
I like the feature of passing the configuration using xml files. I started by the this example and it worked fine.
The only problem is that once I add my custom xml configuration with beans to set the attribute value inside the controller it doesn't work anymore, in the server log file it says Caused by: java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'com.example.controller.FirstController#0' bean method (...) then it lists all the methods in the controller exactly like if I defined multiple methods with identical RequestMapping (which is not the case).
I wanted to set a single attribute, but it seems that because of that the entire autoconfiguration doesn't work anymore.
Before
Main class
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
Controller class
#RestController
#RequestMapping("first")
public class FirstController {
protected final Logger log = LoggerFactory.getLogger(getClass());
#RequestMapping("test")
public String test() {
log.info("Test");
return "OK";
}
}
After
Main class
#Configuration
#ComponentScan
#EnableAutoConfiguration
#ImportResource("classpath:config.xml")
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
Controller class
#RestController
#RequestMapping("first")
public class FirstController {
protected final Logger log = LoggerFactory.getLogger(getClass());
private String testingbean;
public void setTestingbean(String testingbean) {
this.testingbean = testingbean;
}
#RequestMapping("test")
public String test() {
log.info("Test");
return "OK";
}
#RequestMapping("beantest")
public String testBeans() {
return testingbean;
}
}
Config.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- test bean -->
<bean class="com.example.controller.FirstController">
<property name="testingbean" value="works"/>
</bean>
</beans>
In the Before version after accessing /first/test it returned OK, now I get blank page and Ambiguous mapping found error in the log file.
Could someone explain to me how to mix Spring Boot autoconfiguration with custom defined beans?
I would also recommend to use property files to externalize configuration if possible.
EDIT: Spring boot provides fine documentation on that topic.
The problem can be the combination of XML configuration and default component scan - the same bean can be defined twice. If this is the case consider "manual" exclude via http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html#excludeFilters--

Categories

Resources