Spring Cloud Config: define server config properties programmatically - java

I have self-configured Spring Cloud Config server (it uses itself for its own configuration).
So basically I have a single file bootstrap.properties with the content:
spring.cloud.config.server.bootstrap=true
spring.cloud.config.server.git.uri=<my GitHub repo>
spring.application.name=config
spring.profiles.active=development
It works well, but I want to define the properties above using Java code. In fact, I can keep those properties there, I just want to add spring.cloud.config.server.git.username and spring.cloud.config.server.git.password programmatically. Is it possible to do it somehow?
I tried using common approaches for adding/overriding of Spring properties defined in application.properties, but haven't succeeded: looks like bootstrap.properties should be declared programmatically in some other way (if it's even possible).

Well, after some digging into Spring sources, I've managed to find a solution. I don't like it because it looks like a dirty hack, but at least it works. I would appreciate if anyone proposes a cleaner solution.
resources/META-INF/spring.factories:
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
my.awesome.package.ConfigClientBootstrapConfiguration
ConfigClientBootstrapConfiguration:
#Configuration
public class ConfigClientBootstrapConfiguration {
#Autowired
private ConfigurableEnvironment environment;
#Autowired
private ConfigServerProperties server;
#Bean
public MultipleJGitEnvironmentRepository environmentRepository() {
Map<String, Object> properties = new HashMap<>();
properties.put("spring.cloud.config.server.bootstrap", "true");
properties.put("spring.cloud.config.server.git.uri", "<GitHub repo URI>");
properties.put("spring.cloud.config.server.git.username", "username");
properties.put("spring.cloud.config.server.git.password", "password");
properties.put("spring.application.name", "config");
properties.put("spring.profiles.active", "development");
MapPropertySource customPropertySource = new MapPropertySource("applicationConfig: [classpath:/bootstrap.properties]", properties);
environment.getPropertySources().replace("applicationConfig: [classpath:/bootstrap.properties]", customPropertySource);
MultipleJGitEnvironmentRepository repository = new MultipleJGitEnvironmentRepository(this.environment);
if (this.server.getDefaultLabel() != null) {
repository.setDefaultLabel(this.server.getDefaultLabel());
}
return repository;
}
}
P.S. It's also possible to add iteration over existing properties here to keep them as in the file (and possibly override).
P.P.S. I don't know why, but it worked only with the class name ConfigClientBootstrapConfiguration. When I renamed it, it stopped working. I don't care much about the naming here though...

Why would you prefer:
properties.put("spring.cloud.config.server.git.uri", "<GitHub repo URI>");
properties.put("spring.cloud.config.server.git.username", "username");
properties.put("spring.cloud.config.server.git.password", "password");
to add them to bootstrap.yml?
or set them via VM args (.... -Dspring.cloud.config.server.git.uri=xxxxxx ....) or via env variables (... SPRING_CLOUD_CONFIG_SERVER_GIT_URI=xxxxx .... java -jar application.jar?

Try below in your bootstrap.yml
username: ${SPRING_CLOUD_CONFIG_USERNAME:defaultuser}
password: ${SPRING_CLOUD_CONFIG_PASSWORD:defaultpass}
And add the values as environments variable for-
SPRING_CLOUD_CONFIG_USERNAME
SPRING_CLOUD_CONFIG_PASSWORD

Related

How to configure intellij to find spring properties source

I'm looking for a way to follow source of spring configuration from annotation.
E.g. Having below Bean is any way to e.g. click on my-components-service.books.configurations and be redirect or list yaml files which contains config which would be injected in runtime?
#Bean
#ConfigurationProperties(prefix = "my-components-service.books.configurations")
Map<ComponentType, BooksConfiguration> booksConfiguration() {
return new HashMap<>();
}
If #ConfigurationProperties is at the class level then there should be some gutter icons that will show where the properties have been set.
It doesn't look like this works when it's specified on a #Bean like in your example however. A possible workaround is to use a nested #Configuration class, though that may be undesirable.

Spring (not boot) load multiple yml files from multiple projects

So I have read dosens af articles on how to configure Spring boot to be aware of more yml files than application.yml and how to include these - even from subprojects. It is however hard to come by articles describing the same for "pure" Spring. I think however that i'm heading in the right direction I just can't get my configuration values back.
It's a straight forward multi-project gradle build with - for simplicity - two projects. One project is the "main" spring project - ie. Spring Context is initialized in this project. The other is a "support" module with some database entities and datasource configuration. We use annotation based configuration.
I would like to be able to define a set of configuration properties in the support module and based on whatever spring profile is active, the datasource configuration is loaded accordingly.
This SA post got me quite far following the different links in the different answers and composing my solution from this. The structure and code is as follows:
mainproject
src
main
groovy
Application.groovy
resourcers
application.yml
submodule
src
main
groovy
PropertiesConfiguration.groovy
DataSource.groovy
resources
datasource.yml
The PropertiesConfiguration.groovy adds the datasource.yml by using PropertySourcesPlaceholderConfigurer:
#Configuration
class PropertiesConfiguration {
#Bean
public PropertySourcesPlaceholderConfigurer configure() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer()
YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean()
yamlPropertiesFactoryBean.setResources(new ClassPathResource("datasource.yml"))
configurer.setProperties(yamlPropertiesFactoryBean.getObject())
return configurer
}
}
The Datasource.groovy should then read values based on the spring profile using (code reduced for readability):
#Autowired
Environment env
datasource.username = env.getProperty("datasource.username")
The env.getProperty returns null. No matter what spring profile is active. I can access the configuration value using the #Value annotation, however then the active profile is not respected and it return a value even if it is not defined for that profile. My yml looks (something) like this:
---
spring:
profiles: development
datasource:
username: sa
password:
databaseUrl: jdbc:h2:mem:tests
databaseDriver: org.h2.Driver
I can from Application.groovy inspect my ApplicationContext using a debugger and confirm that my PropertySourcesPlaceholderConfigurer exist and the values are loaded. Inspecting applicationContext.environment.propertySources it is NOT there.
What am I missing?
Using a PropertySourcesPlaceholderConfigurer does not add properties to Environment. Using something like #PropertySource("classpath:something.properties") on the class level of your configuration class will add properties to Environment, but sadly this does not work with yaml-files.
So, you would have to manually add the properties read from the yaml file to your Environment. Here is one way to do this:
#Bean
public PropertySourcesPlaceholderConfigurer config(final ConfigurableEnvironment confenv) {
final PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
final YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean();
yamlProperties.setResources(new ClassPathResource("datasource.yml"));
configurer.setProperties(yamlProperties.getObject());
confenv.getPropertySources().addFirst(new PropertiesPropertySource("datasource", yamlProperties.getObject()));
return configurer;
}
With this code, you can inject properties in either of these two fashions:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = PropertiesConfiguration.class)
public class ConfigTest {
#Autowired
private Environment environment;
#Value("${datasource.username}")
private String username;
#Test
public void props() {
System.out.println(environment.getProperty("datasource.username"));
System.out.println(username);
}
}
With the properties supplied in the question, this will print "sa" two times.
Edit: It doesn't seem that the PropertySourcesPlaceholderConfigurer is actually needed now, so the code can be simplified to the below and still produce the same output.
#Autowired
public void config(final ConfigurableEnvironment confenv) {
final YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean();
yamlProperties.setResources(new ClassPathResource("datasource.yml"));
confenv.getPropertySources().addFirst(new PropertiesPropertySource("datasource", yamlProperties.getObject()));
}
Edit 2:
I see now that you are looking to use the yaml-file with multiple documents in one file, and Spring boot-style selection by profile. It does not seem to be possible using regular Spring. So I think you have to split your yaml files into several, named "datasource-{profile}.yml". Then, this should work (perhaps with some more advanced checking for multiple profiles, etc)
#Autowired
public void config(final ConfigurableEnvironment confenv) {
final YamlPropertiesFactoryBean yamlProperties = new YamlPropertiesFactoryBean();
yamlProperties.setResources(new ClassPathResource("datasource-" + confenv.getActiveProfiles()[0] + ".yml"));
confenv.getPropertySources().addFirst(new PropertiesPropertySource("datasource", yamlProperties.getObject()));
}
Edit 3:
It could also be possible to use functionality from Spring boot without doing a full conversion of your project (I haven't actually tried it on a real project though). By adding a dependency to org.springframework.boot:spring-boot:1.5.9.RELEASE I was able to get it working with the single datasource.yml and multiple profiles, like this:
#Autowired
public void config (final ConfigurableEnvironment confenv) {
final YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader();
try {
final PropertySource<?> datasource =
yamlPropertySourceLoader.load("datasource",
new ClassPathResource("datasource.yml"),
confenv.getActiveProfiles()[0]);
confenv.getPropertySources().addFirst(datasource);
} catch (final IOException e) {
throw new RuntimeException("Failed to load datasource properties", e);
}
}

Dynamic Database property changes in Springboot

I have mysql database and i have configured database properties in application.properties file .
Now if i do change db connection properties , i want reflect that changes into my application on the fly , means with out restarting the server
Is this possible using with spring cloud config server and actuator?
I have tested this quite a bit and here are my findings.
Spring config server works pretty well for simple key value pairs.
It also works for Database properties provided that you are creating datasource objects yourself and you are using #RefreshScope.
For example, if you have a config server with these properties.
mongodb.feed.database=kiran
mongodb.feed.host=localhost
mongodb.feed.port=27017
And you are configuring MongoTemplate in your application like this.
#Configuration
#ConfigurationProperties(prefix = "mongodb.feed")
#EnableMongoRepositories(basePackages = "in.phani.springboot.repository", mongoTemplateRef = "feedMongoTemplate")
#Setter
class FeedMongoConfig {
private String host;
private int port;
private String database;
#Primary
#Bean(name = "feedMongoTemplate")
#RefreshScope // this is the key
public MongoTemplate feedMongoTemplate() throws Exception {
final Mongo mongoClient = createMongoClient(new ServerAddress(host, port));
return new MongoTemplate(mongoClient, database);
}
Mongo createMongoClient(ServerAddress serverAddress) {
return new MongoClient(serverAddress);
}
}
And if you change the database name in your config properties and then refresh the scope with /refresh endpoint. It works pretty well.
With springboot you need not do manual configuration like this. Spring boot has Autoconfiguration for most of the stuff. Continuing with the same example above, if you were to put in config properties something like this
spring.data.mongodb.uri=mongodb://localhost:27017/phani
spring-boot will configure MongoTemplate for you(you don't need to create yourself as in 2nd point).
Here comes the hiccup.
Now if you change the database name, and refresh the scope, it doesn't work. Because in this case, MongoTemplate was configured by spring-boot Autoconfiguration(MongoAutoConfiguration)
So in conclusion, it needs extensive testing to be done, before using it on production(especially for complex beans like datasources, MongoTemplates), since there is not enough documentation on this.. But I would say, it is worth trying.

How to load property file based on spring profiles

How to create project architecture to support multiple envionment. Each environment will have different datasource from different property file like(dev-propertfile,test-propertyFil,Production-propertyfile) with help of spring's
org.springframework.core.env.Environment;
I'll give step by step procedure for Spring boot applications.
Inside /src/main/resources/application.properties mention spring.profiles.active=dev (or Prod)
Create /src/main/resources/application-dev.properties and give your custom dev configurations here.
Create /src/main/resources/application-prod.properties and give your custom prod configurations here.
Run.
Put property file in same location as application.property and follow
the naming convention application-{profile}.properties like
application-dev.properties,application-test.properties,
application-prod.properties
And in application.properties set spring.profiles.active=dev,test etc
For Spring Boot applications it will work easily even by using a YAML File
spring:
profiles: dev
property: this is a dev env
---
spring:
profiles: prod
property: this is a production env
---
However, for a Spring MVC application, it needs more work. Have a look at this link
Basically, it involves 2 steps
Get the Spring Profile within servlet context
If you have set the profile on the server and want it to retrieve it within your application you can use System.getProperty or System.getenv methods.
Here is the code which fetches the profile and defaults it to a local profile, if no profile has been found.
private static final String SPRING_PROFILES_ACTIVE = "SPRING_PROFILES_ACTIVE";
String profile;
/**
* In local system getProperty() returns the profile correctly, however in docker getenv() return profile correctly
* */
protected void setSpringProfile(ServletContext servletContext) {
if(null!= System.getenv(SPRING_PROFILES_ACTIVE)){
profile=System.getenv(SPRING_PROFILES_ACTIVE);
}else if(null!= System.getProperty(SPRING_PROFILES_ACTIVE)){
profile=System.getProperty(SPRING_PROFILES_ACTIVE);
}else{
profile="local";
}
log.info("***** Profile configured is ****** "+ profile);
servletContext.setInitParameter("spring.profiles.active", profile);
}
To access the application-dev.properties, say now you will need to use
#Profile("dev") at the class level
The following code will fetch the application-dev.properties and common.properties
#Configuration
#Profile("dev")
public class DevPropertyReader {
#Bean
public static PropertyPlaceholderConfigurer properties() {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
Resource[] resources = new ClassPathResource[] { new ClassPathResource("properties/common.properties"), new ClassPathResource("properties/application-dev.properties") };
ppc.setLocations(resources);
ppc.setIgnoreUnresolvablePlaceholders(true);
return ppc;
}
}
For accessing say application-prod.properties you have to use #Profile("prod") at the class level. More details can be found here
Take a look at Spring Profile. You will define a set of profiles configurations, like Test, Dev, Production. And then, when you launch the application, you can define wich profile it should use.
Here are some tutorials of how to use.
And this guys had the same problem as yours: How to config #ComponentScan dynamic?
We wanted a way to load different properties from application-<your_env>.properties file depending on the environment (spring profile) in a Spring MVC project, so we implemented a configuration class something like this.
#Configuration
#PropertySource({ "classpath:application-${envTarget:dev}.properties" })
#Data
public class EnvironmentConfig {
#Value("${files.s3.accessId:}")
String s3AccessId;
#Value("${files.s3.accessToken:}")
String s3AccessToken;
.
.
.
}
Then we loaded the EnvironmentConfig in the class where we needed to use it.
While running the application, you just need to pass the -DenvTarget=<your_env>, and it will pick up the application-<your_env>.properties file from src/resources folder of the project.
In the above code, it will load values from application-dev.properties files when no envTarget is specified.
Thanks to Karthikeyan Muthurangam for suggesting this clever solution.

How do you handle configuration on spring framework when developing custom libraries intended to be consumed by other applications?

I'm working on building a custom "event" library that encapsulates the technical details of an event buffer we are planning to share with multiple consumers. Ideally, we want this library to use the spring framework (note: not spring boot), and be environmentally aware. Something I am not groking from the current docs is how to make the library environmentally aware.
For example, we want to include with the library a static configuration for the queue end points the library will publish / consume from; however, we want to enable "overriding" these queues when in the development or integration environments. Ideally, I do not want to make multiple builds that swap out what the config file is, but include them all and know to read the "right" one.
Some of the things I am not understanding;
How to pass in a "profile" when debugging (it seems the Environment object won't honor the -Dspring.active.profiles property).
How to structure the #Configuration classes so that you do not hard code #Profile(prod).
Total Spring n00b, thanks in advance!
---UPDATE: Trying to provide a more concrete example.
So I have create a basic configuration class to hold the details that would be populated by configuration files:
#Configuration
public class EventConfiguration implements EnvironmentAware{
private static Environment env = null;
#Value("${events.queue1}")
private String queue1;
#Value("${events.queue2}")
private String queue2;
#Bean
public EventDispatcher eventDispatcher() {
return new EventDispatcher(this);
}
#Override
public void setEnvironment(Environment environment) {
env = environment;
}
... getters and setters
Essentially I want to either go the yaml approach and define the queues by environment "dev", "integration", "prod"; or have 3 different files following the application-{env}.properties convention.
Then, to help me understand how this works, I threw together a quick test so I can inspect the configuration / environment:
#Test
public void testContext() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext();
Environment env = ctx.getEnvironment();
ctx.scan("com...events");
ctx.refresh();
EventDispatcher dispatcher = ctx.getBean(EventDispatcher.class);
}
I started the debugger with a -Dspring.profiles.active=dev, after having created an application-dev.profile available on the class path.
Am I on the right track? Seems weird to have to have that type of boiler plate code to instantiate the objects, plus it didnt work. The Environment object only showed "default" as the active profile.

Categories

Resources