Conversation
There was a problem hiding this comment.
Pull request overview
This pull request refactors the state management in the Azure App Configuration Spring library, moving from a static singleton pattern to an instance-based approach managed by Spring's dependency injection framework.
Changes:
- Refactored
StateHolderfrom a static singleton to an instance-based class registered in Spring's BootstrapContext and promoted to the ApplicationContext - Updated
AppConfigurationRefreshUtilto acceptStateHolderas a constructor parameter instead of using static methods - Modified tests to use instance-based StateHolder instead of static methods, splitting monolithic tests into focused unit tests
- Applied minor code quality improvements including replacing
.size() > 0with.isEmpty()and improving variable naming
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| StateHolder.java | Converted from package-private singleton to public instance-based class; removed static methods and singleton pattern |
| StateHolderTest.java | Refactored from single monolithic test to multiple focused tests using instance-based StateHolder |
| AppConfigurationRefreshUtil.java | Added StateHolder constructor parameter; converted static refresh methods to instance methods |
| AppConfigurationRefreshUtilTest.java | Updated to use mocked StateHolder instances instead of static method mocking |
| AppConfigurationPullRefresh.java | Added StateHolder constructor parameter and injected dependency |
| AppConfigurationPullRefreshTest.java | Updated constructor calls to include StateHolder mock; reorganized imports |
| AzureAppConfigBoostrapRegistrar.java | Added StateHolder registration in BootstrapContext with promotion to ApplicationContext |
| AzureAppConfigDataLoader.java | Modified to retrieve StateHolder from BootstrapContext instead of creating new instance |
| AppConfigurationWatchAutoConfiguration.java | Updated bean creation to retrieve StateHolder from BootstrapContext and pass to dependencies |
| ConnectionManager.java | Simplified client tracking by replacing currentReplica with lastActiveClient |
| ConfigStore.java | Improved variable naming (endpoint → validationEndpoint) and validation logic |
| AppConfigurationProperties.java | Replaced .size() > 0 with .isEmpty() for better code style |
| AppConfigurationReplicaClient.java | Replaced .size() > 0 with .isEmpty() for better code style |
| AppConfigurationApplicationSettingPropertySource.java | Added @OverRide annotation and removed unnecessary return statement from void method |
...ure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPullRefreshTest.java
Outdated
Show resolved
Hide resolved
...src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolder.java
Show resolved
Hide resolved
...src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolder.java
Show resolved
Hide resolved
...m/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtil.java
Show resolved
Hide resolved
...m/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationPullRefresh.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.
Comments suppressed due to low confidence (2)
sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolder.java:156
expireStatedereferencesoldStatewithout a null check. IfexpireStateis called for an endpoint that hasn't hadsetState(...)called yet (or state was cleared/never initialized), this will throw a NullPointerException. Add a guard (e.g., return early whenoldState == null) so expiring an unknown endpoint is a no-op.
public void expireState(String originEndpoint) {
State oldState = state.get(originEndpoint);
long wait = (long) (new SecureRandom().nextDouble() * MAX_JITTER);
long timeLeft = (int) ((oldState.getNextRefreshCheck().toEpochMilli() - (Instant.now().toEpochMilli())) / 1000);
if (wait < timeLeft) {
sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolder.java:195
updateNextRefreshTimecallsgetNextRefreshCheck(nextForcedRefresh, ...)without handlingnextForcedRefresh == null, which will NPE insidegetNextRefreshCheck. Consider initializingnextForcedRefreshin the constructor (e.g.,Instant.now()) and/or treating null as "expired" and setting a new value before callinggetNextRefreshCheck.
public void updateNextRefreshTime(Duration refreshInterval, Long defaultMinBackoff) {
if (refreshInterval != null) {
Instant newForcedRefresh = getNextRefreshCheck(nextForcedRefresh,
clientRefreshAttempts, refreshInterval.getSeconds(), defaultMinBackoff);
if (newForcedRefresh.compareTo(nextForcedRefresh) != 0) {
clientRefreshAttempts += 1;
}
nextForcedRefresh = newForcedRefresh;
}
...m/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtil.java
Outdated
Show resolved
Hide resolved
...m/azure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtil.java
Outdated
Show resolved
Hide resolved
...va/com/azure/spring/cloud/appconfiguration/config/implementation/properties/ConfigStore.java
Outdated
Show resolved
Hide resolved
...ure/spring/cloud/appconfiguration/config/implementation/AzureAppConfigBoostrapRegistrar.java
Outdated
Show resolved
Hide resolved
...ure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
Outdated
Show resolved
Hide resolved
...test/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolderTest.java
Outdated
Show resolved
Hide resolved
...test/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolderTest.java
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
...ure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
Show resolved
Hide resolved
...ure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
Show resolved
Hide resolved
...a/com/azure/spring/cloud/appconfiguration/config/AppConfigurationWatchAutoConfiguration.java
Outdated
Show resolved
Hide resolved
...src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolder.java
Show resolved
Hide resolved
...ure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
Show resolved
Hide resolved
...ure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
Show resolved
Hide resolved
...test/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolderTest.java
Outdated
Show resolved
Hide resolved
...a/com/azure/spring/cloud/appconfiguration/config/AppConfigurationWatchAutoConfiguration.java
Outdated
Show resolved
Hide resolved
...pring/cloud/feature/management/implementation/timewindow/recurrence/RecurrenceEvaluator.java
Outdated
Show resolved
Hide resolved
...ure/spring/cloud/appconfiguration/config/implementation/AppConfigurationRefreshUtilTest.java
Show resolved
Hide resolved
...pring/cloud/feature/management/implementation/timewindow/recurrence/RecurrenceEvaluator.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/StateHolder.java:197
updateNextRefreshTimecallsgetNextRefreshCheck(nextForcedRefresh, ...)whenrefreshInterval != null, butnextForcedRefreshcan be null (pergetNextForcedRefresh()JavaDoc and whensetNextForcedRefreshhasn’t been called). This can cause a NullPointerException insidegetNextRefreshCheck(and also innewForcedRefresh.compareTo(nextForcedRefresh)). Consider initializingnextForcedRefreshwhen first needed (e.g., treat null as expired and set toInstant.now()/Instant.EPOCH), or guard null explicitly before callinggetNextRefreshCheck.
public void updateNextRefreshTime(Duration refreshInterval, Long defaultMinBackoff) {
if (refreshInterval != null) {
Instant newForcedRefresh = getNextRefreshCheck(nextForcedRefresh,
clientRefreshAttempts, refreshInterval.getSeconds(), defaultMinBackoff);
if (newForcedRefresh.compareTo(nextForcedRefresh) != 0) {
clientRefreshAttempts += 1;
}
nextForcedRefresh = newForcedRefresh;
Description
All SDK Contribution checklist:
General Guidelines and Best Practices
Testing Guidelines