Is it possible to call a bean from bootstrap.properties?
I'm trying to implement a Cloud Config Client.
The Bean is similar to:
#Bean
public MyObject myObject(String environment) {
return new MyObject(environment);
}
....
public class MyObject {
private String environment;
// getters setters
}
In the bootstrap.properties file I have the following line:
spring.profiles.active= #Here I should get the value from the bean
Is it possible to write something like:
spring.profiles.active= ${myObject.environment}
Thank you very much.
You can, plugging into spring.factories:
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.demo.MyBootstrapConfiguration
And then write a normal Spring Java Configuration Object
#Configuration
public class MyBootstrapConfiguration {
// normal spring java config
}
Based on the docs, the bootstrap file:
Out of the box it is responsible for loading configuration properties
from the external sources, and also decrypting properties in the local
external configuration files
and the its content is meant to be referenced in the beans, not the other way round.
If you want to pass the active profile somehow to it, based on the docs you can:
1) Use -D option while starting the app:
java -jar -Dspring.profiles.active=production demo-0.0.1-SNAPSHOT.jar
2) Set SPRING_PROFILES_ACTIVE property on your OS environment.
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.
I want to add some property at runtime in spring config server and it should be available to all client applications with #Value annotation.
I wont have this property predefine because I am going calculate that value in spring config server and add to environment.
Can you please help me understand what is best way to achieve this.
Spring cloud configuration contains a feature named 'RefreshScope' which allows to refresh properties and beans of a running application.
If you read about spring cloud config, it looks like it can only load properties from a git repository, but that is not true.
You can use RefreshScope to reload properties from a local file without any need to connect to an external git repository or HTTP requests.
Create a file bootstrap.properties with this content:
# false: spring cloud config will not try to connect to a git repository
spring.cloud.config.enabled=false
# let the location point to the file with the reloadable properties
reloadable-properties.location=file:/config/defaults/reloadable.properties
Create a file reloadable.properties at the location you defined above.
You can leave it empty, or add some properties. In this file you can later, at runtime, change or add properties.
Add a dependency to
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
All beans, that are using properties, that may be changed during runtime, should be annotated with #RefreshScope like this:
#Bean
#RefreshScope
Controller controller() {
return new Controller();
}
Create a class
public class ReloadablePropertySourceLocator implements PropertySourceLocator
{
private final String location;
public ReloadablePropertySourceLocator(
#Value("${reloadable-properties.location}") String location) {
this.location = location;
}
/**
* must create a new instance of the property source on every call
*/
#Override
public PropertySource<?> locate(Environment environment) {
try {
return new ResourcePropertySource(location);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Configure Spring to bootstrap the configuration using that class.
Create (or extend) the META-INF/spring.factories file in your resource folder:
org.springframework.cloud.bootstrap.BootstrapConfiguration=your.package.ReloadablePropertySourceLocator
This bean will read the properties from the reloadable.properties. Spring Cloud Config will reload it from disk, when you refresh the application.
Add runtime, edit reloadable.properties as you like, then refresh the spring context.
You can do that by sending a POST request to the /refresh endpoint, or in Java by using ContextRefresher:
#Autowired
ContextRefresher contextRefresher;
...
contextRefresher.refresh();
This should also work, if you choose to use it in parallel to properties from a remote git repository.
I've got quite a simple application.yml file:
spring:
config:
name: android,ios,test,web
I expected to gain an ability to name config files like android.yml and put them into
classpath:/,classpath:/config/,file:./,file:./config/
as the DEFAULT_SEARCH_LOCATIONS constant from the ConfigFileApplicationListener class specifies. I created a file in the same directory with the main config:
android:
clientId: 0
clientSecret: clientSecret
Then I wrote a #Configuration class with one method to get an instance of ClientDetails by the #ConfigurationProperties:
#Configuration
public class TrustedClientInformationConfiguration {
#Bean(name = ANDROID)
#ConfigurationProperties(prefix = ANDROID)
public ClientDetails getAndroidClientDetails() {
return new BaseClientDetails();
}
}
Unfortunately, after autowiring, I got the instance with unfilled fields. What have I missed?
EDIT1: I found and debugged a method where CONFIG_NAME_PROPERTY = "spring.config.name" is used (it's only one usage), the containsProperty condition always returns false:
private Set<String> getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
null);
}
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
EDIT2: It is a try to create an oauth2 client configuration by moving properties to a separate file for each trusted client. They should be always instantiated despite the active profile.
The spring.config properties should be set on the command-line as they're needed before any config files are loaded.
However it looks like the feature you actually want is profiles. They're a much easier way to organise different configuration for different environments. Files can be named application-android, application-test, etc.
Documentation
I was wondering if there is any way to do this.
I want to hardcode a property (I know might not be the best), let's say I want to run my application always on port XXX or any other configuration without using a .properties.
Is there any way I can do this from the main? or a configuration bean?
Thanks.
Take a look at this for how to configure a port: Spring Boot - how to configure port
Relevant code is this:
#Controller
public class ServletConfig {
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return (container -> {
container.setPort(8012);
});
}
In general, most properties that can be configured via application.properties can also be configured through a Java bean. But, I would suggest using application.properties if you can. It allows you to change properties, without having to change source code.
EDIT:
Some other code from the posted link you might find useful:
HashMap<String, Object> props = new HashMap<>();
props.put("server.port", 9999);
new SpringApplicationBuilder()
.sources(SampleController.class)
.properties(props)
.run(args);
I have looked at the below threads and followed things given there. Still my property override is not happening
Spring Boot - Externalized properties
Profile Specific Property Enablement
Spring Boot External Config
I am on Tomcat 8.0.33 and Spring boot starter web and got this in my setenv.sh
export JAVA_OPTS="$JAVA_OPTS -Dlog.level=INFO -Dspring.config.location=file:/opt/jboss/apache-tomcat-8.0.33/overrides/ -Dspring.profiles.active=dev"
And in the overrides folder I got 2 files
1) application.properties
2) application-dev.properties
The application.properties has a single entry in it
spring.profiles.active=dev
I see that the proper log.level is fed to my code which means this command is working. Its just that I am clueless as to why my override is not happening as expected
I don't have any `PropertyPlaceholderConfigurer code in my workspace. I am not even sure if I need 1
I don't use this method to externalise properties. First, I'll try a suggestion for your method and then I'll show you what I'm using.
The suggestion for your method is to use file:/// instead of file:/ as with Spring I found that when not passing the three slashes after the colon it didn't recognise the property.
I've created a sample project for you, available here with instructions.
Now for the method I use.
I define a Configuration file for each profile and I keep the application.properties file under src/main/resources.
Then I use the #Profile and #PropertySource annotations on each configuration file.
For example:
#Configuration
#Profile("dev")
#PropertySource("file:///${user.home}/.devopsbuddy/application-dev.properties")
public class DevelopmentConfig {
#Bean
public EmailService emailService() {
return new MockEmailService();
}
#Bean
public ServletRegistrationBean h2ConsoleServletRegistration() {
ServletRegistrationBean bean = new ServletRegistrationBean(new WebServlet());
bean.addUrlMappings("/console/*");
return bean;
}
}
And
#Configuration
#Profile("prod")
#PropertySource("file:///${user.home}/.devopsbuddy/application-prod.properties")
public class ProductionConfig {
#Bean
public EmailService emailService() {
return new SmtpEmailService();
}
}
I have also got a Configuration file that is valid for all profiles, which I call ApplicationConfig, as follows:
#Configuration
#EnableJpaRepositories(basePackages = "com.devopsbuddy.backend.persistence.repositories")
#EntityScan(basePackages = "com.devopsbuddy.backend.persistence.domain.backend")
#EnableTransactionManagement
#PropertySource("file:///${user.home}/.devopsbuddy/application-common.properties")
public class ApplicationConfig {
}
My src/main/resources/application.properties file looks like the following:
spring.profiles.active=dev
default.to.address=me#example.com
token.expiration.length.minutes=120
Of course I could externalise the spring.profile.active property by passing it as a system property but for my case and for now it's fine.
When running the application, if I pass the "dev" profile, Spring will load all properties and Beans defined in the DevelopmentConfig class plus all those in ApplicationConfig. If I pass "prod", the ProductionConfig and ApplicationConfig properties will be loaded instead.
I'm completing a course on how to create a Spring Boot website with Security, Email, Data JPA, Amazon Web Services, Stripe and much more. If you want, you can register your interest here and you will get notified when the course is open for enrolment.