From 0bc03c7141cb055add162e5e33873e482fb1c058 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 11 Feb 2021 15:13:10 +0100 Subject: [PATCH] Associate application classloader to auto-configured Hazelcast instance Closes gh-24836 --- ...zelcast3HazelcastHealthIndicatorTests.java | 32 ++++++------- .../HazelcastHealthIndicatorTests.java | 32 ++++++------- .../HazelcastClientConfiguration.java | 35 +++++++++++--- .../hazelcast/HazelcastClientFactory.java | 4 +- .../hazelcast/HazelcastInstanceFactory.java | 4 +- .../HazelcastServerConfiguration.java | 47 ++++++++++++++++--- ...HazelcastAutoConfigurationClientTests.java | 14 +++++- ...HazelcastAutoConfigurationServerTests.java | 10 +++- 8 files changed, 124 insertions(+), 54 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/hazelcast/Hazelcast3HazelcastHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/hazelcast/Hazelcast3HazelcastHealthIndicatorTests.java index 5312e65f1d..6c9e902742 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/hazelcast/Hazelcast3HazelcastHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/hazelcast/Hazelcast3HazelcastHealthIndicatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -16,18 +16,17 @@ package org.springframework.boot.actuate.hazelcast; -import java.io.IOException; - import com.hazelcast.core.HazelcastException; import com.hazelcast.core.HazelcastInstance; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; -import org.springframework.boot.autoconfigure.hazelcast.HazelcastInstanceFactory; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.testsupport.classpath.ClassPathOverrides; -import org.springframework.core.io.ClassPathResource; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -45,19 +44,16 @@ import static org.mockito.Mockito.mock; class Hazelcast3HazelcastHealthIndicatorTests { @Test - void hazelcastUp() throws IOException { - HazelcastInstance hazelcast = new HazelcastInstanceFactory(new ClassPathResource("hazelcast-3.xml")) - .getHazelcastInstance(); - try { - Health health = new HazelcastHealthIndicator(hazelcast).health(); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails()).containsOnlyKeys("name", "uuid").containsEntry("name", - "actuator-hazelcast-3"); - assertThat(health.getDetails().get("uuid")).asString().isNotEmpty(); - } - finally { - hazelcast.shutdown(); - } + void hazelcastUp() { + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(HazelcastAutoConfiguration.class)) + .withPropertyValues("spring.hazelcast.config=hazelcast-3.xml").run((context) -> { + HazelcastInstance hazelcast = context.getBean(HazelcastInstance.class); + Health health = new HazelcastHealthIndicator(hazelcast).health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsOnlyKeys("name", "uuid").containsEntry("name", + "actuator-hazelcast-3"); + assertThat(health.getDetails().get("uuid")).asString().isNotEmpty(); + }); } @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicatorTests.java index ad91180052..053db98a3a 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicatorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -16,16 +16,15 @@ package org.springframework.boot.actuate.hazelcast; -import java.io.IOException; - import com.hazelcast.core.HazelcastException; import com.hazelcast.core.HazelcastInstance; import org.junit.jupiter.api.Test; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; -import org.springframework.boot.autoconfigure.hazelcast.HazelcastInstanceFactory; -import org.springframework.core.io.ClassPathResource; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -41,19 +40,16 @@ import static org.mockito.Mockito.mock; class HazelcastHealthIndicatorTests { @Test - void hazelcastUp() throws IOException { - HazelcastInstance hazelcast = new HazelcastInstanceFactory(new ClassPathResource("hazelcast.xml")) - .getHazelcastInstance(); - try { - Health health = new HazelcastHealthIndicator(hazelcast).health(); - assertThat(health.getStatus()).isEqualTo(Status.UP); - assertThat(health.getDetails()).containsOnlyKeys("name", "uuid").containsEntry("name", - "actuator-hazelcast"); - assertThat(health.getDetails().get("uuid")).asString().isNotEmpty(); - } - finally { - hazelcast.shutdown(); - } + void hazelcastUp() { + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(HazelcastAutoConfiguration.class)) + .withPropertyValues("spring.hazelcast.config=hazelcast.xml").run((context) -> { + HazelcastInstance hazelcast = context.getBean(HazelcastInstance.class); + Health health = new HazelcastHealthIndicator(hazelcast).health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsOnlyKeys("name", "uuid").containsEntry("name", + "actuator-hazelcast"); + assertThat(health.getDetails().get("uuid")).asString().isNotEmpty(); + }); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java index 2e02677636..897085d6df 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -17,9 +17,12 @@ package org.springframework.boot.autoconfigure.hazelcast; import java.io.IOException; +import java.net.URL; import com.hazelcast.client.HazelcastClient; import com.hazelcast.client.config.ClientConfig; +import com.hazelcast.client.config.XmlClientConfigBuilder; +import com.hazelcast.client.config.YamlClientConfigBuilder; import com.hazelcast.core.HazelcastInstance; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -29,6 +32,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.StringUtils; /** * Configuration for Hazelcast client. @@ -43,18 +48,34 @@ class HazelcastClientConfiguration { static final String CONFIG_SYSTEM_PROPERTY = "hazelcast.client.config"; + private static HazelcastInstance getHazelcastInstance(ClientConfig config) { + if (StringUtils.hasText(config.getInstanceName())) { + return HazelcastClient.getOrCreateHazelcastClient(config); + } + return HazelcastClient.newHazelcastClient(config); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(ClientConfig.class) @Conditional(HazelcastClientConfigAvailableCondition.class) static class HazelcastClientConfigFileConfiguration { @Bean - HazelcastInstance hazelcastInstance(HazelcastProperties properties) throws IOException { - Resource config = properties.resolveConfigLocation(); - if (config != null) { - return new HazelcastClientFactory(config).getHazelcastInstance(); + HazelcastInstance hazelcastInstance(HazelcastProperties properties, ResourceLoader resourceLoader) + throws IOException { + Resource configLocation = properties.resolveConfigLocation(); + ClientConfig config = (configLocation != null) ? loadClientConfig(configLocation) : ClientConfig.load(); + config.setClassLoader(resourceLoader.getClassLoader()); + return getHazelcastInstance(config); + } + + private ClientConfig loadClientConfig(Resource configLocation) throws IOException { + URL configUrl = configLocation.getURL(); + String configFileName = configUrl.getPath(); + if (configFileName.endsWith(".yaml")) { + return new YamlClientConfigBuilder(configUrl).build(); } - return HazelcastClient.newHazelcastClient(); + return new XmlClientConfigBuilder(configUrl).build(); } } @@ -65,7 +86,7 @@ class HazelcastClientConfiguration { @Bean HazelcastInstance hazelcastInstance(ClientConfig config) { - return new HazelcastClientFactory(config).getHazelcastInstance(); + return getHazelcastInstance(config); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientFactory.java index b1c9753514..55b40d2758 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientFactory.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastClientFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -34,7 +34,9 @@ import org.springframework.util.StringUtils; * * @author Vedran Pavic * @since 2.0.0 + * @deprecated since 2.3.4 in favor of using the Hazelcast API directly */ +@Deprecated public class HazelcastClientFactory { private final ClientConfig clientConfig; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastInstanceFactory.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastInstanceFactory.java index be66d2dffb..82380751f1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastInstanceFactory.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastInstanceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -36,7 +36,9 @@ import org.springframework.util.StringUtils; * @author Stephane Nicoll * @author Phillip Webb * @since 1.3.0 + * @deprecated since 2.3.4 in favor of using the Hazelcast API directly */ +@Deprecated public class HazelcastInstanceFactory { private final 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 0a552bb19d..63fe94612e 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-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -17,8 +17,11 @@ package org.springframework.boot.autoconfigure.hazelcast; import java.io.IOException; +import java.net.URL; import com.hazelcast.config.Config; +import com.hazelcast.config.XmlConfigBuilder; +import com.hazelcast.config.YamlConfigBuilder; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; @@ -28,6 +31,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; /** * Configuration for Hazelcast server. @@ -41,18 +47,45 @@ class HazelcastServerConfiguration { static final String CONFIG_SYSTEM_PROPERTY = "hazelcast.config"; + private static HazelcastInstance getHazelcastInstance(Config config) { + if (StringUtils.hasText(config.getInstanceName())) { + return Hazelcast.getOrCreateHazelcastInstance(config); + } + return Hazelcast.newHazelcastInstance(config); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(Config.class) @Conditional(ConfigAvailableCondition.class) static class HazelcastServerConfigFileConfiguration { @Bean - HazelcastInstance hazelcastInstance(HazelcastProperties properties) throws IOException { - Resource config = properties.resolveConfigLocation(); - if (config != null) { - return new HazelcastInstanceFactory(config).getHazelcastInstance(); + HazelcastInstance hazelcastInstance(HazelcastProperties properties, ResourceLoader resourceLoader) + throws IOException { + Resource configLocation = properties.resolveConfigLocation(); + Config config = (configLocation != null) ? loadConfig(configLocation) : Config.load(); + config.setClassLoader(resourceLoader.getClassLoader()); + return getHazelcastInstance(config); + } + + private Config loadConfig(Resource configLocation) throws IOException { + URL configUrl = configLocation.getURL(); + Config config = loadConfig(configUrl); + if (ResourceUtils.isFileURL(configUrl)) { + config.setConfigurationFile(configLocation.getFile()); + } + else { + config.setConfigurationUrl(configUrl); + } + return config; + } + + private static Config loadConfig(URL configUrl) throws IOException { + String configFileName = configUrl.getPath(); + if (configFileName.endsWith(".yaml")) { + return new YamlConfigBuilder(configUrl).build(); } - return Hazelcast.newHazelcastInstance(); + return new XmlConfigBuilder(configUrl).build(); } } @@ -63,7 +96,7 @@ class HazelcastServerConfiguration { @Bean HazelcastInstance hazelcastInstance(Config config) { - return new HazelcastInstanceFactory(config).getHazelcastInstance(); + return getHazelcastInstance(config); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java index 2369424d77..27d98497a3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfigurationClientTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -135,6 +135,18 @@ class HazelcastAutoConfigurationClientTests { .extracting(HazelcastInstance::getName).isEqualTo("spring-boot")); } + @Test + void autoConfiguredClientConfigUsesApplicationClassLoader() { + this.contextRunner.withPropertyValues("spring.hazelcast.config=org/springframework/boot/autoconfigure/" + + "hazelcast/hazelcast-client-specific.xml").run((context) -> { + HazelcastInstance hazelcast = context.getBean(HazelcastInstance.class); + assertThat(hazelcast).isInstanceOf(HazelcastClientProxy.class); + ClientConfig clientConfig = ((HazelcastClientProxy) hazelcast).getClientConfig(); + assertThat(clientConfig.getClassLoader()) + .isSameAs(context.getSourceApplicationContext().getClassLoader()); + }); + } + private ContextConsumer assertSpecificHazelcastClient(String label) { return (context) -> assertThat(context).getBean(HazelcastInstance.class).isInstanceOf(HazelcastInstance.class) .has(labelEqualTo(label)); 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 25e8c4271d..3856dc9941 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-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -156,6 +156,14 @@ class HazelcastAutoConfigurationServerTests { }); } + @Test + void autoConfiguredConfigUsesApplicationClassLoader() { + this.contextRunner.run((context) -> { + Config config = context.getBean(HazelcastInstance.class).getConfig(); + assertThat(config.getClassLoader()).isSameAs(context.getSourceApplicationContext().getClassLoader()); + }); + } + @Configuration(proxyBeanMethods = false) static class HazelcastConfigWithName {