diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/Bean.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/Bean.java index 5189481da95..e9772936392 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/Bean.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/Bean.java @@ -57,6 +57,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; * @author Rod Johnson * @author Costin Leau * @author Chris Beams + * @since 3.0 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/BeanMethod.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/BeanMethod.java index 983597ff8bc..23f5508a12a 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/BeanMethod.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/BeanMethod.java @@ -25,7 +25,6 @@ import java.util.List; import org.springframework.util.Assert; -/** TODO: JAVADOC */ public final class BeanMethod implements Validatable { private final String name; @@ -91,8 +90,8 @@ public final class BeanMethod implements Validatable { T anno = getAnnotation(annoType); if (anno == null) - throw new IllegalStateException(format("annotation %s not found on %s", annoType.getSimpleName(), - this)); + throw new IllegalStateException( + format("annotation %s not found on %s", annoType.getSimpleName(), this)); return anno; } @@ -123,8 +122,6 @@ public final class BeanMethod implements Validatable { } public void validate(List errors) { -// for (Validator validator : validators) -// validator.validate(this, errors); if (Modifier.isPrivate(getModifiers())) errors.add(new PrivateMethodError()); @@ -163,7 +160,7 @@ public final class BeanMethod implements Validatable { public String toString() { String returnTypeName = returnType == null ? "" : returnType.getSimpleName(); return String.format("%s: name=%s; returnType=%s; modifiers=%d", getClass().getSimpleName(), name, - returnTypeName, modifiers); + returnTypeName, modifiers); } @Override @@ -246,15 +243,14 @@ class BeanValidator implements Validator { public void validate(Object object, List errors) { BeanMethod method = (BeanMethod) object; - // TODO: re-enable for @ScopedProxy support - // if (method.getAnnotation(ScopedProxy.class) == null) - // return; - // - // Bean bean = method.getRequiredAnnotation(Bean.class); - // - // if (bean.scope().equals(DefaultScopes.SINGLETON) - // || bean.scope().equals(DefaultScopes.PROTOTYPE)) - // errors.add(new InvalidScopedProxyDeclarationError(method)); + if (method.getAnnotation(ScopedProxy.class) == null) + return; + + Bean bean = method.getRequiredAnnotation(Bean.class); + + if (bean.scope().equals(StandardScopes.SINGLETON) + || bean.scope().equals(StandardScopes.PROTOTYPE)) + errors.add(new InvalidScopedProxyDeclarationError(method)); } } diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/BeanRegistrar.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/BeanRegistrar.java index 30cb188f8e1..b9852c839df 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/BeanRegistrar.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/BeanRegistrar.java @@ -7,6 +7,8 @@ import java.lang.reflect.Method; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.aop.framework.autoproxy.AutoProxyUtils; +import org.springframework.aop.scope.ScopedProxyFactoryBean; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; @@ -50,7 +52,7 @@ public class BeanRegistrar implements BeanDefinitionRegistrar { if (bean.autowire() != AnnotationUtils.getDefaultValue(Bean.class, "autowire")) beanDef.setAutowireMode(bean.autowire().value()); else if (defaults.defaultAutowire() != AnnotationUtils.getDefaultValue(Configuration.class, - "defaultAutowire")) + "defaultAutowire")) beanDef.setAutowireMode(defaults.defaultAutowire().value()); String beanName = method.getName(); @@ -70,9 +72,8 @@ public class BeanRegistrar implements BeanDefinitionRegistrar { } // overriding is legal, return immediately - logger.info(format( - "Skipping loading bean definition for %s: a definition for bean '%s' already exists. " - + "This is likely due to an override in XML.", method, beanName)); + logger.info(format("Skipping loading bean definition for %s: a definition for bean " + + "'%s' already exists. This is likely due to an override in XML.", method, beanName)); return; } } @@ -104,39 +105,33 @@ public class BeanRegistrar implements BeanDefinitionRegistrar { if (hasText(destroyMethodName)) beanDef.setDestroyMethodName(destroyMethodName); - // TODO: re-enable for @ScopedProxy support // is this method annotated with @ScopedProxy? - // ScopedProxy scopedProxy = method.getAnnotation(ScopedProxy.class); - // if (scopedProxy != null) { - // RootBeanDefinition targetDef = beanDef; - // - // // Create a scoped proxy definition for the original bean name, - // // "hiding" the target bean in an internal target definition. - // String targetBeanName = - // ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); - // RootBeanDefinition scopedProxyDefinition = new - // RootBeanDefinition(ScopedProxyFactoryBean.class); - // scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", - // targetBeanName); - // - // if (scopedProxy.proxyTargetClass()) - // targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, - // Boolean.TRUE); - // // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we - // // don't need to set it explicitly here. - // else - // scopedProxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", - // Boolean.FALSE); - // - // // The target bean should be ignored in favor of the scoped proxy. - // targetDef.setAutowireCandidate(false); - // - // // Register the target bean as separate bean in the factory - // registry.registerBeanDefinition(targetBeanName, targetDef); - // - // // replace the original bean definition with the target one - // beanDef = scopedProxyDefinition; - // } + ScopedProxy scopedProxy = method.getAnnotation(ScopedProxy.class); + if (scopedProxy != null) { + RootBeanDefinition targetDef = beanDef; + // + // Create a scoped proxy definition for the original bean name, + // "hiding" the target bean in an internal target definition. + String targetBeanName = ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); + RootBeanDefinition scopedProxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); + scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName); + + if (scopedProxy.proxyTargetClass()) + targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); + // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we + // don't need to set it explicitly here. + else + scopedProxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE); + + // The target bean should be ignored in favor of the scoped proxy. + targetDef.setAutowireCandidate(false); + + // Register the target bean as separate bean in the factory + registry.registerBeanDefinition(targetBeanName, targetDef); + + // replace the original bean definition with the target one + beanDef = scopedProxyDefinition; + } // TODO: re-enable for @Meta support // does this bean method have any @Meta annotations? @@ -147,8 +142,8 @@ public class BeanRegistrar implements BeanDefinitionRegistrar { if (bean.dependsOn().length > 0) beanDef.setDependsOn(bean.dependsOn()); - logger.info(format("Registering bean definition for @Bean method %s.%s()", configClass.getName(), - beanName)); + logger.info(format("Registering bean definition for @Bean method %s.%s()", + configClass.getName(), beanName)); registry.registerBeanDefinition(beanName, beanDef); @@ -188,7 +183,7 @@ public class BeanRegistrar implements BeanDefinitionRegistrar { } while (clbf != null); throw new NoSuchBeanDefinitionException(format("No bean definition matching name '%s' " - + "could be found in %s or its ancestry", beanName, registry)); + + "could be found in %s or its ancestry", beanName, registry)); } } @@ -201,4 +196,4 @@ public class BeanRegistrar implements BeanDefinitionRegistrar { */ @SuppressWarnings("serial") class ConfigurationClassBeanDefinition extends RootBeanDefinition { -} \ No newline at end of file +} diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/Configuration.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/Configuration.java index 1ea137739c4..1c6756f581b 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/Configuration.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/Configuration.java @@ -46,6 +46,7 @@ import org.springframework.stereotype.Component; * * @author Rod Johnson * @author Chris Beams + * @since 3.0 */ @Component @Target( { ElementType.TYPE }) diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/InvalidScopedProxyDeclarationError.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/InvalidScopedProxyDeclarationError.java new file mode 100644 index 00000000000..4fc9dbe2e3d --- /dev/null +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/InvalidScopedProxyDeclarationError.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2009 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.config.java; + + +public class InvalidScopedProxyDeclarationError extends UsageError { + private final BeanMethod method; + + public InvalidScopedProxyDeclarationError(BeanMethod method) { + super(method.getDeclaringClass(), method.getLineNumber()); + this.method = method; + } + + @Override + public String getDescription() { + return String.format("method %s contains an invalid annotation declaration: @%s " + + "cannot be used on a singleton/prototype bean", method.getName(), ScopedProxy.class + .getSimpleName()); + } +} diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/ScopedProxy.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/ScopedProxy.java new file mode 100644 index 00000000000..92c94e59710 --- /dev/null +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/ScopedProxy.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2008 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.config.java; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.util.Assert; + + +/** + * Annotation identical in functionality with <aop:scoped-proxy/> tag. Provides a smart + * proxy backed by a scoped bean, which can be injected into object instances (usually singletons) + * allowing the same reference to be held while delegating method invocations to the backing, scoped + * beans. + * + *

Used with scoped beans (non-singleton and non-prototype).

+ * + *
+ *	@Configuration
+ *	public class ScopedConfig {
+ *
+ *		@Bean(scope = "myScope")
+ *		@ScopedProxy
+ *		public SomeBean someBean() {
+ *			return new SomeBean();
+ *    }
+ *
+ *		@Bean
+ *		public SomeOtherBean() {
+ *			return new AnotherBean(someBean());
+ *		}
+ *	}
+ * 
+ * + *

See Spring reference + * documentation for more + * details.

+ * + * @author Costin Leau + * @author Chris Beams + * @since 3.0 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ScopedProxy { + + /** + * Use CGLib-based class proxies (true) or JDK interface-based (false). + * + * Default is CGLib (true). + * @return + */ + boolean proxyTargetClass() default true; + + public static class Util { + + private static final String TARGET_NAME_PREFIX = "scopedTarget."; + + /** + * Return the hidden name based on a scoped proxy bean name. + * + * @param originalBeanName the scope proxy bean name as declared in the + * Configuration-annotated class + * + * @return the internally-used hidden bean name + */ + public static String resolveHiddenScopedProxyBeanName(String originalBeanName) { + Assert.hasText(originalBeanName); + return TARGET_NAME_PREFIX.concat(originalBeanName); + } + + } +} diff --git a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/BeanMethodInterceptor.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/BeanMethodInterceptor.java index 46b8bb483f1..a031d8caf32 100644 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/BeanMethodInterceptor.java +++ b/org.springframework.config.java/src/main/java/org/springframework/config/java/internal/enhancement/BeanMethodInterceptor.java @@ -24,6 +24,8 @@ import net.sf.cglib.proxy.MethodProxy; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.config.java.Bean; import org.springframework.config.java.BeanRegistrar; +import org.springframework.config.java.ScopedProxy; +import org.springframework.core.annotation.AnnotationUtils; /** @@ -44,14 +46,13 @@ class BeanMethodInterceptor extends AbstractMethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { String beanName = getBeanName(method); - // TODO: re-enable for @ScopedProxy support - // boolean isScopedProxy = (AnnotationUtils.findAnnotation(method, - // ScopedProxy.class) != null); - // - // String scopedBeanName = - // ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); - // if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName)) - // beanName = scopedBeanName; + boolean isScopedProxy = + (AnnotationUtils.findAnnotation(method, ScopedProxy.class) != null); + + String scopedBeanName = + ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); + if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName)) + beanName = scopedBeanName; if (factoryContainsBean(beanName)) { // we have an already existing cached instance of this bean -> retrieve it diff --git a/org.springframework.config.java/src/test/java/org/springframework/config/java/support/ConfigurationPostProcessorTests.java b/org.springframework.config.java/src/test/java/org/springframework/config/java/support/ConfigurationPostProcessorTests.java index b9ff55d7f07..cb69af19cdd 100644 --- a/org.springframework.config.java/src/test/java/org/springframework/config/java/support/ConfigurationPostProcessorTests.java +++ b/org.springframework.config.java/src/test/java/org/springframework/config/java/support/ConfigurationPostProcessorTests.java @@ -110,7 +110,7 @@ public class ConfigurationPostProcessorTests { * certain bean semantics, like singleton-scoping, scoped proxies, etc. * * Technically, {@link ConfigurationClassPostProcessor} could fail to enhance the - * registered Configuration classes, and many use cases would still work. + * registered Configuration classes and many use cases would still work. * Certain cases, however, like inter-bean singleton references would not. * We test for such a case below, and in doing so prove that enhancement is * working. @@ -142,5 +142,5 @@ public class ConfigurationPostProcessorTests { final Foo foo; public Bar(Foo foo) { this.foo = foo; } } - + } diff --git a/org.springframework.config.java/src/test/java/test/common/scope/CustomScope.java b/org.springframework.config.java/src/test/java/test/common/scope/CustomScope.java new file mode 100644 index 00000000000..fa3d0ad67ae --- /dev/null +++ b/org.springframework.config.java/src/test/java/test/common/scope/CustomScope.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2008 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 test.common.scope; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.Scope; + + +/** + * Simple scope implementation which creates object based on a flag. + * + * @author Costin Leau + * @author Chris Beams + */ +public class CustomScope implements Scope { + + public boolean createNewScope = true; + + private Map beans = new HashMap(); + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.Scope#get(java.lang.String, + * org.springframework.beans.factory.ObjectFactory) + */ + public Object get(String name, ObjectFactory objectFactory) { + if (createNewScope) { + beans.clear(); + // reset the flag back + createNewScope = false; + } + + Object bean = beans.get(name); + // if a new object is requested or none exists under the current + // name, create one + if (bean == null) { + beans.put(name, objectFactory.getObject()); + } + + return beans.get(name); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.Scope#getConversationId() + */ + public String getConversationId() { + return null; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String, + * java.lang.Runnable) + */ + public void registerDestructionCallback(String name, Runnable callback) { + // do nothing + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.Scope#remove(java.lang.String) + */ + public Object remove(String name) { + return beans.remove(name); + } + + public Object resolveContextualObject(String key) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/org.springframework.config.java/src/test/java/test/feature/lifecycle/scoping/ScopingTests.java b/org.springframework.config.java/src/test/java/test/feature/lifecycle/scoping/ScopingTests.java new file mode 100644 index 00000000000..a5e0eb40bd3 --- /dev/null +++ b/org.springframework.config.java/src/test/java/test/feature/lifecycle/scoping/ScopingTests.java @@ -0,0 +1,374 @@ +/* + * Copyright 2002-2008 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 test.feature.lifecycle.scoping; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.aop.scope.ScopedObject; +import org.springframework.beans.factory.config.Scope; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.config.java.Bean; +import org.springframework.config.java.Configuration; +import org.springframework.config.java.InvalidScopedProxyDeclarationError; +import org.springframework.config.java.MalformedConfigurationException; +import org.springframework.config.java.ScopedProxy; +import org.springframework.config.java.StandardScopes; +import org.springframework.config.java.support.ConfigurationClassPostProcessor; +import org.springframework.context.support.GenericApplicationContext; + +import test.beans.ITestBean; +import test.beans.TestBean; +import test.common.scope.CustomScope; + + + +/** + * Tests that scopes are properly supported by using a custom {@link Scope} and + * {@link ScopedProxy} declarations. + * + * @see ScopeIntegrationTests + * @author Costin Leau + * @author Chris Beams + */ +public class ScopingTests { + + public static String flag = "1"; + + private static final String SCOPE = "my scope"; + private CustomScope customScope; + private GenericApplicationContext ctx; + + @Before + public void setUp() throws Exception { + customScope = new CustomScope(); + ctx = createContext(customScope, ScopedConfigurationClass.class); + } + + @After + public void tearDown() throws Exception { + ctx.close(); + ctx = null; + customScope = null; + } + + private GenericApplicationContext createContext(Scope customScope, Class configClass) { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + if(customScope != null) + beanFactory.registerScope(SCOPE, customScope); + beanFactory.registerBeanDefinition("config", + rootBeanDefinition(configClass).getBeanDefinition()); + GenericApplicationContext ctx = new GenericApplicationContext(beanFactory); + ctx.addBeanFactoryPostProcessor(new ConfigurationClassPostProcessor()); + ctx.refresh(); + return ctx; + } + + + @Test + public void testScopeOnClasses() throws Exception { + genericTestScope("scopedClass"); + } + + + @Test + public void testScopeOnInterfaces() throws Exception { + genericTestScope("scopedInterface"); + } + + + @Test + public void testSameScopeOnDifferentBeans() throws Exception { + Object beanAInScope = ctx.getBean("scopedClass"); + Object beanBInScope = ctx.getBean("scopedInterface"); + + assertNotSame(beanAInScope, beanBInScope); + + customScope.createNewScope = true; + + Object newBeanAInScope = ctx.getBean("scopedClass"); + Object newBeanBInScope = ctx.getBean("scopedInterface"); + + assertNotSame(newBeanAInScope, newBeanBInScope); + assertNotSame(newBeanAInScope, beanAInScope); + assertNotSame(newBeanBInScope, beanBInScope); + } + + + @Test + public void testScopedProxyOnNonBeanAnnotatedMethod() throws Exception { + // should throw - @ScopedProxy should not be applied on singleton/prototype beans + try { + createContext(null, InvalidProxyOnPredefinedScopesConfiguration.class); + fail("exception expected"); + } catch (MalformedConfigurationException ex) { + assertTrue(ex.containsError(InvalidScopedProxyDeclarationError.class)); + } + } + + + @Test + public void testRawScopes() throws Exception { + + String beanName = "scopedProxyInterface"; + + // get hidden bean + Object bean = ctx.getBean("scopedTarget." + beanName); + + assertFalse(bean instanceof ScopedObject); + } + + + @Test + public void testScopedProxyConfiguration() throws Exception { + + TestBean singleton = (TestBean) ctx.getBean("singletonWithScopedInterfaceDep"); + ITestBean spouse = singleton.getSpouse(); + assertTrue("scoped bean is not wrapped by the scoped-proxy", spouse instanceof ScopedObject); + + String beanName = "scopedProxyInterface"; + + String scopedBeanName = ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); + + // get hidden bean + assertEquals(flag, spouse.getName()); + + ITestBean spouseFromBF = (ITestBean) ctx.getBean(scopedBeanName); + assertEquals(spouse.getName(), spouseFromBF.getName()); + // the scope proxy has kicked in + assertNotSame(spouse, spouseFromBF); + + // create a new bean + customScope.createNewScope = true; + + // get the bean again from the BF + spouseFromBF = (ITestBean) ctx.getBean(scopedBeanName); + // make sure the name has been updated + assertSame(spouse.getName(), spouseFromBF.getName()); + assertNotSame(spouse, spouseFromBF); + + // get the bean again + spouseFromBF = (ITestBean) ctx.getBean(scopedBeanName); + assertSame(spouse.getName(), spouseFromBF.getName()); + } + + + @Test + public void testScopedProxyConfigurationWithClasses() throws Exception { + + TestBean singleton = (TestBean) ctx.getBean("singletonWithScopedClassDep"); + ITestBean spouse = singleton.getSpouse(); + assertTrue("scoped bean is not wrapped by the scoped-proxy", spouse instanceof ScopedObject); + + String beanName = "scopedProxyClass"; + + String scopedBeanName = ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); + + // get hidden bean + assertEquals(flag, spouse.getName()); + + TestBean spouseFromBF = (TestBean) ctx.getBean(scopedBeanName); + assertEquals(spouse.getName(), spouseFromBF.getName()); + // the scope proxy has kicked in + assertNotSame(spouse, spouseFromBF); + + // create a new bean + customScope.createNewScope = true; + flag = "boo"; + + // get the bean again from the BF + spouseFromBF = (TestBean) ctx.getBean(scopedBeanName); + // make sure the name has been updated + assertSame(spouse.getName(), spouseFromBF.getName()); + assertNotSame(spouse, spouseFromBF); + + // get the bean again + spouseFromBF = (TestBean) ctx.getBean(scopedBeanName); + assertSame(spouse.getName(), spouseFromBF.getName()); + } + + + @Test + public void testScopedConfigurationBeanDefinitionCount() throws Exception { + + // count the beans + // 6 @Beans + 1 Configuration + 2 @ScopedProxy + assertThat(ctx.getBeanDefinitionCount(), equalTo(9)); + } + +// /** +// * SJC-254 caught a regression in handling scoped proxies starting in 1.0 m4. +// * The ScopedProxyFactoryBean object was having its scope set to that of its delegate +// * whereas it should have remained singleton. +// */ +// @Test +// public void sjc254() { +// JavaConfigWebApplicationContext ctx = new JavaConfigWebApplicationContext(); +// ctx.setConfigLocations(new String[] { ScopeTestConfiguration.class.getName() }); +// ctx.refresh(); +// +// // should be fine +// ctx.getBean(Bar.class); +// +// boolean threw = false; +// try { +// ctx.getBean(Foo.class); +// } catch (BeanCreationException ex) { +// if(ex.getCause() instanceof IllegalStateException) { +// threw = true; +// } +// } +// assertTrue(threw); +// } + + @Configuration + static class ScopeTestConfiguration { + + @Bean(scope = StandardScopes.SESSION) + @ScopedProxy + public Foo foo() { + return new Foo(); + } + + @Bean + public Bar bar() { + return new Bar(foo()); + } + } + + static class Foo { + public Foo() { + //System.out.println("created foo: " + this.getClass().getName()); + } + + public void doSomething() { + //System.out.println("interesting: " + this); + } + } + + static class Bar { + + private final Foo foo; + + public Bar(Foo foo) { + this.foo = foo; + //System.out.println("created bar: " + this); + } + + public Foo getFoo() { + return foo; + } + + } + + private void genericTestScope(String beanName) throws Exception { + String message = "scope is ignored"; + Object bean1 = ctx.getBean(beanName); + Object bean2 = ctx.getBean(beanName); + + assertSame(message, bean1, bean2); + + Object bean3 = ctx.getBean(beanName); + + assertSame(message, bean1, bean3); + + // make the scope create a new object + customScope.createNewScope = true; + + Object newBean1 = ctx.getBean(beanName); + assertNotSame(message, bean1, newBean1); + + Object sameBean1 = ctx.getBean(beanName); + + assertSame(message, newBean1, sameBean1); + + // make the scope create a new object + customScope.createNewScope = true; + + Object newBean2 = ctx.getBean(beanName); + assertNotSame(message, newBean1, newBean2); + + // make the scope create a new object .. again + customScope.createNewScope = true; + + Object newBean3 = ctx.getBean(beanName); + assertNotSame(message, newBean2, newBean3); + } + + @Configuration + public static class InvalidProxyObjectConfiguration { + @ScopedProxy + public Object invalidProxyObject() { return new Object(); } + } + + @Configuration + public static class InvalidProxyOnPredefinedScopesConfiguration { + @ScopedProxy @Bean + public Object invalidProxyOnPredefinedScopes() { return new Object(); } + } + + @Configuration + public static class ScopedConfigurationClass { + @Bean(scope = SCOPE) + public TestBean scopedClass() { + TestBean tb = new TestBean(); + tb.setName(flag); + return tb; + } + + @Bean(scope = SCOPE) + public ITestBean scopedInterface() { + TestBean tb = new TestBean(); + tb.setName(flag); + return tb; + } + + @Bean(scope = SCOPE) + @ScopedProxy(proxyTargetClass = false) + public ITestBean scopedProxyInterface() { + TestBean tb = new TestBean(); + tb.setName(flag); + return tb; + } + + @ScopedProxy + @Bean(scope = SCOPE) + public TestBean scopedProxyClass() { + TestBean tb = new TestBean(); + tb.setName(flag); + return tb; + } + + @Bean + public TestBean singletonWithScopedClassDep() { + TestBean singleton = new TestBean(); + singleton.setSpouse(scopedProxyClass()); + return singleton; + } + + @Bean + public TestBean singletonWithScopedInterfaceDep() { + TestBean singleton = new TestBean(); + singleton.setSpouse(scopedProxyInterface()); + return singleton; + } + } + +}