From 157c0b6caeffbe6daf06d909b1cf99c12adbed5d Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 6 Nov 2015 12:01:59 +0100 Subject: [PATCH] Register hazelcast post processor only when necessary Commit 8e0a94f introduced a post processor that adds an explicit link between the `HazelcastInstance` and the `EntityManagerFactory` so that Hazelcast is fully initialized before Hibernate actually kicks in. Unfortunately, the conditions that were implemented to register this post processor are wrong and any app that has both JPA and Hazelcast support blows up if no bean with name `hazelcastInstance` is defined. This commit fixes the situation and reworks the configuration in a separate auto-configuration that runs after the Hazelcast and JPA support and check both the presence of an `EntityManagerFactory` and a bean of name `hazelcastInstance`. If any of those conditions does not apply the post processor is no longer registered. Closes gh-4158 --- .../hazelcast/HazelcastAutoConfiguration.java | 16 --- ...zelcastJpaDependencyAutoConfiguration.java | 79 +++++++++++ .../main/resources/META-INF/spring.factories | 1 + ...stJpaDependencyAutoConfigurationTests.java | 128 ++++++++++++++++++ 4 files changed, 208 insertions(+), 16 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastJpaDependencyAutoConfiguration.java create mode 100644 spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastJpaDependencyAutoConfigurationTests.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfiguration.java index 531e9246b6..8b1c9e8e74 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastAutoConfiguration.java @@ -24,18 +24,14 @@ import com.hazelcast.core.HazelcastInstance; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; -import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor; import org.springframework.boot.context.properties.EnableConfigurationProperties; 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.orm.jpa.AbstractEntityManagerFactoryBean; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; /** * {@link EnableAutoConfiguration Auto-configuration} for Hazelcast. Creates a @@ -82,18 +78,6 @@ public class HazelcastAutoConfiguration { } - @Configuration - @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class) - @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) - protected static class HazelcastInstanceJpaDependencyConfiguration - extends EntityManagerFactoryDependsOnPostProcessor { - - public HazelcastInstanceJpaDependencyConfiguration() { - super("hazelcastInstance"); - } - - } - /** * {@link HazelcastConfigResourceCondition} that checks if the * {@code spring.hazelcast.config} configuration key is defined. diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastJpaDependencyAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastJpaDependencyAutoConfiguration.java new file mode 100644 index 0000000000..29251db18d --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastJpaDependencyAutoConfiguration.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2015 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 + * + * http://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 javax.persistence.EntityManagerFactory; + +import com.hazelcast.core.HazelcastInstance; + +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.AllNestedConditions; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; + +/** + * Additional configuration to ensure that {@link EntityManagerFactory} beans + * depend-on the {@code hazelcastInstance} bean. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +@Configuration +@ConditionalOnClass({ HazelcastInstance.class, LocalContainerEntityManagerFactoryBean.class }) +@AutoConfigureAfter({ HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) +class HazelcastJpaDependencyAutoConfiguration { + + @Bean + @Conditional(OnHazelcastAndJpaCondition.class) + public static HazelcastInstanceJpaDependencyPostProcessor hazelcastInstanceJpaDependencyPostProcessor() { + return new HazelcastInstanceJpaDependencyPostProcessor(); + } + + private static class HazelcastInstanceJpaDependencyPostProcessor + extends EntityManagerFactoryDependsOnPostProcessor { + + HazelcastInstanceJpaDependencyPostProcessor() { + super("hazelcastInstance"); + } + } + + static class OnHazelcastAndJpaCondition extends AllNestedConditions { + + OnHazelcastAndJpaCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnBean(name = "hazelcastInstance") + static class HasHazelcastInstance { + + } + + @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) + static class HasJpa { + + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index c3cd466d3b..cdfe4cabbe 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -36,6 +36,7 @@ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\ +org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastJpaDependencyAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastJpaDependencyAutoConfigurationTests.java new file mode 100644 index 0000000000..95c7837d5c --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hazelcast/HazelcastJpaDependencyAutoConfigurationTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2012-2015 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 + * + * http://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 java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.hazelcast.core.HazelcastInstance; +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasKey; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link HazelcastJpaDependencyAutoConfiguration}. + * + * @author Stephane Nicoll + */ +public class HazelcastJpaDependencyAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void closeContext() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void registrationIfHazelcastInstanceHasRegularBeanName() { + load(HazelcastConfiguration.class); + assertThat(this.context.getBeansOfType(EntityManagerFactoryDependsOnPostProcessor.class), + hasKey("hazelcastInstanceJpaDependencyPostProcessor")); + assertThat(getEntityManagerFactoryDependencies(), hasItem("hazelcastInstance")); + } + + @Test + public void noRegistrationIfHazelcastInstanceHasCustomBeanName() { + load(HazelcastCustomNameConfiguration.class); + assertThat(getEntityManagerFactoryDependencies(), not(hasItem("hazelcastInstance"))); + assertThat(this.context.getBeansOfType(EntityManagerFactoryDependsOnPostProcessor.class), + not(hasKey("hazelcastInstanceJpaDependencyPostProcessor"))); + } + + @Test + public void noRegistrationWithNoHazelcastInstance() { + load(null); + assertThat(getEntityManagerFactoryDependencies(), not(hasItem("hazelcastInstance"))); + assertThat(this.context.getBeansOfType(EntityManagerFactoryDependsOnPostProcessor.class), + not(hasKey("hazelcastInstanceJpaDependencyPostProcessor"))); + } + + @Test + public void noRegistrationWithNoEntityManagerFactory() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(HazelcastConfiguration.class, + HazelcastJpaDependencyAutoConfiguration.class); + this.context.refresh(); + assertThat(this.context.getBeansOfType(EntityManagerFactoryDependsOnPostProcessor.class), + not(hasKey("hazelcastInstanceJpaDependencyPostProcessor"))); + } + + private List getEntityManagerFactoryDependencies() { + String[] dependsOn = this.context.getBeanDefinition("entityManagerFactory").getDependsOn(); + return dependsOn != null ? Arrays.asList(dependsOn) : Collections.emptyList(); + } + + public void load(Class config) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + if (config != null) { + ctx.register(config); + } + ctx.register(EmbeddedDataSourceConfiguration.class, + DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class); + ctx.register(HazelcastJpaDependencyAutoConfiguration.class); + ctx.refresh(); + this.context = ctx; + } + + @Configuration + static class HazelcastConfiguration { + + @Bean + public HazelcastInstance hazelcastInstance() { + return mock(HazelcastInstance.class); + } + + } + + @Configuration + static class HazelcastCustomNameConfiguration { + + @Bean + public HazelcastInstance myHazelcastInstance() { + return mock(HazelcastInstance.class); + } + + } + +}