1 /*
2  * Copyright 2002-2009 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.context.annotation.configuration;
18 
19 import java.lang.annotation.ElementType;
20 import java.lang.annotation.Retention;
21 import java.lang.annotation.RetentionPolicy;
22 import java.lang.annotation.Target;
23 import java.util.HashMap;
24 import java.util.Map;
25 
26 import org.junit.After;
27 import static org.junit.Assert.*;
28 import org.junit.Before;
29 import org.junit.Test;
30 import test.beans.ITestBean;
31 import test.beans.TestBean;
32 
33 import org.springframework.aop.scope.ScopedObject;
34 import org.springframework.beans.factory.ObjectFactory;
35 import org.springframework.beans.factory.support.DefaultListableBeanFactory;
36 import org.springframework.beans.factory.support.RootBeanDefinition;
37 import org.springframework.context.annotation.Bean;
38 import org.springframework.context.annotation.Configuration;
39 import org.springframework.context.annotation.ConfigurationClassPostProcessor;
40 import org.springframework.context.annotation.Scope;
41 import org.springframework.context.annotation.ScopedProxyMode;
42 import org.springframework.context.support.GenericApplicationContext;
43 
44 /**
45  * Tests that scopes are properly supported by using a custom Scope implementations
46  * and scoped proxy {@link Bean} declarations.
47  *
48  * @author Costin Leau
49  * @author Chris Beams
50  */
51 public class ScopingTests {
52 
53 	public static String flag = "1";
54 
55 	private static final String SCOPE = "my scope";
56 
57 	private CustomScope customScope;
58 
59 	private GenericApplicationContext ctx;
60 
61 	@Before
setUp()62 	public void setUp() throws Exception {
63 		customScope = new CustomScope();
64 		ctx = createContext(customScope, ScopedConfigurationClass.class);
65 	}
66 
67 	@After
tearDown()68 	public void tearDown() throws Exception {
69 		if (ctx != null) {
70 			ctx.close();
71 		}
72 		ctx = null;
73 		customScope = null;
74 	}
75 
createContext(org.springframework.beans.factory.config.Scope customScope, Class<?> configClass)76 	private GenericApplicationContext createContext(org.springframework.beans.factory.config.Scope customScope, Class<?> configClass) {
77 		DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
78 		if (customScope != null) {
79 			beanFactory.registerScope(SCOPE, customScope);
80 		}
81 		beanFactory.registerBeanDefinition("config", new RootBeanDefinition(configClass));
82 		GenericApplicationContext ctx = new GenericApplicationContext(beanFactory);
83 		ctx.addBeanFactoryPostProcessor(new ConfigurationClassPostProcessor());
84 		ctx.refresh();
85 		return ctx;
86 	}
87 
88 	@Test
testScopeOnClasses()89 	public void testScopeOnClasses() throws Exception {
90 		genericTestScope("scopedClass");
91 	}
92 
93 	@Test
testScopeOnInterfaces()94 	public void testScopeOnInterfaces() throws Exception {
95 		genericTestScope("scopedInterface");
96 	}
97 
98 	@Test
testSameScopeOnDifferentBeans()99 	public void testSameScopeOnDifferentBeans() throws Exception {
100 		Object beanAInScope = ctx.getBean("scopedClass");
101 		Object beanBInScope = ctx.getBean("scopedInterface");
102 
103 		assertNotSame(beanAInScope, beanBInScope);
104 
105 		customScope.createNewScope = true;
106 
107 		Object newBeanAInScope = ctx.getBean("scopedClass");
108 		Object newBeanBInScope = ctx.getBean("scopedInterface");
109 
110 		assertNotSame(newBeanAInScope, newBeanBInScope);
111 		assertNotSame(newBeanAInScope, beanAInScope);
112 		assertNotSame(newBeanBInScope, beanBInScope);
113 	}
114 
115 	@Test
testRawScopes()116 	public void testRawScopes() throws Exception {
117 		String beanName = "scopedProxyInterface";
118 
119 		// get hidden bean
120 		Object bean = ctx.getBean("scopedTarget." + beanName);
121 
122 		assertFalse(bean instanceof ScopedObject);
123 	}
124 
125 	@Test
testScopedProxyConfiguration()126 	public void testScopedProxyConfiguration() throws Exception {
127 		TestBean singleton = (TestBean) ctx.getBean("singletonWithScopedInterfaceDep");
128 		ITestBean spouse = singleton.getSpouse();
129 		assertTrue("scoped bean is not wrapped by the scoped-proxy", spouse instanceof ScopedObject);
130 
131 		String beanName = "scopedProxyInterface";
132 
133 		String scopedBeanName = "scopedTarget." + beanName;
134 
135 		// get hidden bean
136 		assertEquals(flag, spouse.getName());
137 
138 		ITestBean spouseFromBF = (ITestBean) ctx.getBean(scopedBeanName);
139 		assertEquals(spouse.getName(), spouseFromBF.getName());
140 		// the scope proxy has kicked in
141 		assertNotSame(spouse, spouseFromBF);
142 
143 		// create a new bean
144 		customScope.createNewScope = true;
145 
146 		// get the bean again from the BF
147 		spouseFromBF = (ITestBean) ctx.getBean(scopedBeanName);
148 		// make sure the name has been updated
149 		assertSame(spouse.getName(), spouseFromBF.getName());
150 		assertNotSame(spouse, spouseFromBF);
151 
152 		// get the bean again
153 		spouseFromBF = (ITestBean) ctx.getBean(scopedBeanName);
154 		assertSame(spouse.getName(), spouseFromBF.getName());
155 	}
156 
157 
158 	@Test
testScopedProxyConfigurationWithClasses()159 	public void testScopedProxyConfigurationWithClasses() throws Exception {
160 
161 		TestBean singleton = (TestBean) ctx.getBean("singletonWithScopedClassDep");
162 		ITestBean spouse = singleton.getSpouse();
163 		assertTrue("scoped bean is not wrapped by the scoped-proxy", spouse instanceof ScopedObject);
164 
165 		String beanName = "scopedProxyClass";
166 
167 		String scopedBeanName = "scopedTarget." + beanName;
168 
169 		// get hidden bean
170 		assertEquals(flag, spouse.getName());
171 
172 		TestBean spouseFromBF = (TestBean) ctx.getBean(scopedBeanName);
173 		assertEquals(spouse.getName(), spouseFromBF.getName());
174 		// the scope proxy has kicked in
175 		assertNotSame(spouse, spouseFromBF);
176 
177 		// create a new bean
178 		customScope.createNewScope = true;
179 		flag = "boo";
180 
181 		// get the bean again from the BF
182 		spouseFromBF = (TestBean) ctx.getBean(scopedBeanName);
183 		// make sure the name has been updated
184 		assertSame(spouse.getName(), spouseFromBF.getName());
185 		assertNotSame(spouse, spouseFromBF);
186 
187 		// get the bean again
188 		spouseFromBF = (TestBean) ctx.getBean(scopedBeanName);
189 		assertSame(spouse.getName(), spouseFromBF.getName());
190 	}
191 
192 
193 	@Test
testScopedConfigurationBeanDefinitionCount()194 	public void testScopedConfigurationBeanDefinitionCount() throws Exception {
195 		// count the beans
196 		// 6 @Beans + 1 Configuration + 2 scoped proxy + 1 importRegistry
197 		assertEquals(10, ctx.getBeanDefinitionCount());
198 	}
199 
200 //	/**
201 //	 * SJC-254 caught a regression in handling scoped proxies starting in 1.0 m4.
202 //	 * The ScopedProxyFactoryBean object was having its scope set to that of its delegate
203 //	 * whereas it should have remained singleton.
204 //	 */
205 //	@Test
206 //	public void sjc254() {
207 //		JavaConfigWebApplicationContext ctx = new JavaConfigWebApplicationContext();
208 //		ctx.setConfigLocations(new String[] { ScopeTestConfiguration.class.getName() });
209 //		ctx.refresh();
210 //
211 //		// should be fine
212 //		ctx.getBean(Bar.class);
213 //
214 //		boolean threw = false;
215 //		try {
216 //			ctx.getBean(Foo.class);
217 //		} catch (BeanCreationException ex) {
218 //			if(ex.getCause() instanceof IllegalStateException) {
219 //				threw = true;
220 //			}
221 //		}
222 //		assertTrue(threw);
223 //	}
224 
225 	@Configuration
226 	static class ScopeTestConfiguration {
227 
228 		@Bean
229 		@Scope(value="session", proxyMode=ScopedProxyMode.INTERFACES)
foo()230 		public Foo foo() {
231 			return new Foo();
232 		}
233 
234 		@Bean
bar()235 		public Bar bar() {
236 			return new Bar(foo());
237 		}
238 	}
239 
240 	static class Foo {
Foo()241 		public Foo() {
242 			//System.out.println("created foo: " + this.getClass().getName());
243 		}
244 
doSomething()245 		public void doSomething() {
246 			//System.out.println("interesting: " + this);
247 		}
248 	}
249 
250 	static class Bar {
251 
252 		private final Foo foo;
253 
Bar(Foo foo)254 		public Bar(Foo foo) {
255 			this.foo = foo;
256 			//System.out.println("created bar: " + this);
257 		}
258 
getFoo()259 		public Foo getFoo() {
260 			return foo;
261 		}
262 
263 	}
264 
genericTestScope(String beanName)265 	private void genericTestScope(String beanName) throws Exception {
266 		String message = "scope is ignored";
267 		Object bean1 = ctx.getBean(beanName);
268 		Object bean2 = ctx.getBean(beanName);
269 
270 		assertSame(message, bean1, bean2);
271 
272 		Object bean3 = ctx.getBean(beanName);
273 
274 		assertSame(message, bean1, bean3);
275 
276 		// make the scope create a new object
277 		customScope.createNewScope = true;
278 
279 		Object newBean1 = ctx.getBean(beanName);
280 		assertNotSame(message, bean1, newBean1);
281 
282 		Object sameBean1 = ctx.getBean(beanName);
283 
284 		assertSame(message, newBean1, sameBean1);
285 
286 		// make the scope create a new object
287 		customScope.createNewScope = true;
288 
289 		Object newBean2 = ctx.getBean(beanName);
290 		assertNotSame(message, newBean1, newBean2);
291 
292 		// make the scope create a new object .. again
293 		customScope.createNewScope = true;
294 
295 		Object newBean3 = ctx.getBean(beanName);
296 		assertNotSame(message, newBean2, newBean3);
297 	}
298 
299 	@Configuration
300 	public static class InvalidProxyOnPredefinedScopesConfiguration {
301 
302 		@Bean @Scope(proxyMode=ScopedProxyMode.INTERFACES)
invalidProxyOnPredefinedScopes()303 		public Object invalidProxyOnPredefinedScopes() { return new Object(); }
304 	}
305 
306 	@Configuration
307 	public static class ScopedConfigurationClass {
308 
309 		@Bean
310 		@MyScope
scopedClass()311 		public TestBean scopedClass() {
312 			TestBean tb = new TestBean();
313 			tb.setName(flag);
314 			return tb;
315 		}
316 
317 		@Bean
318 		@MyScope
scopedInterface()319 		public ITestBean scopedInterface() {
320 			TestBean tb = new TestBean();
321 			tb.setName(flag);
322 			return tb;
323 		}
324 
325 		@Bean
326 		@MyProxiedScope
scopedProxyInterface()327 		public ITestBean scopedProxyInterface() {
328 			TestBean tb = new TestBean();
329 			tb.setName(flag);
330 			return tb;
331 		}
332 
333 		@MyProxiedScope
scopedProxyClass()334 		public TestBean scopedProxyClass() {
335 			TestBean tb = new TestBean();
336 			tb.setName(flag);
337 			return tb;
338 		}
339 
340 		@Bean
singletonWithScopedClassDep()341 		public TestBean singletonWithScopedClassDep() {
342 			TestBean singleton = new TestBean();
343 			singleton.setSpouse(scopedProxyClass());
344 			return singleton;
345 		}
346 
347 		@Bean
singletonWithScopedInterfaceDep()348 		public TestBean singletonWithScopedInterfaceDep() {
349 			TestBean singleton = new TestBean();
350 			singleton.setSpouse(scopedProxyInterface());
351 			return singleton;
352 		}
353 	}
354 
355 
356 	@Target({ElementType.METHOD})
357 	@Retention(RetentionPolicy.RUNTIME)
358 	@Scope(SCOPE)
359 	@interface MyScope {
360 	}
361 
362 
363 	@Target({ElementType.METHOD})
364 	@Retention(RetentionPolicy.RUNTIME)
365 	@Bean
366 	@Scope(value=SCOPE, proxyMode=ScopedProxyMode.TARGET_CLASS)
367 	@interface MyProxiedScope {
368 	}
369 
370 
371 	/**
372 	 * Simple scope implementation which creates object based on a flag.
373 	 * @author Costin Leau
374 	 * @author Chris Beams
375 	 */
376 	static class CustomScope implements org.springframework.beans.factory.config.Scope {
377 
378 		public boolean createNewScope = true;
379 
380 		private Map<String, Object> beans = new HashMap<String, Object>();
381 
get(String name, ObjectFactory<?> objectFactory)382 		public Object get(String name, ObjectFactory<?> objectFactory) {
383 			if (createNewScope) {
384 				beans.clear();
385 				// reset the flag back
386 				createNewScope = false;
387 			}
388 
389 			Object bean = beans.get(name);
390 			// if a new object is requested or none exists under the current
391 			// name, create one
392 			if (bean == null) {
393 				beans.put(name, objectFactory.getObject());
394 			}
395 
396 			return beans.get(name);
397 		}
398 
getConversationId()399 		public String getConversationId() {
400 			return null;
401 		}
402 
registerDestructionCallback(String name, Runnable callback)403 		public void registerDestructionCallback(String name, Runnable callback) {
404 			// do nothing
405 		}
406 
remove(String name)407 		public Object remove(String name) {
408 			return beans.remove(name);
409 		}
410 
resolveContextualObject(String key)411 		public Object resolveContextualObject(String key) {
412 			return null;
413 		}
414 	}
415 
416 }
417