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 96077eb48d1..ddb4bdf4952 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 @@ -27,6 +27,7 @@ import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.core.io.FileSystemResource; import org.springframework.util.Assert; @@ -128,12 +129,11 @@ public final class BeanMethod { if (Modifier.isFinal(getModifiers())) problemReporter.error(new FinalMethodError()); - if (this.getAnnotation(ScopedProxy.class) == null) - return; - Scope scope = this.getAnnotation(Scope.class); - if(scope == null || scope.equals(SINGLETON) || scope.equals(PROTOTYPE)) - problemReporter.error(new InvalidScopedProxyDeclarationError(this)); + if(scope != null + && scope.proxyMode() != ScopedProxyMode.NO + && (scope.value().equals(SINGLETON) || scope.value().equals(PROTOTYPE))) + problemReporter.error(new InvalidScopedProxyDeclarationError(this)); } @Override @@ -196,5 +196,16 @@ public final class BeanMethod { super(format("method '%s' may not be final. remove the final modifier to continue", getName()), new Location(new FileSystemResource("/dev/null"))); } } + + public class InvalidScopedProxyDeclarationError extends Problem { + public InvalidScopedProxyDeclarationError(BeanMethod method) { + super( + String.format("method %s contains an invalid annotation declaration: scoped proxies " + + "cannot be created for singleton/prototype beans", method.getName()), + new Location(new FileSystemResource("/dev/null")) + ); + } + + } } \ No newline at end of file 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 266123ef05c..dbed9f03293 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 @@ -18,7 +18,9 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; // TODO: SJC-242 document BeanHandler @@ -26,6 +28,9 @@ import org.springframework.core.annotation.AnnotationUtils; public class BeanRegistrar implements BeanDefinitionRegistrar { private static final Log logger = LogFactory.getLog(BeanRegistrar.class); + + /** Prefix used when registering the target object for a scoped proxy. */ + private static final String TARGET_NAME_PREFIX = "scopedTarget."; /** * Ensures that member is a method and is annotated (directly or indirectly) @@ -110,18 +115,17 @@ public class BeanRegistrar implements BeanDefinitionRegistrar { if (hasText(destroyMethodName)) beanDef.setDestroyMethodName(destroyMethodName); - // is this method annotated with @ScopedProxy? - ScopedProxy scopedProxy = method.getAnnotation(ScopedProxy.class); - if (scopedProxy != null) { + // is this method annotated with @Scope(scopedProxy=...)? + if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) { 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); + String targetBeanName = resolveHiddenScopedProxyBeanName(beanName); RootBeanDefinition scopedProxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName); - if (scopedProxy.proxyTargetClass()) + if (scope.proxyMode() == ScopedProxyMode.TARGET_CLASS) 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. @@ -191,6 +195,19 @@ public class BeanRegistrar implements BeanDefinitionRegistrar { + "could be found in %s or its ancestry", beanName, registry)); } + /** + * 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/InvalidScopedProxyDeclarationError.java b/org.springframework.config.java/src/main/java/org/springframework/config/java/InvalidScopedProxyDeclarationError.java deleted file mode 100644 index 78894e33c05..00000000000 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/InvalidScopedProxyDeclarationError.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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; - -import org.springframework.beans.factory.parsing.Location; -import org.springframework.beans.factory.parsing.Problem; -import org.springframework.core.io.FileSystemResource; - - -public class InvalidScopedProxyDeclarationError extends Problem { - public InvalidScopedProxyDeclarationError(BeanMethod method) { - super( - String.format("method %s contains an invalid annotation declaration: @%s " - + "cannot be used on a singleton/prototype bean", method.getName(), ScopedProxy.class - .getSimpleName()), - new Location(new FileSystemResource("/dev/null")) - ); - } - -} 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 deleted file mode 100644 index 92c94e59710..00000000000 --- a/org.springframework.config.java/src/main/java/org/springframework/config/java/ScopedProxy.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 a031d8caf32..c8d7d2b7738 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,7 +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.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.core.annotation.AnnotationUtils; @@ -46,11 +47,11 @@ class BeanMethodInterceptor extends AbstractMethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { String beanName = getBeanName(method); - boolean isScopedProxy = - (AnnotationUtils.findAnnotation(method, ScopedProxy.class) != null); + Scope scope = AnnotationUtils.findAnnotation(method, Scope.class); + boolean isScopedProxy = (scope != null && scope.proxyMode() != ScopedProxyMode.NO); String scopedBeanName = - ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); + BeanRegistrar.resolveHiddenScopedProxyBeanName(beanName); if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName)) beanName = scopedBeanName; 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 index 99f21a4e90d..8843958eb02 100644 --- 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 @@ -23,14 +23,15 @@ 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.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.config.java.Bean; +import org.springframework.config.java.BeanRegistrar; import org.springframework.config.java.Configuration; -import org.springframework.config.java.ScopedProxy; import org.springframework.config.java.StandardScopes; import org.springframework.config.java.support.ConfigurationClassPostProcessor; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.context.support.GenericApplicationContext; import test.beans.ITestBean; @@ -40,8 +41,8 @@ import test.common.scope.CustomScope; /** - * Tests that scopes are properly supported by using a custom {@link Scope} and - * {@link ScopedProxy} declarations. + * Tests that scopes are properly supported by using a custom Scope implementations + * and scoped proxy {@link Bean} declarations. * * @see ScopeIntegrationTests * @author Costin Leau @@ -68,7 +69,7 @@ public class ScopingTests { customScope = null; } - private GenericApplicationContext createContext(Scope customScope, Class> configClass) { + private GenericApplicationContext createContext(org.springframework.beans.factory.config.Scope customScope, Class> configClass) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); if(customScope != null) beanFactory.registerScope(SCOPE, customScope); @@ -112,13 +113,13 @@ public class ScopingTests { @Test - public void testScopedProxyOnNonBeanAnnotatedMethod() throws Exception { - // should throw - @ScopedProxy should not be applied on singleton/prototype beans + public void testScopedProxyOnSingletonBeanMethod() throws Exception { + // should throw - scoped proxies should not be applied on singleton/prototype beans try { createContext(null, InvalidProxyOnPredefinedScopesConfiguration.class); fail("exception expected"); } catch (BeanDefinitionParsingException ex) { - assertTrue(ex.getMessage().contains("cannot be used on a singleton/prototype bean")); + assertTrue(ex.getMessage().contains("scoped proxies cannot be created for singleton/prototype beans")); } } @@ -144,7 +145,7 @@ public class ScopingTests { String beanName = "scopedProxyInterface"; - String scopedBeanName = ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); + String scopedBeanName = BeanRegistrar.resolveHiddenScopedProxyBeanName(beanName); // get hidden bean assertEquals(flag, spouse.getName()); @@ -178,7 +179,7 @@ public class ScopingTests { String beanName = "scopedProxyClass"; - String scopedBeanName = ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); + String scopedBeanName = BeanRegistrar.resolveHiddenScopedProxyBeanName(beanName); // get hidden bean assertEquals(flag, spouse.getName()); @@ -208,7 +209,7 @@ public class ScopingTests { public void testScopedConfigurationBeanDefinitionCount() throws Exception { // count the beans - // 6 @Beans + 1 Configuration + 2 @ScopedProxy + // 6 @Beans + 1 Configuration + 2 scoped proxy assertThat(ctx.getBeanDefinitionCount(), equalTo(9)); } @@ -241,8 +242,7 @@ public class ScopingTests { static class ScopeTestConfiguration { @Bean - @org.springframework.context.annotation.Scope(StandardScopes.SESSION) - @ScopedProxy + @Scope(value=StandardScopes.SESSION, proxyMode=ScopedProxyMode.INTERFACES) public Foo foo() { return new Foo(); } @@ -312,22 +312,16 @@ public class ScopingTests { assertNotSame(message, newBean2, newBean3); } - @Configuration - public static class InvalidProxyObjectConfiguration { - @ScopedProxy - public Object invalidProxyObject() { return new Object(); } - } - @Configuration public static class InvalidProxyOnPredefinedScopesConfiguration { - @ScopedProxy @Bean + @Bean @Scope(proxyMode=ScopedProxyMode.INTERFACES) public Object invalidProxyOnPredefinedScopes() { return new Object(); } } @Configuration public static class ScopedConfigurationClass { @Bean - @org.springframework.context.annotation.Scope(SCOPE) + @Scope(SCOPE) public TestBean scopedClass() { TestBean tb = new TestBean(); tb.setName(flag); @@ -335,7 +329,7 @@ public class ScopingTests { } @Bean - @org.springframework.context.annotation.Scope(SCOPE) + @Scope(SCOPE) public ITestBean scopedInterface() { TestBean tb = new TestBean(); tb.setName(flag); @@ -343,17 +337,15 @@ public class ScopingTests { } @Bean - @org.springframework.context.annotation.Scope(SCOPE) - @ScopedProxy(proxyTargetClass = false) + @Scope(value=SCOPE, proxyMode=ScopedProxyMode.TARGET_CLASS) public ITestBean scopedProxyInterface() { TestBean tb = new TestBean(); tb.setName(flag); return tb; } - @ScopedProxy @Bean - @org.springframework.context.annotation.Scope(SCOPE) + @Scope(value=SCOPE, proxyMode=ScopedProxyMode.TARGET_CLASS) public TestBean scopedProxyClass() { TestBean tb = new TestBean(); tb.setName(flag); diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java index dce7f65b628..4342dda1303 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Scope.java @@ -44,5 +44,17 @@ public @interface Scope { * @return the desired scope */ String value() default BeanDefinition.SCOPE_SINGLETON; + + /** + * Specifies whether a component should be configured as a scoped proxy + * and if so, whether the proxy should be interface-based or subclass-based. + * + *Defaults to {@link ScopedProxyMode#NO}, indicating no scoped proxy + * should be created. + * + *
Analogous to {@literal