From b875b5571187d8d6eb5d3ea8775decffcfc8d2d4 Mon Sep 17 00:00:00 2001 From: Jaromir Hamala Date: Wed, 24 Nov 2021 15:19:10 +0300 Subject: [PATCH 1/2] Inject SpringManagedContext into Hazelcast configuration This commit makes it possible to inject Spring managed beans into objects instantiated by Hazelcast. See gh-28801 --- .../HazelcastServerConfiguration.java | 34 +++++++++- ...HazelcastAutoConfigurationServerTests.java | 67 +++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastServerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastServerConfiguration.java index 63fe94612e..98ce41e108 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastServerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastServerConfiguration.java @@ -25,8 +25,12 @@ import com.hazelcast.config.YamlConfigBuilder; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.spring.context.SpringManagedContext; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -60,10 +64,14 @@ class HazelcastServerConfiguration { static class HazelcastServerConfigFileConfiguration { @Bean - HazelcastInstance hazelcastInstance(HazelcastProperties properties, ResourceLoader resourceLoader) + HazelcastInstance hazelcastInstance(HazelcastProperties properties, ResourceLoader resourceLoader, + ObjectProvider customizerProvider) throws IOException { Resource configLocation = properties.resolveConfigLocation(); Config config = (configLocation != null) ? loadConfig(configLocation) : Config.load(); + customizerProvider.ifAvailable(c -> { + c.customize(config); + }); config.setClassLoader(resourceLoader.getClassLoader()); return getHazelcastInstance(config); } @@ -93,7 +101,6 @@ class HazelcastServerConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnSingleCandidate(Config.class) static class HazelcastServerConfigConfiguration { - @Bean HazelcastInstance hazelcastInstance(Config config) { return getHazelcastInstance(config); @@ -101,6 +108,29 @@ class HazelcastServerConfiguration { } + @FunctionalInterface + private interface ConfigurationCustomizer { + void customize(Config configuration); + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "com.hazelcast.spring.context.SpringManagedContext") + static class HazelcastConfigCustomizerConfiguration { + private final ApplicationContext applicationContext; + + public HazelcastConfigCustomizerConfiguration(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Bean + public ConfigurationCustomizer springManagedContextConfigurationCustomizer() { + return configuration -> { + SpringManagedContext springManagedContext = new SpringManagedContext(applicationContext); + configuration.setManagedContext(springManagedContext); + }; + } + } + /** * {@link HazelcastConfigResourceCondition} that checks if the * {@code spring.hazelcast.config} configuration key is defined. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationServerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationServerTests.java index 3856dc9941..192837198c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationServerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationServerTests.java @@ -22,14 +22,21 @@ import com.hazelcast.config.Config; import com.hazelcast.config.QueueConfig; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.EntryProcessor; +import com.hazelcast.map.IMap; +import com.hazelcast.spring.context.SpringAware; +import com.hazelcast.spring.context.SpringManagedContext; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; @@ -164,6 +171,55 @@ class HazelcastAutoConfigurationServerTests { }); } + @Test + void defaultConfigFile_injectManagedContext() { + this.contextRunner.run((context) -> { + HazelcastInstance hz = context.getBean(HazelcastInstance.class); + IMap map = hz.getMap("myMap"); + boolean contextInjected = map.executeOnKey(42, new SpringAwareEntryProcessor<>()); + assertThat(contextInjected).isEqualTo(true); + }); + } + + @Test + void defaultConfigFile_injectManagedContext_SpringHazelcastModuleNotAvailable() { + this.contextRunner + .withClassLoader(new FilteredClassLoader(SpringManagedContext.class)) + .run((context) -> { + HazelcastInstance hz = context.getBean(HazelcastInstance.class); + IMap map = hz.getMap("myMap"); + boolean contextInjected = map.executeOnKey(42, new SpringAwareEntryProcessor<>()); + assertThat(contextInjected).isEqualTo(false); + }); + } + + @Test + void explicitConfigFile_injectManagedContext() { + this.contextRunner + .withSystemProperties(HazelcastServerConfiguration.CONFIG_SYSTEM_PROPERTY + + "=classpath:org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.yaml") + .run((context) -> { + HazelcastInstance hz = context.getBean(HazelcastInstance.class); + IMap map = hz.getMap("myMap"); + boolean contextInjected = map.executeOnKey(42, new SpringAwareEntryProcessor<>()); + assertThat(contextInjected).isEqualTo(true); + }); + } + + @Test + void explicitConfigFile_injectManagedContext_SpringHazelcastModuleNotAvailable() { + this.contextRunner + .withClassLoader(new FilteredClassLoader(SpringManagedContext.class)) + .withSystemProperties(HazelcastServerConfiguration.CONFIG_SYSTEM_PROPERTY + + "=classpath:org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.yaml") + .run((context) -> { + HazelcastInstance hz = context.getBean(HazelcastInstance.class); + IMap map = hz.getMap("myMap"); + boolean contextInjected = map.executeOnKey(42, new SpringAwareEntryProcessor<>()); + assertThat(contextInjected).isEqualTo(false); + }); + } + @Configuration(proxyBeanMethods = false) static class HazelcastConfigWithName { @@ -174,6 +230,17 @@ class HazelcastAutoConfigurationServerTests { } + @SpringAware + static class SpringAwareEntryProcessor implements EntryProcessor { + @Autowired + private ApplicationContext context; + + @Override + public Boolean process(Map.Entry entry) { + return context != null; + } + } + @Configuration(proxyBeanMethods = false) static class HazelcastConfigNoName { From e1a5be83d2bb03215b468c10dc985ba50faf92f6 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 4 Jan 2022 09:14:22 +0100 Subject: [PATCH 2/2] Polish "Inject SpringManagedContext into Hazelcast configuration" See gh-28801 --- .../hazelcast/HazelcastConfigCustomizer.java | 38 ++++++++ .../HazelcastServerConfiguration.java | 36 +++---- ...HazelcastAutoConfigurationServerTests.java | 93 +++++++++---------- .../src/docs/asciidoc/io/hazelcast.adoc | 3 + 4 files changed, 98 insertions(+), 72 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigCustomizer.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigCustomizer.java new file mode 100644 index 0000000000..f6cebb79e4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastConfigCustomizer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2022 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.boot.autoconfigure.hazelcast; + +import com.hazelcast.config.Config; + +/** + * Callback interface that can be implemented by beans wishing to customize the Hazelcast + * server {@link Config configuration}. + * + * @author Jaromir Hamala + * @author Stephane Nicoll + * @since 2.7.0 + */ +@FunctionalInterface +public interface HazelcastConfigCustomizer { + + /** + * Customize the configuration. + * @param config the {@link Config} to customize + */ + void customize(Config config); + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastServerConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastServerConfiguration.java index 98ce41e108..b72cd2471d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastServerConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastServerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -24,8 +24,8 @@ import com.hazelcast.config.XmlConfigBuilder; import com.hazelcast.config.YamlConfigBuilder; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; - import com.hazelcast.spring.context.SpringManagedContext; + import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -34,6 +34,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.util.ResourceUtils; @@ -65,14 +66,11 @@ class HazelcastServerConfiguration { @Bean HazelcastInstance hazelcastInstance(HazelcastProperties properties, ResourceLoader resourceLoader, - ObjectProvider customizerProvider) - throws IOException { + ObjectProvider hazelcastConfigCustomizers) throws IOException { Resource configLocation = properties.resolveConfigLocation(); Config config = (configLocation != null) ? loadConfig(configLocation) : Config.load(); - customizerProvider.ifAvailable(c -> { - c.customize(config); - }); config.setClassLoader(resourceLoader.getClassLoader()); + hazelcastConfigCustomizers.orderedStream().forEach((customizer) -> customizer.customize(config)); return getHazelcastInstance(config); } @@ -101,6 +99,7 @@ class HazelcastServerConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnSingleCandidate(Config.class) static class HazelcastServerConfigConfiguration { + @Bean HazelcastInstance hazelcastInstance(Config config) { return getHazelcastInstance(config); @@ -108,27 +107,16 @@ class HazelcastServerConfiguration { } - @FunctionalInterface - private interface ConfigurationCustomizer { - void customize(Config configuration); - } - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(name = "com.hazelcast.spring.context.SpringManagedContext") - static class HazelcastConfigCustomizerConfiguration { - private final ApplicationContext applicationContext; - - public HazelcastConfigCustomizerConfiguration(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } + @ConditionalOnClass(SpringManagedContext.class) + static class SpringManagedContextHazelcastConfigCustomizerConfiguration { @Bean - public ConfigurationCustomizer springManagedContextConfigurationCustomizer() { - return configuration -> { - SpringManagedContext springManagedContext = new SpringManagedContext(applicationContext); - configuration.setManagedContext(springManagedContext); - }; + @Order(0) + HazelcastConfigCustomizer springManagedContextHazelcastConfigCustomizer(ApplicationContext applicationContext) { + return (config) -> config.setManagedContext(new SpringManagedContext(applicationContext)); } + } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationServerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationServerTests.java index 192837198c..a6ea4451b1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationServerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationServerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -26,6 +26,7 @@ import com.hazelcast.map.EntryProcessor; import com.hazelcast.map.IMap; import com.hazelcast.spring.context.SpringAware; import com.hazelcast.spring.context.SpringManagedContext; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.BeanCreationException; @@ -36,9 +37,9 @@ import org.springframework.boot.test.context.assertj.AssertableApplicationContex import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; import org.springframework.core.io.ClassPathResource; import static org.assertj.core.api.Assertions.assertThat; @@ -172,52 +173,36 @@ class HazelcastAutoConfigurationServerTests { } @Test - void defaultConfigFile_injectManagedContext() { + void autoConfiguredConfigUsesSpringManagedContext() { this.contextRunner.run((context) -> { - HazelcastInstance hz = context.getBean(HazelcastInstance.class); - IMap map = hz.getMap("myMap"); - boolean contextInjected = map.executeOnKey(42, new SpringAwareEntryProcessor<>()); - assertThat(contextInjected).isEqualTo(true); + Config config = context.getBean(HazelcastInstance.class).getConfig(); + assertThat(config.getManagedContext()).isInstanceOf(SpringManagedContext.class); }); } @Test - void defaultConfigFile_injectManagedContext_SpringHazelcastModuleNotAvailable() { - this.contextRunner - .withClassLoader(new FilteredClassLoader(SpringManagedContext.class)) - .run((context) -> { - HazelcastInstance hz = context.getBean(HazelcastInstance.class); - IMap map = hz.getMap("myMap"); - boolean contextInjected = map.executeOnKey(42, new SpringAwareEntryProcessor<>()); - assertThat(contextInjected).isEqualTo(false); - }); + void autoConfiguredConfigCanUseSpringAwareComponent() { + this.contextRunner.withPropertyValues("test.hazelcast.key=42").run((context) -> { + HazelcastInstance hz = context.getBean(HazelcastInstance.class); + IMap map = hz.getMap("test"); + assertThat(map.executeOnKey("test.hazelcast.key", new SpringAwareEntryProcessor<>())).isEqualTo("42"); + }); } @Test - void explicitConfigFile_injectManagedContext() { - this.contextRunner - .withSystemProperties(HazelcastServerConfiguration.CONFIG_SYSTEM_PROPERTY - + "=classpath:org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.yaml") - .run((context) -> { - HazelcastInstance hz = context.getBean(HazelcastInstance.class); - IMap map = hz.getMap("myMap"); - boolean contextInjected = map.executeOnKey(42, new SpringAwareEntryProcessor<>()); - assertThat(contextInjected).isEqualTo(true); - }); + void autoConfiguredConfigWithoutHazelcastSpringDoesNotUseSpringManagedContext() { + this.contextRunner.withClassLoader(new FilteredClassLoader(SpringManagedContext.class)).run((context) -> { + Config config = context.getBean(HazelcastInstance.class).getConfig(); + assertThat(config.getManagedContext()).isNull(); + }); } @Test - void explicitConfigFile_injectManagedContext_SpringHazelcastModuleNotAvailable() { - this.contextRunner - .withClassLoader(new FilteredClassLoader(SpringManagedContext.class)) - .withSystemProperties(HazelcastServerConfiguration.CONFIG_SYSTEM_PROPERTY - + "=classpath:org/springframework/boot/autoconfigure/hazelcast/hazelcast-specific.yaml") - .run((context) -> { - HazelcastInstance hz = context.getBean(HazelcastInstance.class); - IMap map = hz.getMap("myMap"); - boolean contextInjected = map.executeOnKey(42, new SpringAwareEntryProcessor<>()); - assertThat(contextInjected).isEqualTo(false); - }); + void autoConfiguredContextCanOverrideManagementContextUsingCustomizer() { + this.contextRunner.withBean(TestHazelcastConfigCustomizer.class).run((context) -> { + Config config = context.getBean(HazelcastInstance.class).getConfig(); + assertThat(config.getManagedContext()).isNull(); + }); } @Configuration(proxyBeanMethods = false) @@ -230,17 +215,6 @@ class HazelcastAutoConfigurationServerTests { } - @SpringAware - static class SpringAwareEntryProcessor implements EntryProcessor { - @Autowired - private ApplicationContext context; - - @Override - public Boolean process(Map.Entry entry) { - return context != null; - } - } - @Configuration(proxyBeanMethods = false) static class HazelcastConfigNoName { @@ -253,4 +227,27 @@ class HazelcastAutoConfigurationServerTests { } + @SpringAware + static class SpringAwareEntryProcessor implements EntryProcessor { + + @Autowired + private Environment environment; + + @Override + public String process(Map.Entry entry) { + return this.environment.getProperty(entry.getKey()); + } + + } + + @Order(1) + static class TestHazelcastConfigCustomizer implements HazelcastConfigCustomizer { + + @Override + public void customize(Config config) { + config.setManagedContext(null); + } + + } + } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/hazelcast.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/hazelcast.adoc index 3969f9cdd3..8df03a5862 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/hazelcast.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/io/hazelcast.adoc @@ -30,5 +30,8 @@ Otherwise, Spring Boot tries to find the Hazelcast configuration from the defaul We also check if the `hazelcast.config` system property is set. See the https://docs.hazelcast.org/docs/latest/manual/html-single/[Hazelcast documentation] for more details. +TIP: By default, `@SpringAware` on Hazelcast components is supported. +The `ManagementContext` can be overridden by declaring a `HazelcastConfigCustomizer` bean with an `@Order` higher than zero. + NOTE: Spring Boot also has <>. If caching is enabled, the `HazelcastInstance` is automatically wrapped in a `CacheManager` implementation.