I developed a small library that adds a custom endpoint for the actuator and I like to expose it by default. Spring Boot 2.7.4 only exposes by default health.
At the moment, what I am doing is registering an EnvironmentPostProcessor to add a property to include health,jwks at the last PropertySource in the environment. But it seems a little bit fragile. There are other libraries that have to export other endpoints by default (metrics, prometheus...)
This is what I am doing at the moment:
public class PoCEnvironmentPostProcessor implements EnvironmentPostProcessor {
private static final String PROPERTY_NAME = "management.endpoints.web.exposure.include";
#Override
public void postProcessEnvironment(
ConfigurableEnvironment environment,
SpringApplication application
) {
var propertySources = environment.getPropertySources();
propertySources.stream()
.filter(it -> it.containsProperty(PROPERTY_NAME))
.findFirst().ifPresentOrElse(source -> {
var property = source.getProperty(PROPERTY_NAME);
var pocSource = new MapPropertySource(PROPERTY_NAME, Map.of(PROPERTY_NAME, property + ",jwks"));
// Add the new property with more priority
propertySources.addBefore(source.getName(), pocSource);
}, () -> {
var pocSource = new MapPropertySource(PROPERTY_NAME, Map.of(PROPERTY_NAME, "health,jwks"));
propertySources.addLast(pocSource);
});
}
}
Is there any way to expose by default that allow me to add several endpoints in different libraries without playing to much with the property sources?
It’s not exactly clear to me if you’re asking how the client apps that use your library would enable specific endpoints, or if you are writing more than one library and want to expose different endpoints. I’ll answer both.
management.endpoints.web.exposure.include=comma-separated-endpoints would enable the listed endpoints without your library having to do anything. Your client apps can set this property in application.yml.
If you want to set this property by default in your library, one of the easiest ways is to put it in a property file, and load it as a #PropertySource on a #Configuration bean. I’m assuming your library is a starter and the #Configuration bean is auto-configured. If you don’t know how to create a starter, refer to this article.
Related
I started using Azure App Configuration service and Feature Flags functionality in my project. I followed this documentation and was able to create a Spring boot project. I also created a FeatureFlagService class and autowired the FeatureManager class in it as shown below :
#Service
public class FeatureFlagService {
private FeatureManager featureManager;
public FeatureFlagService(FeatureManager featureManager) {
this.featureManager = featureManager;
}
public boolean isFeatureEnabled() {
return featureManager.isEnabledAsync("scopeid/MyFeature").block();
}
}
With this I get the value of the feature flag 'MyFeature' but with no label.
I have the same feature defined with different labels in Azure App Configuration as shown below.
I need to fetch the feature flag with specific label. How can I achieve it at runtime?
I don't see a way to do it using the FeatureManager class.
They only way to load from a label is by using spring.cloud.azure.appconfiguration.stores[0].feature-flags.label-filter, the Feature Management Library itself has no concept of a label.
By default Spring Boot will automatically load properties from classpath:/application.properties
I want to know where is this auto configuration source code.
I want to exclude from my app.
IE: #EnableAutoConfiguration(exclude=XXXXAutoconfiguration.class)
The reason is:
Because I cannot override the default application.properties by an external property using #PropertySource
#SpringBootApplication
#ComponentScan(basePackages = {"com.test.green.ws"})
#PropertySource(value = {"classpath:/application.properties", "file:/opt/green-ws/application.properties"})
public class GreenWSApplication {
public static void main(String[] args) {
SpringApplication.run(GreenWSApplication.class, args);
}
}
There are many ways to override property keys without disabling the whole externalized configuration feature; and that's actually the goal.
You can see here the order the properties are considered in. For example, you can add that external properties file in a config folder right next to the packaged JAR, or even configure the file location yourself.
Now if you really want to disable all of that (and the Boot team strongly suggests not to do that), you can register your own EnvironmentPostProcessor (see here) and remove PropertySources from MutablePropertySources, which you can fetch with configurableEnvironment. getPropertySources().
There's no easier way to do that because:
this comes really early in the application init phase, before auto-configurations
this is not something you should do, as it will have many side effects
I am using spring boot, and I have two external properties files, so that I can easily change its value.
But I hope spring app will reload the changed value when it is updated, just like reading from files. Since property file is easy enough to meet my need, I hope I don' nessarily need a db or file.
I use two different ways to load property value, code sample will like:
#RestController
public class Prop1Controller{
#Value("${prop1}")
private String prop1;
#RequestMapping(value="/prop1",method = RequestMethod.GET)
public String getProp() {
return prop1;
}
}
#RestController
public class Prop2Controller{
#Autowired
private Environment env;
#RequestMapping(value="/prop2/{sysId}",method = RequestMethod.GET)
public String prop2(#PathVariable String sysId) {
return env.getProperty("prop2."+sysId);
}
}
I will boot my application with
-Dspring.config.location=conf/my.properties
I'm afraid you will need to restart Spring context.
I think the only way to achieve your need is to enable spring-cloud. There is a refresh endpoint /refresh which refreshes the context and beans.
I'm not quite sure if you need a spring-cloud-config-server (its a microservice and very easy to build) where your config is stored(Git or svn). Or if its also useable just by the application.properties file in the application.
Here you can find the doc to the refresh scope and spring cloud.
You should be able to use Spring Cloud for that
Add this as a dependency
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter', version: '1.1.2.RELEASE'
And then use #RefreshScope annotation
A Spring #Bean that is marked as #RefreshScope will get special treatment when there is a configuration change. This addresses the problem of stateful beans that only get their configuration injected when they are initialized. For instance if a DataSource has open connections when the database URL is changed via the Environment, we probably want the holders of those connections to be able to complete what they are doing. Then the next time someone borrows a connection from the pool he gets one with the new URL.
Also relevant if you have Spring Actuator
For a Spring Boot Actuator application there are some additional management endpoints:
POST to
/env to update the Environment and rebind #ConfigurationProperties and log levels
/refresh for re-loading the boot strap context and refreshing the #RefreshScope beans
Spring Cloud Doc
(1) Spring Cloud's RestartEndPoint
You may use the RestartEndPoint: Programatically restart Spring Boot application / Refresh Spring Context
RestartEndPoint is an Actuator EndPoint, bundled with spring-cloud-context.
However, RestartEndPoint will not monitor for file changes, you'll have to handle that yourself.
(2) devtools
I don't know if this is for a production application or not. You may hack devtools a little to do what you want.
Take a look at this other answer I wrote for another question: Force enable spring-boot DevTools when running Jar
Devtools monitors for file changes:
Applications that use spring-boot-devtools will automatically restart
whenever files on the classpath change.
Technically, devtools is built to only work within an IDE. With the hack, it also works when launched from a jar. However, I may not do that for a real production application, you decide if it fits your needs.
I know this is a old thread, but it will help someone in future.
You can use a scheduler to periodically refresh properties.
//MyApplication.java
#EnableScheduling
//application.properties
management.endpoint.refresh.enabled = true
//ContextRefreshConfig.java
#Autowired
private RefreshEndpoint refreshEndpoint;
#Scheduled(fixedDelay = 60000, initialDelay = 10000)
public Collection<String> refreshContext() {
final Collection<String> properties = refreshEndpoint.refresh();
LOGGER.log(Level.INFO, "Refreshed Properties {0}", properties);
return properties;
}
//add spring-cloud-starter to the pom file.
Attribues annotated with #Value is refreshed if the bean is annotated with #RefreshScope.
Configurations annotated with #ConfigurationProperties is refreshed without #RefreshScope.
Hope this will help.
You can follow the ContextRefresher.refresh() code implements.
public synchronized Set<String> refresh() {
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());
addConfigFilesToEnvironment();
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
this.context.publishEvent(new EnvironmentChangeEvent(context, keys));
this.scope.refreshAll();
return keys;
}
I'm in the process of building a library which provides an opinionated configuration for applications which use our Spring Cloud Config/Eureka setup. The idea is to deliver this configuration as a custom starter with little or no spring cloud-related boilerplate in individual microservice apps.
At this point, the majority of the shared configuration that I want to put in this library consists of stuff in bootstrap.yml. I'd like to provide bootstrap.yml in my custom starter, but applications using the library still need to be able to provide their own bootstrap.yml, even if only so they can set their spring.application.name properly.
Due to the way bootstrap.yml is loaded from the classpath, Spring seems to ignore the one in the shared lib if the application has its own bootstrap.yml. I can't even use an ApplicationContextInitializer to customize the Environment because of the special way the bootstrap context treats ApplicationContextInitializers.
Does anyone have any recommendations for an approach that would work here? I want to provide a drop-in lib that makes our opinionated bootstrap config work without having to duplicate a boilerplate bootstrap.yml in all of our projects.
You can add a PropertySource in a shared library to the bootstrap properties by using the org.springframework.cloud.bootstrap.BootstrapConfiguration key in the META-INF/spring.factories file.
For example, you can create a library containing the following:
src/main/java/com/example/mylib/MyLibConfig.java
package com.example.mylib;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
#Configuration
#PropertySource("classpath:mylib-config.properties")
public class MyLibConfig {
}
src/main/resources/mylib-config.properties
eureka.instance.public=true
# or whatever...
src/main/resources/META-INF/spring.factories
org.springframework.cloud.bootstrap.BootstrapConfiguration=com.example.mylib.MyLibConfig
More details: http://projects.spring.io/spring-cloud/spring-cloud.html#_customizing_the_bootstrap_configuration
I was able to find a solution to this. The goals of this solution are:
Load the values from a yaml file in a shared library.
Allow applications using the library to introduce their own bootstrap.yml that is also loaded into the Environment.
Values in the bootstrap.yml should override values in the shared yaml.
The main challenge is to inject some code at the appropriate point in the application lifecycle. Specifically, we need to do it after the bootstrap.yml PropertySource is added to the environment (so that we can inject our custom PropertySource in the correct order relative to it), but also before the application starts configuring beans (as our config values control behavior).
The solution I found was to use a custom EnvironmentPostProcessor
public class CloudyConfigEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
private YamlPropertySourceLoader loader;
public CloudyConfigEnvironmentPostProcessor() {
loader = new YamlPropertySourceLoader();
}
#Override
public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication application) {
//ensure that the bootstrap file is only loaded in the bootstrap context
if (env.getPropertySources().contains("bootstrap")) {
//Each document in the multi-document yaml must be loaded separately.
//Start by loading the no-profile configs...
loadProfile("cloudy-bootstrap", env, null);
//Then loop through the active profiles and load them.
for (String profile: env.getActiveProfiles()) {
loadProfile("cloudy-bootstrap", env, profile);
}
}
}
private void loadProfile(String prefix, ConfigurableEnvironment env, String profile) {
try {
PropertySource<?> propertySource = loader.load(prefix + (profile != null ? "-" + profile: ""), new ClassPathResource(prefix + ".yml"), profile);
//propertySource will be null if the profile isn't represented in the yml, so skip it if this is the case.
if (propertySource != null) {
//add PropertySource after the "applicationConfigurationProperties" source to allow the default yml to override these.
env.getPropertySources().addAfter("applicationConfigurationProperties", propertySource);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
public int getOrder() {
//must go after ConfigFileApplicationListener
return Ordered.HIGHEST_PRECEDENCE + 11;
}
}
This custom EnvironmentPostProcessor can be injected via META-INF/spring.factories:
#Environment PostProcessors
org.springframework.boot.env.EnvironmentPostProcessor=\
com.mycompany.cloudy.bootstrap.autoconfig.CloudyConfigEnvironmentPostProcessor
A couple things to note:
The YamlPropertySourceLoader loads yaml properties by profile, so if you are using a multi-document yaml file you need to actually load each profile from it separately, including the no-profile configs.
ConfigFileApplicationListener is the EnvironmentPostProcessor responsible for loading bootstrap.yml (or application.yml for the regular context) into the Environment, so in order to position the custom yaml properties correctly relative to the bootstrap.yml properties precedence-wise, you need to order your custom EnvironmentPostProcessor after ConfigFileApplicationListener.
Edit: My initial answer did not work. I'm replacing it with this one, which does.
I have a project setup something like this:
-common-lib (common lib to included by multiple services)
-event-lib (spring framework 4 (read IOC) library for our event buffer. I want to embed the prod configuration within the app so consumers can use it without configuring it.
-serviceA (depends on event-lib, springboot application)
-serviceB (depends on event-lib, spring framework application)
I've been struggling on how to manage configuration in a Java-annotated way.
In the example below (running in the event library as a spring framework 4 project):
I couldn't get the PropertySource to honor the enviornment's spring.profiles.active
The environment wouldn't set an active profile even though -Dspring.profiles.active="dev" was specified)
#Configuration
#ComponentScan(basePackages = "com.*")
#PropertySource("classpath:events-{$spring.profiles.active}.properties")
public class EventConfiguration {
#Inject
private ConfigurableApplicationContext ctx;
#Inject
private Environment environment;
#Value("${events.asset-processing-queue}")
private String assetProcessingEventQueue;
}
It didn't make much sense to me, since multiple profiles could be activated at once (and that approach to referencing files is dependent on having only 1 set active).
Ideally, I am trying to find a solution that:
Uses either yaml or a combination of properties files for all the environment properties needed
Has some sort of intelligent hierarchy of what properties should be loaded. E.g. if I specify a property in my shared lib, honor it unless the consumer overrides it with their own value.
Can work in a spring framework 4 or spring boot app (we do some stuff with AWS lambda and dont want the spring boot overhead)
Relies only on java annotation and flat files for the properties. (Prefer to avoid XML).
Here's how we did it:
#PropertySource("classpath:/${env}.config.properties")
public class Application implements RequestHandler<Request, Object> {
#Override
public Object handleRequest(Request request, Context awsContext) {
ExecutionEnvironment env = getEnvironment(awsContext.getFunctionName());
System.setProperty("env", env.toString());
This respects the environment property.