Skip to content

Commit 00855c4

Browse files
yrodierejhoeller
authored andcommitted
Add tests for SpringBeanContainer (Hibernate ORM integration) and fix the behavior when requesting named beans (#22260)
* Add integration tests for SpringBeanContainer (Hibernate ORM integration) * Autowire bean properties of beans retrieved by name in SpringBeanContainer * Add integration tests for fallback cases in SpringBeanContainer (Hibernate ORM integration) * Fix SpringBeanContainer incorrectly losing the bean name when calling the fallback producer
1 parent 4c9ae64 commit 00855c4

File tree

8 files changed

+516
-5
lines changed

8 files changed

+516
-5
lines changed

spring-orm/src/main/java/org/springframework/orm/hibernate5/SpringBeanContainer.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,6 @@ public <B> ContainedBean<B> getBean(
115115
@SuppressWarnings("unchecked")
116116
public <B> ContainedBean<B> getBean(
117117
String name, Class<B> beanType, LifecycleOptions lifecycleOptions, BeanInstanceProducer fallbackProducer) {
118-
119-
if (!this.beanFactory.containsBean(name)) {
120-
return getBean(beanType, lifecycleOptions, fallbackProducer);
121-
}
122-
123118
SpringContainedBean<?> bean;
124119
if (lifecycleOptions.canUseCachedReferences()) {
125120
bean = this.beanCache.get(name);
@@ -169,6 +164,7 @@ private SpringContainedBean<?> createBean(
169164
try {
170165
if (lifecycleOptions.useJpaCompliantCreation()) {
171166
Object bean = this.beanFactory.autowire(beanType, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
167+
this.beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
172168
this.beanFactory.applyBeanPropertyValues(bean, name);
173169
bean = this.beanFactory.initializeBean(bean, name);
174170
return new SpringContainedBean<>(bean, beanInstance -> this.beanFactory.destroyBean(name, beanInstance));
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.orm.jpa.hibernate;
18+
19+
import org.hibernate.SessionFactory;
20+
import org.hibernate.resource.beans.container.spi.BeanContainer;
21+
import org.hibernate.resource.beans.container.spi.ContainedBean;
22+
import org.hibernate.resource.beans.spi.BeanInstanceProducer;
23+
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
24+
import org.hibernate.service.ServiceRegistry;
25+
26+
import org.junit.Test;
27+
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.context.ApplicationContext;
30+
import org.springframework.orm.jpa.AbstractEntityManagerFactoryIntegrationTests;
31+
import org.springframework.orm.jpa.hibernate.beans.*;
32+
33+
import static org.junit.Assert.*;
34+
35+
/**
36+
* Hibernate-specific SpringBeanContainer integration tests.
37+
*
38+
* @author Yoann Rodiere
39+
*/
40+
public class HibernateNativeEntityManagerFactorySpringBeanContainerIntegrationTests
41+
extends AbstractEntityManagerFactoryIntegrationTests {
42+
43+
@Autowired
44+
private ApplicationContext applicationContext;
45+
46+
@Override
47+
protected String[] getConfigLocations() {
48+
return new String[] {"/org/springframework/orm/jpa/hibernate/hibernate-manager-native.xml",
49+
"/org/springframework/orm/jpa/memdb.xml", "/org/springframework/orm/jpa/inject.xml",
50+
"/org/springframework/orm/jpa/hibernate/inject-hibernate-spring-bean-container-tests.xml"};
51+
}
52+
53+
private ManagedBeanRegistry getManagedBeanRegistry() {
54+
SessionFactory sessionFactory = entityManagerFactory.unwrap( SessionFactory.class );
55+
ServiceRegistry serviceRegistry = sessionFactory.getSessionFactoryOptions().getServiceRegistry();
56+
return serviceRegistry.requireService( ManagedBeanRegistry.class );
57+
}
58+
59+
private BeanContainer getBeanContainer() {
60+
return getManagedBeanRegistry().getBeanContainer();
61+
}
62+
63+
64+
@Test
65+
public void testCanRetrieveBeanByTypeWithJpaCompliantOptions() {
66+
BeanContainer beanContainer = getBeanContainer();
67+
assertNotNull(beanContainer);
68+
69+
ContainedBean<SinglePrototypeInSpringContextTestBean> bean = beanContainer.getBean(
70+
SinglePrototypeInSpringContextTestBean.class,
71+
JpaLifecycleOptions.INSTANCE,
72+
IneffectiveBeanInstanceProducer.INSTANCE
73+
);
74+
75+
assertNotNull(bean);
76+
SinglePrototypeInSpringContextTestBean instance = bean.getBeanInstance();
77+
assertNotNull(instance);
78+
assertSame(applicationContext, instance.getApplicationContext());
79+
}
80+
81+
@Test
82+
public void testCanRetrieveBeanByNameWithJpaCompliantOptions() {
83+
BeanContainer beanContainer = getBeanContainer();
84+
assertNotNull(beanContainer);
85+
86+
ContainedBean<MultiplePrototypesInSpringContextTestBean> bean = beanContainer.getBean(
87+
"multiple-1", MultiplePrototypesInSpringContextTestBean.class,
88+
JpaLifecycleOptions.INSTANCE,
89+
IneffectiveBeanInstanceProducer.INSTANCE
90+
);
91+
92+
assertNotNull(bean);
93+
MultiplePrototypesInSpringContextTestBean instance = bean.getBeanInstance();
94+
assertNotNull(instance);
95+
assertEquals("multiple-1", instance.getName());
96+
assertSame(applicationContext, instance.getApplicationContext());
97+
}
98+
99+
@Test
100+
public void testCanRetrieveBeanByTypeWithNativeOptions() {
101+
BeanContainer beanContainer = getBeanContainer();
102+
assertNotNull(beanContainer);
103+
104+
ContainedBean<SinglePrototypeInSpringContextTestBean> bean = beanContainer.getBean(
105+
SinglePrototypeInSpringContextTestBean.class,
106+
NativeLifecycleOptions.INSTANCE,
107+
IneffectiveBeanInstanceProducer.INSTANCE
108+
);
109+
110+
assertNotNull(bean);
111+
SinglePrototypeInSpringContextTestBean instance = bean.getBeanInstance();
112+
assertNotNull(instance);
113+
assertEquals("single", instance.getName());
114+
assertSame(applicationContext, instance.getApplicationContext());
115+
116+
ContainedBean<SinglePrototypeInSpringContextTestBean> bean2 = beanContainer.getBean(
117+
SinglePrototypeInSpringContextTestBean.class,
118+
NativeLifecycleOptions.INSTANCE,
119+
IneffectiveBeanInstanceProducer.INSTANCE
120+
);
121+
122+
assertNotNull(bean2);
123+
SinglePrototypeInSpringContextTestBean instance2 = bean2.getBeanInstance();
124+
assertNotNull(instance2);
125+
// Due to the lifecycle options, and because the bean has the "prototype" scope, we should not return the same instance
126+
assertNotSame(instance, instance2);
127+
}
128+
129+
@Test
130+
public void testCanRetrieveBeanByNameWithNativeOptions() {
131+
BeanContainer beanContainer = getBeanContainer();
132+
assertNotNull(beanContainer);
133+
134+
ContainedBean<MultiplePrototypesInSpringContextTestBean> bean = beanContainer.getBean(
135+
"multiple-1", MultiplePrototypesInSpringContextTestBean.class,
136+
NativeLifecycleOptions.INSTANCE,
137+
IneffectiveBeanInstanceProducer.INSTANCE
138+
);
139+
140+
assertNotNull(bean);
141+
MultiplePrototypesInSpringContextTestBean instance = bean.getBeanInstance();
142+
assertNotNull(instance);
143+
assertEquals("multiple-1", instance.getName());
144+
assertSame(applicationContext, instance.getApplicationContext());
145+
146+
ContainedBean<MultiplePrototypesInSpringContextTestBean> bean2 = beanContainer.getBean(
147+
"multiple-1", MultiplePrototypesInSpringContextTestBean.class,
148+
NativeLifecycleOptions.INSTANCE,
149+
IneffectiveBeanInstanceProducer.INSTANCE
150+
);
151+
152+
assertNotNull(bean2);
153+
MultiplePrototypesInSpringContextTestBean instance2 = bean2.getBeanInstance();
154+
assertNotNull(instance2);
155+
// Due to the lifecycle options, and because the bean has the "prototype" scope, we should not return the same instance
156+
assertNotSame(instance, instance2);
157+
}
158+
159+
@Test
160+
public void testCanRetrieveFallbackBeanByTypeWithJpaCompliantOptions() {
161+
BeanContainer beanContainer = getBeanContainer();
162+
assertNotNull(beanContainer);
163+
NoDefinitionInSpringContextTestBeanInstanceProducer fallbackProducer = new NoDefinitionInSpringContextTestBeanInstanceProducer();
164+
165+
ContainedBean<NoDefinitionInSpringContextTestBean> bean = beanContainer.getBean(
166+
NoDefinitionInSpringContextTestBean.class,
167+
JpaLifecycleOptions.INSTANCE,
168+
fallbackProducer
169+
);
170+
171+
assertEquals(1, fallbackProducer.currentUnnamedInstantiationCount());
172+
assertEquals(0, fallbackProducer.currentNamedInstantiationCount());
173+
174+
assertNotNull(bean);
175+
NoDefinitionInSpringContextTestBean instance = bean.getBeanInstance();
176+
assertNotNull(instance);
177+
assertEquals(BeanSource.FALLBACK, instance.getSource());
178+
assertNull(instance.getApplicationContext());
179+
}
180+
181+
@Test
182+
public void testCanRetrieveFallbackBeanByNameWithJpaCompliantOptions() {
183+
BeanContainer beanContainer = getBeanContainer();
184+
assertNotNull(beanContainer);
185+
NoDefinitionInSpringContextTestBeanInstanceProducer fallbackProducer = new NoDefinitionInSpringContextTestBeanInstanceProducer();
186+
187+
ContainedBean<NoDefinitionInSpringContextTestBean> bean = beanContainer.getBean(
188+
"some name", NoDefinitionInSpringContextTestBean.class,
189+
JpaLifecycleOptions.INSTANCE,
190+
fallbackProducer
191+
);
192+
193+
assertEquals(0, fallbackProducer.currentUnnamedInstantiationCount());
194+
assertEquals(1, fallbackProducer.currentNamedInstantiationCount());
195+
196+
assertNotNull(bean);
197+
NoDefinitionInSpringContextTestBean instance = bean.getBeanInstance();
198+
assertNotNull(instance);
199+
assertEquals(BeanSource.FALLBACK, instance.getSource());
200+
assertEquals("some name", instance.getName());
201+
assertNull(instance.getApplicationContext());
202+
}
203+
204+
@Test
205+
public void testCanRetrieveFallbackBeanByTypeWithNativeOptions() {
206+
BeanContainer beanContainer = getBeanContainer();
207+
assertNotNull(beanContainer);
208+
NoDefinitionInSpringContextTestBeanInstanceProducer fallbackProducer = new NoDefinitionInSpringContextTestBeanInstanceProducer();
209+
210+
ContainedBean<NoDefinitionInSpringContextTestBean> bean = beanContainer.getBean(
211+
NoDefinitionInSpringContextTestBean.class,
212+
NativeLifecycleOptions.INSTANCE,
213+
fallbackProducer
214+
);
215+
216+
assertEquals(1, fallbackProducer.currentUnnamedInstantiationCount());
217+
assertEquals(0, fallbackProducer.currentNamedInstantiationCount());
218+
219+
assertNotNull(bean);
220+
NoDefinitionInSpringContextTestBean instance = bean.getBeanInstance();
221+
assertNotNull(instance);
222+
assertEquals(BeanSource.FALLBACK, instance.getSource());
223+
assertNull(instance.getApplicationContext());
224+
}
225+
226+
@Test
227+
public void testCanRetrieveFallbackBeanByNameWithNativeOptions() {
228+
BeanContainer beanContainer = getBeanContainer();
229+
assertNotNull(beanContainer);
230+
NoDefinitionInSpringContextTestBeanInstanceProducer fallbackProducer = new NoDefinitionInSpringContextTestBeanInstanceProducer();
231+
232+
ContainedBean<NoDefinitionInSpringContextTestBean> bean = beanContainer.getBean(
233+
"some name", NoDefinitionInSpringContextTestBean.class,
234+
NativeLifecycleOptions.INSTANCE,
235+
fallbackProducer
236+
);
237+
238+
assertEquals(0, fallbackProducer.currentUnnamedInstantiationCount());
239+
assertEquals(1, fallbackProducer.currentNamedInstantiationCount());
240+
241+
assertNotNull(bean);
242+
NoDefinitionInSpringContextTestBean instance = bean.getBeanInstance();
243+
assertNotNull(instance);
244+
assertEquals(BeanSource.FALLBACK, instance.getSource());
245+
assertEquals("some name", instance.getName());
246+
assertNull(instance.getApplicationContext());
247+
}
248+
249+
250+
/**
251+
* The lifecycle options mandated by the JPA spec and used as a default in Hibernate ORM.
252+
*/
253+
private static class JpaLifecycleOptions implements BeanContainer.LifecycleOptions {
254+
public static final JpaLifecycleOptions INSTANCE = new JpaLifecycleOptions();
255+
256+
@Override
257+
public boolean canUseCachedReferences() {
258+
return true;
259+
}
260+
261+
@Override
262+
public boolean useJpaCompliantCreation() {
263+
return true;
264+
}
265+
}
266+
267+
/**
268+
* The lifecycle options used by libraries integrating into Hibernate ORM
269+
* and that want a behavior closer to Spring's native behavior,
270+
* such as Hibernate Search.
271+
*/
272+
private static class NativeLifecycleOptions implements BeanContainer.LifecycleOptions {
273+
public static final NativeLifecycleOptions INSTANCE = new NativeLifecycleOptions();
274+
275+
@Override
276+
public boolean canUseCachedReferences() {
277+
return false;
278+
}
279+
280+
@Override
281+
public boolean useJpaCompliantCreation() {
282+
return false;
283+
}
284+
}
285+
286+
private static class IneffectiveBeanInstanceProducer implements BeanInstanceProducer {
287+
public static final IneffectiveBeanInstanceProducer INSTANCE = new IneffectiveBeanInstanceProducer();
288+
289+
@Override
290+
public <B> B produceBeanInstance(Class<B> aClass) {
291+
throw new UnsupportedOperationException("should not be called");
292+
}
293+
294+
@Override
295+
public <B> B produceBeanInstance(String s, Class<B> aClass) {
296+
throw new UnsupportedOperationException("should not be called");
297+
}
298+
}
299+
300+
private static class NoDefinitionInSpringContextTestBeanInstanceProducer implements BeanInstanceProducer {
301+
private int unnamedInstantiationCount = 0;
302+
private int namedInstantiationCount = 0;
303+
304+
@Override
305+
public <B> B produceBeanInstance(Class<B> beanType) {
306+
try {
307+
++unnamedInstantiationCount;
308+
/*
309+
* We only expect to ever be asked to instantiate this class, so we just cut corners here.
310+
* A real-world implementation would obviously be different.
311+
*/
312+
NoDefinitionInSpringContextTestBean instance = new NoDefinitionInSpringContextTestBean(null, BeanSource.FALLBACK);
313+
return beanType.cast( instance );
314+
}
315+
catch (RuntimeException e) {
316+
throw new AssertionError( "Unexpected error instantiating a bean by type using reflection", e );
317+
}
318+
}
319+
320+
@Override
321+
public <B> B produceBeanInstance(String name, Class<B> beanType) {
322+
try {
323+
++namedInstantiationCount;
324+
/*
325+
* We only expect to ever be asked to instantiate this class, so we just cut corners here.
326+
* A real-world implementation would obviously be different.
327+
*/
328+
NoDefinitionInSpringContextTestBean instance = new NoDefinitionInSpringContextTestBean(name, BeanSource.FALLBACK);
329+
return beanType.cast( instance );
330+
}
331+
catch (RuntimeException e) {
332+
throw new AssertionError( "Unexpected error instantiating a bean by name using reflection", e );
333+
}
334+
}
335+
336+
private int currentUnnamedInstantiationCount() {
337+
return unnamedInstantiationCount;
338+
}
339+
340+
private int currentNamedInstantiationCount() {
341+
return namedInstantiationCount;
342+
}
343+
}
344+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.orm.jpa.hibernate.beans;
18+
19+
public enum BeanSource {
20+
SPRING,
21+
FALLBACK;
22+
}

0 commit comments

Comments
 (0)