diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java index 9280d38230..bba1c889ec 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -116,10 +116,6 @@ public final class SpringBeanContainer implements BeanContainer { public ContainedBean getBean( String name, Class beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) { - if (!this.beanFactory.containsBean(name)) { - return getBean(beanType, lifecycleOptions, fallbackProducer); - } - SpringContainedBean bean; if (lifecycleOptions.canUseCachedReferences()) { bean = this.beanCache.get(name); @@ -169,6 +165,7 @@ public final class SpringBeanContainer implements BeanContainer { try { if (lifecycleOptions.useJpaCompliantCreation()) { Object bean = this.beanFactory.autowire(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false); + this.beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false); this.beanFactory.applyBeanPropertyValues(bean, name); bean = this.beanFactory.initializeBean(bean, name); return new SpringContainedBean<>(bean, beanInstance -> this.beanFactory.destroyBean(name, beanInstance)); diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactorySpringBeanContainerIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactorySpringBeanContainerIntegrationTests.java new file mode 100644 index 0000000000..048da832a1 --- /dev/null +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactorySpringBeanContainerIntegrationTests.java @@ -0,0 +1,344 @@ +/* + * Copyright 2002-2019 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.orm.jpa.hibernate; + +import org.hibernate.SessionFactory; +import org.hibernate.resource.beans.container.spi.BeanContainer; +import org.hibernate.resource.beans.container.spi.ContainedBean; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; +import org.hibernate.service.ServiceRegistry; + +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.orm.jpa.AbstractEntityManagerFactoryIntegrationTests; +import org.springframework.orm.jpa.hibernate.beans.*; + +import static org.junit.Assert.*; + +/** + * Hibernate-specific SpringBeanContainer integration tests. + * + * @author Yoann Rodiere + */ +public class HibernateNativeEntityManagerFactorySpringBeanContainerIntegrationTests + extends AbstractEntityManagerFactoryIntegrationTests { + + @Autowired + private ApplicationContext applicationContext; + + @Override + protected String[] getConfigLocations() { + return new String[] {"/org/springframework/orm/jpa/hibernate/hibernate-manager-native.xml", + "/org/springframework/orm/jpa/memdb.xml", "/org/springframework/orm/jpa/inject.xml", + "/org/springframework/orm/jpa/hibernate/inject-hibernate-spring-bean-container-tests.xml"}; + } + + private ManagedBeanRegistry getManagedBeanRegistry() { + SessionFactory sessionFactory = entityManagerFactory.unwrap( SessionFactory.class ); + ServiceRegistry serviceRegistry = sessionFactory.getSessionFactoryOptions().getServiceRegistry(); + return serviceRegistry.requireService( ManagedBeanRegistry.class ); + } + + private BeanContainer getBeanContainer() { + return getManagedBeanRegistry().getBeanContainer(); + } + + + @Test + public void testCanRetrieveBeanByTypeWithJpaCompliantOptions() { + BeanContainer beanContainer = getBeanContainer(); + assertNotNull(beanContainer); + + ContainedBean bean = beanContainer.getBean( + SinglePrototypeInSpringContextTestBean.class, + JpaLifecycleOptions.INSTANCE, + IneffectiveBeanInstanceProducer.INSTANCE + ); + + assertNotNull(bean); + SinglePrototypeInSpringContextTestBean instance = bean.getBeanInstance(); + assertNotNull(instance); + assertSame(applicationContext, instance.getApplicationContext()); + } + + @Test + public void testCanRetrieveBeanByNameWithJpaCompliantOptions() { + BeanContainer beanContainer = getBeanContainer(); + assertNotNull(beanContainer); + + ContainedBean bean = beanContainer.getBean( + "multiple-1", MultiplePrototypesInSpringContextTestBean.class, + JpaLifecycleOptions.INSTANCE, + IneffectiveBeanInstanceProducer.INSTANCE + ); + + assertNotNull(bean); + MultiplePrototypesInSpringContextTestBean instance = bean.getBeanInstance(); + assertNotNull(instance); + assertEquals("multiple-1", instance.getName()); + assertSame(applicationContext, instance.getApplicationContext()); + } + + @Test + public void testCanRetrieveBeanByTypeWithNativeOptions() { + BeanContainer beanContainer = getBeanContainer(); + assertNotNull(beanContainer); + + ContainedBean bean = beanContainer.getBean( + SinglePrototypeInSpringContextTestBean.class, + NativeLifecycleOptions.INSTANCE, + IneffectiveBeanInstanceProducer.INSTANCE + ); + + assertNotNull(bean); + SinglePrototypeInSpringContextTestBean instance = bean.getBeanInstance(); + assertNotNull(instance); + assertEquals("single", instance.getName()); + assertSame(applicationContext, instance.getApplicationContext()); + + ContainedBean bean2 = beanContainer.getBean( + SinglePrototypeInSpringContextTestBean.class, + NativeLifecycleOptions.INSTANCE, + IneffectiveBeanInstanceProducer.INSTANCE + ); + + assertNotNull(bean2); + SinglePrototypeInSpringContextTestBean instance2 = bean2.getBeanInstance(); + assertNotNull(instance2); + // Due to the lifecycle options, and because the bean has the "prototype" scope, we should not return the same instance + assertNotSame(instance, instance2); + } + + @Test + public void testCanRetrieveBeanByNameWithNativeOptions() { + BeanContainer beanContainer = getBeanContainer(); + assertNotNull(beanContainer); + + ContainedBean bean = beanContainer.getBean( + "multiple-1", MultiplePrototypesInSpringContextTestBean.class, + NativeLifecycleOptions.INSTANCE, + IneffectiveBeanInstanceProducer.INSTANCE + ); + + assertNotNull(bean); + MultiplePrototypesInSpringContextTestBean instance = bean.getBeanInstance(); + assertNotNull(instance); + assertEquals("multiple-1", instance.getName()); + assertSame(applicationContext, instance.getApplicationContext()); + + ContainedBean bean2 = beanContainer.getBean( + "multiple-1", MultiplePrototypesInSpringContextTestBean.class, + NativeLifecycleOptions.INSTANCE, + IneffectiveBeanInstanceProducer.INSTANCE + ); + + assertNotNull(bean2); + MultiplePrototypesInSpringContextTestBean instance2 = bean2.getBeanInstance(); + assertNotNull(instance2); + // Due to the lifecycle options, and because the bean has the "prototype" scope, we should not return the same instance + assertNotSame(instance, instance2); + } + + @Test + public void testCanRetrieveFallbackBeanByTypeWithJpaCompliantOptions() { + BeanContainer beanContainer = getBeanContainer(); + assertNotNull(beanContainer); + NoDefinitionInSpringContextTestBeanInstanceProducer fallbackProducer = new NoDefinitionInSpringContextTestBeanInstanceProducer(); + + ContainedBean bean = beanContainer.getBean( + NoDefinitionInSpringContextTestBean.class, + JpaLifecycleOptions.INSTANCE, + fallbackProducer + ); + + assertEquals(1, fallbackProducer.currentUnnamedInstantiationCount()); + assertEquals(0, fallbackProducer.currentNamedInstantiationCount()); + + assertNotNull(bean); + NoDefinitionInSpringContextTestBean instance = bean.getBeanInstance(); + assertNotNull(instance); + assertEquals(BeanSource.FALLBACK, instance.getSource()); + assertNull(instance.getApplicationContext()); + } + + @Test + public void testCanRetrieveFallbackBeanByNameWithJpaCompliantOptions() { + BeanContainer beanContainer = getBeanContainer(); + assertNotNull(beanContainer); + NoDefinitionInSpringContextTestBeanInstanceProducer fallbackProducer = new NoDefinitionInSpringContextTestBeanInstanceProducer(); + + ContainedBean bean = beanContainer.getBean( + "some name", NoDefinitionInSpringContextTestBean.class, + JpaLifecycleOptions.INSTANCE, + fallbackProducer + ); + + assertEquals(0, fallbackProducer.currentUnnamedInstantiationCount()); + assertEquals(1, fallbackProducer.currentNamedInstantiationCount()); + + assertNotNull(bean); + NoDefinitionInSpringContextTestBean instance = bean.getBeanInstance(); + assertNotNull(instance); + assertEquals(BeanSource.FALLBACK, instance.getSource()); + assertEquals("some name", instance.getName()); + assertNull(instance.getApplicationContext()); + } + + @Test + public void testCanRetrieveFallbackBeanByTypeWithNativeOptions() { + BeanContainer beanContainer = getBeanContainer(); + assertNotNull(beanContainer); + NoDefinitionInSpringContextTestBeanInstanceProducer fallbackProducer = new NoDefinitionInSpringContextTestBeanInstanceProducer(); + + ContainedBean bean = beanContainer.getBean( + NoDefinitionInSpringContextTestBean.class, + NativeLifecycleOptions.INSTANCE, + fallbackProducer + ); + + assertEquals(1, fallbackProducer.currentUnnamedInstantiationCount()); + assertEquals(0, fallbackProducer.currentNamedInstantiationCount()); + + assertNotNull(bean); + NoDefinitionInSpringContextTestBean instance = bean.getBeanInstance(); + assertNotNull(instance); + assertEquals(BeanSource.FALLBACK, instance.getSource()); + assertNull(instance.getApplicationContext()); + } + + @Test + public void testCanRetrieveFallbackBeanByNameWithNativeOptions() { + BeanContainer beanContainer = getBeanContainer(); + assertNotNull(beanContainer); + NoDefinitionInSpringContextTestBeanInstanceProducer fallbackProducer = new NoDefinitionInSpringContextTestBeanInstanceProducer(); + + ContainedBean bean = beanContainer.getBean( + "some name", NoDefinitionInSpringContextTestBean.class, + NativeLifecycleOptions.INSTANCE, + fallbackProducer + ); + + assertEquals(0, fallbackProducer.currentUnnamedInstantiationCount()); + assertEquals(1, fallbackProducer.currentNamedInstantiationCount()); + + assertNotNull(bean); + NoDefinitionInSpringContextTestBean instance = bean.getBeanInstance(); + assertNotNull(instance); + assertEquals(BeanSource.FALLBACK, instance.getSource()); + assertEquals("some name", instance.getName()); + assertNull(instance.getApplicationContext()); + } + + + /** + * The lifecycle options mandated by the JPA spec and used as a default in Hibernate ORM. + */ + private static class JpaLifecycleOptions implements BeanContainer.LifecycleOptions { + public static final JpaLifecycleOptions INSTANCE = new JpaLifecycleOptions(); + + @Override + public boolean canUseCachedReferences() { + return true; + } + + @Override + public boolean useJpaCompliantCreation() { + return true; + } + } + + /** + * The lifecycle options used by libraries integrating into Hibernate ORM + * and that want a behavior closer to Spring's native behavior, + * such as Hibernate Search. + */ + private static class NativeLifecycleOptions implements BeanContainer.LifecycleOptions { + public static final NativeLifecycleOptions INSTANCE = new NativeLifecycleOptions(); + + @Override + public boolean canUseCachedReferences() { + return false; + } + + @Override + public boolean useJpaCompliantCreation() { + return false; + } + } + + private static class IneffectiveBeanInstanceProducer implements BeanInstanceProducer { + public static final IneffectiveBeanInstanceProducer INSTANCE = new IneffectiveBeanInstanceProducer(); + + @Override + public B produceBeanInstance(Class aClass) { + throw new UnsupportedOperationException("should not be called"); + } + + @Override + public B produceBeanInstance(String s, Class aClass) { + throw new UnsupportedOperationException("should not be called"); + } + } + + private static class NoDefinitionInSpringContextTestBeanInstanceProducer implements BeanInstanceProducer { + private int unnamedInstantiationCount = 0; + private int namedInstantiationCount = 0; + + @Override + public B produceBeanInstance(Class beanType) { + try { + ++unnamedInstantiationCount; + /* + * We only expect to ever be asked to instantiate this class, so we just cut corners here. + * A real-world implementation would obviously be different. + */ + NoDefinitionInSpringContextTestBean instance = new NoDefinitionInSpringContextTestBean(null, BeanSource.FALLBACK); + return beanType.cast( instance ); + } + catch (RuntimeException e) { + throw new AssertionError( "Unexpected error instantiating a bean by type using reflection", e ); + } + } + + @Override + public B produceBeanInstance(String name, Class beanType) { + try { + ++namedInstantiationCount; + /* + * We only expect to ever be asked to instantiate this class, so we just cut corners here. + * A real-world implementation would obviously be different. + */ + NoDefinitionInSpringContextTestBean instance = new NoDefinitionInSpringContextTestBean(name, BeanSource.FALLBACK); + return beanType.cast( instance ); + } + catch (RuntimeException e) { + throw new AssertionError( "Unexpected error instantiating a bean by name using reflection", e ); + } + } + + private int currentUnnamedInstantiationCount() { + return unnamedInstantiationCount; + } + + private int currentNamedInstantiationCount() { + return namedInstantiationCount; + } + } +} diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/BeanSource.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/BeanSource.java new file mode 100644 index 0000000000..e81fe908fb --- /dev/null +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/BeanSource.java @@ -0,0 +1,22 @@ +/* + * Copyright 2002-2019 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.orm.jpa.hibernate.beans; + +public enum BeanSource { + SPRING, + FALLBACK; +} diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/MultiplePrototypesInSpringContextTestBean.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/MultiplePrototypesInSpringContextTestBean.java new file mode 100644 index 0000000000..b936671aec --- /dev/null +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/MultiplePrototypesInSpringContextTestBean.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2019 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.orm.jpa.hibernate.beans; + +public class MultiplePrototypesInSpringContextTestBean extends TestBean { +} diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/NoDefinitionInSpringContextTestBean.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/NoDefinitionInSpringContextTestBean.java new file mode 100644 index 0000000000..d93b5832bd --- /dev/null +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/NoDefinitionInSpringContextTestBean.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2019 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.orm.jpa.hibernate.beans; + +public class NoDefinitionInSpringContextTestBean extends TestBean { + + private NoDefinitionInSpringContextTestBean() { + throw new AssertionError( + "Unexpected call to the default constructor." + + " Is Spring trying to instantiate this class by itself, even though it should delegate to the fallback producer?" + ); + } + + /* + * Expect instantiation through a non-default constructor, just to be sure that Spring will fail if it tries to instantiate it, + * and will subsequently delegate to the fallback bean instance producer. + */ + public NoDefinitionInSpringContextTestBean(String name, BeanSource source) { + setName(name); + setSource(source); + } + +} diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/SinglePrototypeInSpringContextTestBean.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/SinglePrototypeInSpringContextTestBean.java new file mode 100644 index 0000000000..f5ac1ab06b --- /dev/null +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/SinglePrototypeInSpringContextTestBean.java @@ -0,0 +1,20 @@ +/* + * Copyright 2002-2019 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.orm.jpa.hibernate.beans; + +public class SinglePrototypeInSpringContextTestBean extends TestBean { +} diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/TestBean.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/TestBean.java new file mode 100644 index 0000000000..5370734bf0 --- /dev/null +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/beans/TestBean.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2019 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.orm.jpa.hibernate.beans; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; + +public abstract class TestBean { + private BeanSource source; + + private String name; + + @Autowired + private ApplicationContext applicationContext; + + public BeanSource getSource() { + return source; + } + + public void setSource(BeanSource source) { + this.source = source; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ApplicationContext getApplicationContext() { + return applicationContext; + } +} diff --git a/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/inject-hibernate-spring-bean-container-tests.xml b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/inject-hibernate-spring-bean-container-tests.xml new file mode 100644 index 0000000000..3b056bb69b --- /dev/null +++ b/spring-orm/src/test/resources/org/springframework/orm/jpa/hibernate/inject-hibernate-spring-bean-container-tests.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + +