Browse Source

Refresh Scope on restart. (#1266)

* Refresh Scope on restart.
pull/1270/head
Olga Maciaszek-Sharma 1 year ago committed by GitHub
parent
commit
9449edf93b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      docs/modules/ROOT/pages/spring-cloud-commons/application-context-services.adoc
  2. 1
      docs/modules/ROOT/partials/_configprops.adoc
  3. 8
      spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java
  4. 77
      spring-cloud-context/src/main/java/org/springframework/cloud/context/refresh/RefreshScopeLifecycle.java
  5. 6
      spring-cloud-context/src/main/resources/META-INF/additional-spring-configuration-metadata.json
  6. 26
      spring-cloud-context/src/test/java/org/springframework/cloud/autoconfigure/RefreshAutoConfigurationTests.java
  7. 51
      spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/RefreshScopeLifecycleTests.java

6
docs/modules/ROOT/pages/spring-cloud-commons/application-context-services.adoc

@ -217,6 +217,12 @@ The configuration property must be present in order to update the value after a
a value in your application you might want to switch your logic to rely on its absence instead. Another option would be to rely a value in your application you might want to switch your logic to rely on its absence instead. Another option would be to rely
on the value changing rather than not being present in the application's configuration. on the value changing rather than not being present in the application's configuration.
[refresh-scope-on-restart]
=== Refresh Scope on Restart
In order to allow seamlessly refreshing beans on restart, which is especially useful for applications running with JVM Checkpoint Restore (for example with https://github.com/CRaC[Project CRaC]), we now instantiate a `RefreshScopeLifecycle` bean that will trigger Context Refresh on restart, resulting in rebinding configuration properties and refreshing any `@RefreshScope`-annotated beans. This behaviour can be disabled by setting the value of `spring.cloud.refresh.on-restart.enabled` to `false`.
[[encryption-and-decryption]] [[encryption-and-decryption]]
== Encryption and Decryption == Encryption and Decryption

1
docs/modules/ROOT/partials/_configprops.adoc

@ -75,6 +75,7 @@
|spring.cloud.refresh.enabled | `+++true+++` | Enables autoconfiguration for the refresh scope and associated features. |spring.cloud.refresh.enabled | `+++true+++` | Enables autoconfiguration for the refresh scope and associated features.
|spring.cloud.refresh.extra-refreshable | `+++true+++` | Additional class names for beans to post process into refresh scope. |spring.cloud.refresh.extra-refreshable | `+++true+++` | Additional class names for beans to post process into refresh scope.
|spring.cloud.refresh.never-refreshable | `+++true+++` | Comma separated list of class names for beans to never be refreshed or rebound. |spring.cloud.refresh.never-refreshable | `+++true+++` | Comma separated list of class names for beans to never be refreshed or rebound.
|spring.cloud.refresh.on-restart.enabled | `+++true+++` | Enable refreshing context on start.
|spring.cloud.service-registry.auto-registration.enabled | `+++true+++` | Whether service auto-registration is enabled. Defaults to true. |spring.cloud.service-registry.auto-registration.enabled | `+++true+++` | Whether service auto-registration is enabled. Defaults to true.
|spring.cloud.service-registry.auto-registration.fail-fast | `+++false+++` | Whether startup fails if there is no AutoServiceRegistration. Defaults to false. |spring.cloud.service-registry.auto-registration.fail-fast | `+++false+++` | Whether startup fails if there is no AutoServiceRegistration. Defaults to false.
|spring.cloud.service-registry.auto-registration.register-management | `+++true+++` | Whether to register the management as a service. Defaults to true. |spring.cloud.service-registry.auto-registration.register-management | `+++true+++` | Whether to register the management as a service. Defaults to true.

8
spring-cloud-context/src/main/java/org/springframework/cloud/autoconfigure/RefreshAutoConfiguration.java

@ -43,6 +43,7 @@ import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.cloud.context.refresh.ConfigDataContextRefresher; import org.springframework.cloud.context.refresh.ConfigDataContextRefresher;
import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.cloud.context.refresh.LegacyContextRefresher; import org.springframework.cloud.context.refresh.LegacyContextRefresher;
import org.springframework.cloud.context.refresh.RefreshScopeLifecycle;
import org.springframework.cloud.context.scope.refresh.RefreshScope; import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.cloud.endpoint.event.RefreshEventListener; import org.springframework.cloud.endpoint.event.RefreshEventListener;
import org.springframework.cloud.logging.LoggingRebinder; import org.springframework.cloud.logging.LoggingRebinder;
@ -66,6 +67,7 @@ import org.springframework.util.StringUtils;
* *
* @author Dave Syer * @author Dave Syer
* @author Venil Noronha * @author Venil Noronha
* @author Olga Maciaszek-Sharma
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RefreshScope.class) @ConditionalOnClass(RefreshScope.class)
@ -117,6 +119,12 @@ public class RefreshAutoConfiguration {
return new ConfigDataContextRefresher(context, scope, properties); return new ConfigDataContextRefresher(context, scope, properties);
} }
@ConditionalOnProperty(value = "spring.cloud.refresh.on-restart.enabled", matchIfMissing = true)
@Bean
RefreshScopeLifecycle refreshScopeLifecycle(ContextRefresher contextRefresher) {
return new RefreshScopeLifecycle(contextRefresher);
}
@Bean @Bean
public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) { public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
return new RefreshEventListener(contextRefresher); return new RefreshEventListener(contextRefresher);

77
spring-cloud-context/src/main/java/org/springframework/cloud/context/refresh/RefreshScopeLifecycle.java

@ -0,0 +1,77 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.context.refresh;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.Lifecycle;
/**
* A {@link Lifecycle} implementation that triggers {@link ContextRefresher#refresh()} to
* be called on restart.
*
* @author Olga Maciaszek-Sharma
* @since 4.1.0
*/
public class RefreshScopeLifecycle implements Lifecycle {
private static final Log LOG = LogFactory.getLog(RefreshScopeLifecycle.class);
private final ContextRefresher contextRefresher;
private final Object lifecycleMonitor = new Object();
private volatile boolean running = true;
public RefreshScopeLifecycle(ContextRefresher contextRefresher) {
this.contextRefresher = contextRefresher;
}
@Override
public void start() {
synchronized (lifecycleMonitor) {
if (!isRunning()) {
if (LOG.isInfoEnabled()) {
LOG.info("Refreshing context on restart.");
}
Set<String> keys = contextRefresher.refresh();
if(LOG.isInfoEnabled()){
LOG.info("Refreshed keys: " + keys);
}
}
running = true;
}
}
@Override
public void stop() {
synchronized (lifecycleMonitor) {
if (isRunning()) {
running = false;
}
}
}
@Override
public boolean isRunning() {
return running;
}
}

6
spring-cloud-context/src/main/resources/META-INF/additional-spring-configuration-metadata.json

@ -53,6 +53,12 @@
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
"description": "Enable the DecryptEnvironmentPostProcessor.", "description": "Enable the DecryptEnvironmentPostProcessor.",
"defaultValue": true "defaultValue": true
},
{
"name": "spring.cloud.refresh.on-restart.enabled",
"type": "java.lang.Boolean",
"description": "Enable refreshing context on start.",
"defaultValue": true
} }
] ]
} }

26
spring-cloud-context/src/test/java/org/springframework/cloud/autoconfigure/RefreshAutoConfigurationTests.java

@ -22,11 +22,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.WebApplicationType; import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.cloud.context.refresh.ContextRefresher; import org.springframework.cloud.context.refresh.ContextRefresher;
@ -38,9 +40,10 @@ import static org.assertj.core.api.BDDAssertions.then;
/** /**
* @author Dave Syer * @author Dave Syer
* @author Olga Maciaszek-Sharma
*/ */
@ExtendWith(OutputCaptureExtension.class) @ExtendWith(OutputCaptureExtension.class)
public class RefreshAutoConfigurationTests { class RefreshAutoConfigurationTests {
private static ConfigurableApplicationContext getApplicationContext(WebApplicationType type, Class<?> configuration, private static ConfigurableApplicationContext getApplicationContext(WebApplicationType type, Class<?> configuration,
String... properties) { String... properties) {
@ -49,7 +52,7 @@ public class RefreshAutoConfigurationTests {
} }
@Test @Test
public void noWarnings(CapturedOutput output) { void noWarnings(CapturedOutput output) {
try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.NONE, Config.class)) { try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.NONE, Config.class)) {
then(context.containsBean("refreshScope")).isTrue(); then(context.containsBean("refreshScope")).isTrue();
then(output.toString()).doesNotContain("WARN"); then(output.toString()).doesNotContain("WARN");
@ -57,7 +60,7 @@ public class RefreshAutoConfigurationTests {
} }
@Test @Test
public void disabled() { void disabled() {
try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.SERVLET, Config.class, try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.SERVLET, Config.class,
"spring.cloud.refresh.enabled:false")) { "spring.cloud.refresh.enabled:false")) {
then(context.containsBean("refreshScope")).isFalse(); then(context.containsBean("refreshScope")).isFalse();
@ -65,7 +68,7 @@ public class RefreshAutoConfigurationTests {
} }
@Test @Test
public void refreshables() { void refreshables() {
try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.NONE, Config.class, try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.NONE, Config.class,
"config.foo=bar", "spring.cloud.refresh.refreshable:" + SealedConfigProps.class.getName())) { "config.foo=bar", "spring.cloud.refresh.refreshable:" + SealedConfigProps.class.getName())) {
context.getBean(SealedConfigProps.class); context.getBean(SealedConfigProps.class);
@ -84,7 +87,7 @@ public class RefreshAutoConfigurationTests {
} }
@Test @Test
public void neverRefreshable() { void neverRefreshable() {
try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.NONE, Config.class, try (ConfigurableApplicationContext context = getApplicationContext(WebApplicationType.NONE, Config.class,
"countingconfig.foo=bar", "countingconfig.foo=bar",
"spring.cloud.refresh.never-refreshable:" + CountingConfigProps.class.getName())) { "spring.cloud.refresh.never-refreshable:" + CountingConfigProps.class.getName())) {
@ -94,6 +97,19 @@ public class RefreshAutoConfigurationTests {
} }
} }
@Test
void refreshScopeLifecylePresentByDefault() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(RefreshAutoConfiguration.class))
.run(context -> assertThat(context).hasBean("refreshScopeLifecycle"));
}
@Test
void refreshScopeLifecyleDisabledWithProp() {
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(RefreshAutoConfiguration.class))
.withPropertyValues("spring.cloud.refresh.on-restart.enabled=false")
.run(context -> assertThat(context).doesNotHaveBean("refreshScopeLifecycle"));
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
@EnableConfigurationProperties({ SealedConfigProps.class, CountingConfigProps.class }) @EnableConfigurationProperties({ SealedConfigProps.class, CountingConfigProps.class })

51
spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/RefreshScopeLifecycleTests.java

@ -0,0 +1,51 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.context.refresh;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
/**
* Tests for {@link RefreshScopeLifecycle}.
*
* @author Olga Maciaszek-Sharma
*/
class RefreshScopeLifecycleTests {
ContextRefresher contextRefresher = mock();
private final RefreshScopeLifecycle lifecycle = new RefreshScopeLifecycle(contextRefresher);
@Test
void shouldRefreshContextOnRestart() {
lifecycle.stop();
lifecycle.start();
verify(contextRefresher).refresh();
}
@Test
void shouldNotRefreshContextOnStart() {
lifecycle.start();
verifyNoInteractions(contextRefresher);
}
}
Loading…
Cancel
Save