This repository has been archived by the owner on Jun 25, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SpringBootConfigStorage to allow mutable properties in Spring Boo…
…t apps QUES-36 Also move Spring boot tools into their own module to avoid pulling in unnecessary dependencies to non-spring-boot apps.
- Loading branch information
Showing
7 changed files
with
219 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<parent> | ||
<artifactId>pkb-common</artifactId> | ||
<groupId>com.pkb.pkbcommon</groupId> | ||
<version>${revision}${changelist}</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>spring-boot-infrastructure</artifactId> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.springframework</groupId> | ||
<artifactId>spring-test</artifactId> | ||
<version>${spring.version}</version> | ||
<scope>compile</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework</groupId> | ||
<artifactId>spring-beans</artifactId> | ||
<version>${spring.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.cloud</groupId> | ||
<artifactId>spring-cloud-context</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter-api</artifactId> | ||
<scope>compile</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.pkb.pkbcommon</groupId> | ||
<artifactId>config</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework</groupId> | ||
<artifactId>spring-context</artifactId> | ||
<version>${spring.version}</version> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
58 changes: 58 additions & 0 deletions
58
...ture/src/main/java/com/pkb/common/config/ConfigurationPropertiesScopingPostProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package com.pkb.common.config; | ||
|
||
import org.springframework.aop.scope.ScopedProxyUtils; | ||
import org.springframework.beans.BeansException; | ||
import org.springframework.beans.factory.config.BeanDefinition; | ||
import org.springframework.beans.factory.config.BeanDefinitionHolder; | ||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | ||
import org.springframework.beans.factory.config.Scope; | ||
import org.springframework.beans.factory.support.BeanDefinitionRegistry; | ||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; | ||
import org.springframework.beans.factory.support.GenericBeanDefinition; | ||
import org.springframework.context.annotation.ScopedProxyMode; | ||
import org.springframework.core.annotation.AnnotationAttributes; | ||
import org.springframework.core.type.AnnotationMetadata; | ||
|
||
import javax.annotation.ParametersAreNonnullByDefault; | ||
import java.util.Optional; | ||
|
||
/** | ||
* This post-processor works around a bug/limitation of Spring Boot/Spring Cloud whereby | ||
* beans discovered by the @EnableConfigurationProperties or @ConfigurationPropertiesScan annotations | ||
* aren't ever put into custom scopes like the RefreshScope. This post-processor mimics what the | ||
* standard Spring bean component scanning does by reading the scope attributes and then creating | ||
* a scoped proxy if the requested scope requires it. It also ensure that the bean definition | ||
* has the appropriate scope marked on it. | ||
* | ||
* To use this, define a static method on Configuration class that returns an instance of this as a Bean. | ||
* You should probably give it an @Order attribute as well to ensure that this executes as early as possible | ||
* so that other standard scope processing steps can operate on the corrected bean definition. | ||
*/ | ||
@ParametersAreNonnullByDefault | ||
public class ConfigurationPropertiesScopingPostProcessor implements BeanDefinitionRegistryPostProcessor { | ||
|
||
@Override | ||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { | ||
for (String name : registry.getBeanDefinitionNames()) { | ||
BeanDefinition beanDefinition = registry.getBeanDefinition(name); | ||
if (beanDefinition.getClass().getName().equals("org.springframework.boot.context.properties.ConfigurationPropertiesValueObjectBeanDefinition")) { | ||
GenericBeanDefinition gbd = (GenericBeanDefinition) beanDefinition; | ||
Optional<AnnotationAttributes> annAttrs = Optional.ofNullable(AnnotationAttributes.fromMap(AnnotationMetadata.introspect(gbd.getBeanClass()).getAnnotationAttributes(Scope.class.getName(), false))); | ||
ScopedProxyMode scopedProxyMode = annAttrs.map(attrs -> attrs.<ScopedProxyMode>getEnum("proxyMode")).orElse(ScopedProxyMode.NO); | ||
gbd.setScope(annAttrs.map(attrs -> attrs.getString("value")).orElse(gbd.getScope())); | ||
if (scopedProxyMode == ScopedProxyMode.NO) { | ||
continue; | ||
} | ||
boolean proxyTargetClass = scopedProxyMode == ScopedProxyMode.TARGET_CLASS; | ||
registry.removeBeanDefinition(name); | ||
BeanDefinitionHolder scopedProxy = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(gbd, name), registry, proxyTargetClass); | ||
registry.registerBeanDefinition(scopedProxy.getBeanName(), scopedProxy.getBeanDefinition()); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { | ||
//No-op | ||
} | ||
} |
102 changes: 102 additions & 0 deletions
102
spring-boot-infrastructure/src/main/java/com/pkb/common/config/SpringBootConfigStorage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package com.pkb.common.config; | ||
|
||
import org.springframework.cloud.context.scope.refresh.RefreshScope; | ||
import org.springframework.core.env.ConfigurableEnvironment; | ||
import org.springframework.core.env.MapPropertySource; | ||
|
||
import javax.annotation.ParametersAreNonnullByDefault; | ||
import java.util.LinkedHashMap; | ||
import java.util.Map; | ||
|
||
/** | ||
* This class provides an implementation of the PKB ConfigStorage interface that interacts | ||
* helpfully with the Spring environment. This class can be passed to both the TestSupportAgent | ||
* and any PKB config objects that require a ConfigStorage. It will read properties from the | ||
* Spring environment, allowing Spring Boot applications to be be configured using all the standard | ||
* Spring properties mechanisms such as application.properties and the OS environment - so you don't | ||
* need to provide an additional config.properties file just to control shared PKB components that | ||
* use PKB Common configuration classes | ||
* | ||
* Additionally, this class maintains an override map that is loaded into the Spring environment | ||
* as a high-priority property source. If mutations to properties are requested (e.g. from the | ||
* TestSupportAgent) these overrides will be visible in the spring environment. | ||
* | ||
* In a typical Spring Boot application, properties are not accessed directly from the environment but | ||
* are bound to type-safe classes annotated with @ConfigurationProperties. Since this binding happens when | ||
* the Spring context is loading, in order for later changes to the Spring environment to be visible | ||
* to these classes and their clients, it is necessary to do a selective refresh of the spring context. | ||
* ConfigurationProperties beans that contain mutable properties that may need to be updated at runtime | ||
* should be annotated with @RefreshScope. Additionally, you should include the {@link ConfigurationPropertiesScopingPostProcessor} | ||
* bean in your context to ensure that all ConfigurationProperties classes get included in the RefreshScope, | ||
* irrespective of how they are constructed. Once you do this, this class will ensure that configuration | ||
* beans are dynamically re-bound at runtime when properties change. | ||
* | ||
* It is recommended to use @ConstructorBinding for ConfigurationProperties classes; this can be used with | ||
* kotlin data classes, java value objects or at least a POJO with final fields and no setters. This ensures | ||
* that properties present an immutable interface to clients and can only be changed by the framework. | ||
* | ||
* | ||
* NB This class and the above-described mechanism only ensure that the relevant properties classes will | ||
* return the updated values on the next method call to one of the property getters. As yet there is no | ||
* mechanism for notifying clients that an update has occured (although in principle this would not be difficult) | ||
* nor does it make any attempt to ensure that any dependencies of these properties beans - including spring | ||
* infrastructure that may have been wired together according to their values - are dynamically updated. | ||
*/ | ||
@ParametersAreNonnullByDefault | ||
public class SpringBootConfigStorage extends AbstractBaseConfigStorage implements ImmutableConfigStorage { | ||
private final ConfigurableEnvironment environment; | ||
private final RefreshScope refreshScope; | ||
private final Map<String, Object> overrides = new LinkedHashMap<>(); | ||
|
||
public SpringBootConfigStorage(ConfigurableEnvironment environment, RefreshScope refreshScope) { | ||
this.environment = environment; | ||
this.refreshScope = refreshScope; | ||
environment.getPropertySources().addFirst(new MapPropertySource("TestSupportOverrides", overrides)); | ||
} | ||
|
||
|
||
@Override | ||
public String getString(String key) { | ||
return environment.getProperty(key); | ||
} | ||
|
||
@Override | ||
public String getString(String key, String defaultValue) { | ||
return environment.getProperty(key, defaultValue); | ||
} | ||
|
||
@Override | ||
public boolean isMutableConfigEnabled() { | ||
return getBoolean("mutableConfig.enabled", false); | ||
} | ||
|
||
@Override | ||
public void setValue(String key, String value) { | ||
if (isMutableConfigEnabled()) { | ||
overrides.put(key, value); | ||
refreshScope.refreshAll(); | ||
} | ||
} | ||
|
||
@Override | ||
public OverrideRemovalResult removeOverrideAtKey(String key) { | ||
if (!isMutableConfigEnabled()) { | ||
return OverrideRemovalResult.NO_OP_AS_CONFIG_IS_IMMUTABLE; | ||
} | ||
if (overrides.remove(key) == null) { | ||
return OverrideRemovalResult.KEY_NOT_FOUND; | ||
} | ||
refreshScope.refreshAll(); | ||
return OverrideRemovalResult.REMOVED; | ||
} | ||
|
||
@Override | ||
public void reset() { | ||
if (isMutableConfigEnabled()) { | ||
overrides.clear(); | ||
refreshScope.refreshAll(); | ||
} | ||
} | ||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 0 additions & 26 deletions
26
spring-infrastructure/src/main/java/com/pkb/common/config/SpringConfigStorage.java
This file was deleted.
Oops, something went wrong.