Skip to content
This repository has been archived by the owner on Jun 25, 2024. It is now read-only.

Commit

Permalink
Add SpringBootConfigStorage to allow mutable properties in Spring Boo…
Browse files Browse the repository at this point in the history
…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
symposion committed Nov 6, 2020
1 parent a841239 commit bda34f5
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 39 deletions.
11 changes: 11 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
<phr-pulsar.version>12-a115018-86851</phr-pulsar.version>
<restassured.version>3.0.7-PKB</restassured.version>

<spring.version>5.2.3.RELEASE</spring.version> <!-- copied from PHR -->
<commons.testing.version>20-789c720-63866</commons.testing.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<hibernate-core.version>5.3.15.Final</hibernate-core.version>
</properties>

Expand All @@ -64,6 +67,7 @@
<module>spring-infrastructure</module>
<module>testlogging</module>
<module>testsupport</module>
<module>spring-boot-infrastructure</module>
</modules>

<dependencyManagement>
Expand Down Expand Up @@ -210,6 +214,13 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
Expand Down
46 changes: 46 additions & 0 deletions spring-boot-infrastructure/pom.xml
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>
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
}
}
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();
}
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@ActiveProfiles("integration-test")
Expand Down
13 changes: 1 addition & 12 deletions spring-infrastructure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
<name>pkb-common/spring-infrastructure</name>

<properties>
<spring.version>5.2.3.RELEASE</spring.version> <!-- copied from PHR -->
<commons.testing.version>20-789c720-63866</commons.testing.version>

</properties>

<dependencies>
Expand All @@ -27,12 +26,6 @@
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.pkb.pkbcommon</groupId>
<artifactId>infrastructure</artifactId>
Expand All @@ -53,10 +46,6 @@
<artifactId>junit-jupiter-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
</dependency>
<dependency>
<groupId>com.pkb</groupId>
<artifactId>rest-assured</artifactId>
Expand Down

This file was deleted.

0 comments on commit bda34f5

Please sign in to comment.