Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
In this article, we will focus on how to refresh the configuration data fetched from the Spring Cloud Config Server.
In the previous article, we have looked over the Spring Cloud Config basics and created a sample config client and config server with a git repository as a configuration data store. Thus, we created a central configuration server to manage all the configuration in one place.
In this article, we will focus on how to refresh configurations fetched from the config server.
A Spring application holds the properties, which are read from an external property source such as a properties file or a config server, in PropertySource abstractions, and serves these properties to the beans of the Spring Application Context via the Environment interface.
As you see in the diagram above when the Spring Boot Config Client application starts up, it fetches the remote property sources from the config server in precedence we looked over in the previous article and put them higher on the property source list. Higher in this list means higher in precedence. Actually there are more property sources than in the diagram but to be shown here, the ones coming from the bootstrap context generally have priority over the ones from local property files.
Just after this refreshment of property sources, the next step is wiring these properties to the Spring beans and this happens when the beans are instantiated. This can happen via @Value or @ConfigurationProperties annotations.
When it comes to refreshing the properties in the application context, there are two steps again; reloading the property sources in the Environment and refreshing the attributes of Spring beans.
In the case of using Spring Cloud Config Server; Spring Cloud offers the following methods to refresh the properties in config clients.
The first method of property refreshment is calling the /actuator/refresh endpoint. This endpoint is exposed in config clients so a call to this endpoint just refreshes the client to which the request is made.
Let’s look over the following diagram to understand this type of refresh process:
curl -H "Content-Type: application/json" -d {} http://localhost:8080/actuator/refresh
In order to expose an actuator endpoint, we add the following dependency to our config client’s pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
By default, most of the actuator endpoints are disabled. To display which endpoints are enabled, you can call the /actuator endpoint. To enable all the actuator endpoints, including the /actuator/refresh endpoint, we can make the following configuration:
management.endpoints.web.exposure.include=*
We can also just enable the endpoints we need by separating them via commas:
management.endpoints.web.exposure.include=refresh, bus-refresh, beans, env
Just after the reloading of property sources by calling the /actuator/refresh endpoint, the attributes bounded with @Value in the beans having the annotation @RefreshScope is refreshed.
The property bindings made with the @ConfigurationProperties annotation do not need @RefreshScope annotation to be refreshed.
And also, property readings directly from the Environment interface does not need this annotation.
@Service @Getter @Setter @RefreshScope public class MyService { @Value("#{'yes'.equals('${activated:false}')}") private Boolean activated; }
Sometimes, we calculate a bean attribute with the use of a configuration parameter mostly in the post-construction of the bean itself. In this case, besides the refreshment of the attribute annotated with @Value, we also need to execute this initial calculation of the relevant bean attribute. This capability is provided as throwing an event named EnvironmentChangeEvent just at the end of the refresh process. So, the beans listening to this event can recalculate their attributes just like in the following sample:
@Service @RefreshScope @Getter @Setter public class RefreshScopedService { @Value("${tps}") Integer tps; String responseMessage; @PostConstruct public void refresh(){ responseMessage = responseMessage = "Service is running with tps: " + tps; } }
@Service public class MyRefreshListener implements ApplicationListener<EnvironmentChangeEvent> { @Autowired RefreshScopedService refreshScopedService; @Override public void onApplicationEvent(EnvironmentChangeEvent event) { if(event.getKeys().contains("tps")) { refreshScopedService.refresh(); } } }
Calling the refresh endpoint is simple and to the point when the requirement is refreshing the configuration properties in a single config client. However, we generally have numerous instances of several services, and calling the refresh endpoint for each of them manually can be cumbersome work.
So, one solution to this problem is that; we can write bash scripts for each service and call the script of the relevant service when a configuration change happens.
But, there is also an alternative more advanced solution to this problem, provided with another project under Spring Cloud.
Spring Cloud Bus enables broadcasting the state changes among the services over a message broker like Kafka or RabbitMQ.
So in our scenario, each config client reading from the same config server can communicate over a message broker and refresh themselves in case of configuration changes. That means we do not need to make a refresh request for each config client application instead, we just broadcast a message to the services over Spring Cloud Bus to refresh themselves.
Let’s look over the following diagram:
The refresh process can be triggered by calling the actuator endpoint /actuator/bus-refresh exposed on a config client instance or, by calling the /monitor endpoint exposed on the config server.
In each case, a state change message is broadcasted to the services over the message broker configured via Spring Cloud Bus and then each service makes a request to the config server with the config server url to refresh themselves.
The /monitor endpoint is designed for git cloud solutions, like Github, to make push notifications using git webhooks. However, one can manually trigger a refresh event by making a POST request to this endpoint.
We want to send a refresh message over a message broker by calling the /monitor endpoint, so we add the Spring Cloud Bus dependency; in our case, we will use Kafka as a message broker so the relevant dependency to be added is:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-kafka</artifactId> </dependency>
To be able to expose the /monitor endpoint over the config server, we need to add the following dependency:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-monitor</artifactId> </dependency>
And lastly, we need to enable Spring Cloud Bus and configure Kafka settings in the bootstrap.properties:
spring.cloud.bus.enabled = true spring.cloud.bus.id=my-config-server spring.cloud.stream.kafka.binder.zkNodes=localhost:2181 spring.cloud.stream.kafka.binder.brokers=localhost:9092
That’s all to wire the config server to a message broker to enable the refresh process over Spring Cloud Bus.
What we have done here is that:
And now, let’s configure the client-side.
We want to make our services also communicate over a message broker for listening and broadcasting refresh events. So here too, we need to add the Spring Cloud Bus dependency to the config client’s pom.xml:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-kafka</artifactId> </dependency>
And Kafka settings are also nearly the same.
spring.cloud.bus.enabled = true spring.cloud.stream.kafka.binder.zkNodes=localhost:2181 spring.cloud.stream.kafka.binder.brokers=localhost:9092
And now, our config clients are ready to be refreshed with an incoming refresh message over Spring Cloud Bus.
We have just configured our config server and config client applications to be able to communicate via Spring Cloud Bus integration and refresh themselves with the special endpoints. One of them is bus-refresh provided by Spring Cloud as a Spring actuator endpoint.
We have already configured it to be exposed. When a configuration property changes, then we just call this endpoint on any one of the config clients.
curl -X POST http://localhost:8080/actuator/bus-refresh
Then the followings happen:
Note that, you do not need to call the /actuator/bus-refresh endpoint for every client unlike as in /actuator/refresh endpoint.
Another endpoint to refresh the configuration data via Spring Cloud Bus is /monitor endpoint. Here are some notes about this endpoint:
And it’s time to try, let’s run the sample codes and see what happens.
erol@ehira:~/.../case3_config-server-bus$ ./mvnw spring-boot:run
erol@ehira:~/.../case3_config-client-bus$ ./mvnw spring-boot:run
erol@ehira:~/.../case3_config-client-bus$ ./mvnw spring-boot:run -Dspring-boot.run.arguments="--server.port=8081"
curl --location --request POST 'http://localhost:8888/monitor' --header 'X-Github-Event: push' --header 'Content-Type: application/json' --data-raw '{"commits": [{"modified": ["myconfig-client-app.properties"] }]}'
{ "type":"RefreshRemoteApplicationEvent", "timestamp":1585513056017, "originService":"my-config-server", "destinationService":"myconfig-client-app-instance01:**", "id":"a5ede7cd-9c33-450a-938d-b414e9271c46" }
{ "type":"AckRemoteApplicationEvent", "timestamp":1585513056471, "originService":"myconfig-client-app-instance01", "destinationService":"**", "id":"fcd5737d-1685-433e-a669-dc5ab2bc9540", "ackId":"a5ede7cd-9c33-450a-938d-b414e9271c46", "ackDestinationService":"myconfig-client-app-instance01:**", "event":"org.springframework.cloud.bus.event.RefreshRemoteApplicationEvent" }
You can see the sample code for this article on my Github page:
https://github.com/erolhira/spring-cloud-config
https://docs.spring.io/spring-cloud-config/docs/2.2.5.RELEASE/reference/html/
https://cloud.spring.io/spring-cloud-static/spring-cloud-bus/2.2.2.RELEASE/reference/html/
https://cloud.spring.io/spring-cloud-static/Hoxton.RELEASE/reference/html/spring-cloud-hoxton-configprops.html