+ Added 'proxyMode' attribute to @Scope annotation

+ Eliminated @ScopedProxy in favor of @Scope(proxyMode=NO|INTERFACES|TARGET_CLASS)
This commit is contained in:
Chris Beams 2009-03-07 04:54:31 +00:00
parent 9735c8024c
commit 3231f458c8
7 changed files with 73 additions and 165 deletions

View File

@ -27,6 +27,7 @@ import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -128,12 +129,11 @@ public final class BeanMethod {
if (Modifier.isFinal(getModifiers())) if (Modifier.isFinal(getModifiers()))
problemReporter.error(new FinalMethodError()); problemReporter.error(new FinalMethodError());
if (this.getAnnotation(ScopedProxy.class) == null)
return;
Scope scope = this.getAnnotation(Scope.class); Scope scope = this.getAnnotation(Scope.class);
if(scope == null || scope.equals(SINGLETON) || scope.equals(PROTOTYPE)) if(scope != null
problemReporter.error(new InvalidScopedProxyDeclarationError(this)); && scope.proxyMode() != ScopedProxyMode.NO
&& (scope.value().equals(SINGLETON) || scope.value().equals(PROTOTYPE)))
problemReporter.error(new InvalidScopedProxyDeclarationError(this));
} }
@Override @Override
@ -197,4 +197,15 @@ public final class BeanMethod {
} }
} }
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"))
);
}
}
} }

View File

@ -18,7 +18,9 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
// TODO: SJC-242 document BeanHandler // TODO: SJC-242 document BeanHandler
@ -27,6 +29,9 @@ public class BeanRegistrar implements BeanDefinitionRegistrar {
private static final Log logger = LogFactory.getLog(BeanRegistrar.class); 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 <var>member</var> is a method and is annotated (directly or indirectly) * Ensures that <var>member</var> is a method and is annotated (directly or indirectly)
* with {@link Bean @Bean}. * with {@link Bean @Bean}.
@ -110,18 +115,17 @@ public class BeanRegistrar implements BeanDefinitionRegistrar {
if (hasText(destroyMethodName)) if (hasText(destroyMethodName))
beanDef.setDestroyMethodName(destroyMethodName); beanDef.setDestroyMethodName(destroyMethodName);
// is this method annotated with @ScopedProxy? // is this method annotated with @Scope(scopedProxy=...)?
ScopedProxy scopedProxy = method.getAnnotation(ScopedProxy.class); if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
if (scopedProxy != null) {
RootBeanDefinition targetDef = beanDef; RootBeanDefinition targetDef = beanDef;
// //
// Create a scoped proxy definition for the original bean name, // Create a scoped proxy definition for the original bean name,
// "hiding" the target bean in an internal target definition. // "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); RootBeanDefinition scopedProxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName); scopedProxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName);
if (scopedProxy.proxyTargetClass()) if (scope.proxyMode() == ScopedProxyMode.TARGET_CLASS)
targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); targetDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we // ScopedFactoryBean's "proxyTargetClass" default is TRUE, so we
// don't need to set it explicitly here. // 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)); + "could be found in %s or its ancestry", beanName, registry));
} }
/**
* Return the <i>hidden</i> 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 <i>hidden</i> bean name
*/
public static String resolveHiddenScopedProxyBeanName(String originalBeanName) {
Assert.hasText(originalBeanName);
return TARGET_NAME_PREFIX.concat(originalBeanName);
}
} }
/** /**

View File

@ -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"))
);
}
}

View File

@ -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 &lt;aop:scoped-proxy/&gt; 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.
*
* <p/>Used with scoped beans (non-singleton and non-prototype).</p>
*
* <pre class="code">
* &#064;Configuration
* public class ScopedConfig {
*
* &#064;Bean(scope = &quot;myScope&quot;)
* &#064;ScopedProxy
* public SomeBean someBean() {
* return new SomeBean();
*  }
*
* &#064;Bean
* public SomeOtherBean() {
* return new AnotherBean(someBean());
* }
* }
* </pre>
*
* <p>See Spring reference <a href="http://static.springframework.org/spring/docs/2.5.x/reference/">
* documentation</a> for more <a
* href="http://static.springframework.org/spring/docs/2.5.x/reference/beans.html#beans-factory-scopes-other-injection">
* details</a>.</p>
*
* @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 <i>hidden</i> 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 <i>hidden</i> bean name
*/
public static String resolveHiddenScopedProxyBeanName(String originalBeanName) {
Assert.hasText(originalBeanName);
return TARGET_NAME_PREFIX.concat(originalBeanName);
}
}
}

View File

@ -24,7 +24,8 @@ import net.sf.cglib.proxy.MethodProxy;
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.config.java.Bean; import org.springframework.config.java.Bean;
import org.springframework.config.java.BeanRegistrar; 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; 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 { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String beanName = getBeanName(method); String beanName = getBeanName(method);
boolean isScopedProxy = Scope scope = AnnotationUtils.findAnnotation(method, Scope.class);
(AnnotationUtils.findAnnotation(method, ScopedProxy.class) != null); boolean isScopedProxy = (scope != null && scope.proxyMode() != ScopedProxyMode.NO);
String scopedBeanName = String scopedBeanName =
ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); BeanRegistrar.resolveHiddenScopedProxyBeanName(beanName);
if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName)) if (isScopedProxy && beanFactory.isCurrentlyInCreation(scopedBeanName))
beanName = scopedBeanName; beanName = scopedBeanName;

View File

@ -23,14 +23,15 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.aop.scope.ScopedObject; 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.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.config.java.Bean; import org.springframework.config.java.Bean;
import org.springframework.config.java.BeanRegistrar;
import org.springframework.config.java.Configuration; import org.springframework.config.java.Configuration;
import org.springframework.config.java.ScopedProxy;
import org.springframework.config.java.StandardScopes; import org.springframework.config.java.StandardScopes;
import org.springframework.config.java.support.ConfigurationClassPostProcessor; 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 org.springframework.context.support.GenericApplicationContext;
import test.beans.ITestBean; 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 * Tests that scopes are properly supported by using a custom Scope implementations
* {@link ScopedProxy} declarations. * and scoped proxy {@link Bean} declarations.
* *
* @see ScopeIntegrationTests * @see ScopeIntegrationTests
* @author Costin Leau * @author Costin Leau
@ -68,7 +69,7 @@ public class ScopingTests {
customScope = null; 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(); DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
if(customScope != null) if(customScope != null)
beanFactory.registerScope(SCOPE, customScope); beanFactory.registerScope(SCOPE, customScope);
@ -112,13 +113,13 @@ public class ScopingTests {
@Test @Test
public void testScopedProxyOnNonBeanAnnotatedMethod() throws Exception { public void testScopedProxyOnSingletonBeanMethod() throws Exception {
// should throw - @ScopedProxy should not be applied on singleton/prototype beans // should throw - scoped proxies should not be applied on singleton/prototype beans
try { try {
createContext(null, InvalidProxyOnPredefinedScopesConfiguration.class); createContext(null, InvalidProxyOnPredefinedScopesConfiguration.class);
fail("exception expected"); fail("exception expected");
} catch (BeanDefinitionParsingException ex) { } 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 beanName = "scopedProxyInterface";
String scopedBeanName = ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); String scopedBeanName = BeanRegistrar.resolveHiddenScopedProxyBeanName(beanName);
// get hidden bean // get hidden bean
assertEquals(flag, spouse.getName()); assertEquals(flag, spouse.getName());
@ -178,7 +179,7 @@ public class ScopingTests {
String beanName = "scopedProxyClass"; String beanName = "scopedProxyClass";
String scopedBeanName = ScopedProxy.Util.resolveHiddenScopedProxyBeanName(beanName); String scopedBeanName = BeanRegistrar.resolveHiddenScopedProxyBeanName(beanName);
// get hidden bean // get hidden bean
assertEquals(flag, spouse.getName()); assertEquals(flag, spouse.getName());
@ -208,7 +209,7 @@ public class ScopingTests {
public void testScopedConfigurationBeanDefinitionCount() throws Exception { public void testScopedConfigurationBeanDefinitionCount() throws Exception {
// count the beans // count the beans
// 6 @Beans + 1 Configuration + 2 @ScopedProxy // 6 @Beans + 1 Configuration + 2 scoped proxy
assertThat(ctx.getBeanDefinitionCount(), equalTo(9)); assertThat(ctx.getBeanDefinitionCount(), equalTo(9));
} }
@ -241,8 +242,7 @@ public class ScopingTests {
static class ScopeTestConfiguration { static class ScopeTestConfiguration {
@Bean @Bean
@org.springframework.context.annotation.Scope(StandardScopes.SESSION) @Scope(value=StandardScopes.SESSION, proxyMode=ScopedProxyMode.INTERFACES)
@ScopedProxy
public Foo foo() { public Foo foo() {
return new Foo(); return new Foo();
} }
@ -312,22 +312,16 @@ public class ScopingTests {
assertNotSame(message, newBean2, newBean3); assertNotSame(message, newBean2, newBean3);
} }
@Configuration
public static class InvalidProxyObjectConfiguration {
@ScopedProxy
public Object invalidProxyObject() { return new Object(); }
}
@Configuration @Configuration
public static class InvalidProxyOnPredefinedScopesConfiguration { public static class InvalidProxyOnPredefinedScopesConfiguration {
@ScopedProxy @Bean @Bean @Scope(proxyMode=ScopedProxyMode.INTERFACES)
public Object invalidProxyOnPredefinedScopes() { return new Object(); } public Object invalidProxyOnPredefinedScopes() { return new Object(); }
} }
@Configuration @Configuration
public static class ScopedConfigurationClass { public static class ScopedConfigurationClass {
@Bean @Bean
@org.springframework.context.annotation.Scope(SCOPE) @Scope(SCOPE)
public TestBean scopedClass() { public TestBean scopedClass() {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
tb.setName(flag); tb.setName(flag);
@ -335,7 +329,7 @@ public class ScopingTests {
} }
@Bean @Bean
@org.springframework.context.annotation.Scope(SCOPE) @Scope(SCOPE)
public ITestBean scopedInterface() { public ITestBean scopedInterface() {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
tb.setName(flag); tb.setName(flag);
@ -343,17 +337,15 @@ public class ScopingTests {
} }
@Bean @Bean
@org.springframework.context.annotation.Scope(SCOPE) @Scope(value=SCOPE, proxyMode=ScopedProxyMode.TARGET_CLASS)
@ScopedProxy(proxyTargetClass = false)
public ITestBean scopedProxyInterface() { public ITestBean scopedProxyInterface() {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
tb.setName(flag); tb.setName(flag);
return tb; return tb;
} }
@ScopedProxy
@Bean @Bean
@org.springframework.context.annotation.Scope(SCOPE) @Scope(value=SCOPE, proxyMode=ScopedProxyMode.TARGET_CLASS)
public TestBean scopedProxyClass() { public TestBean scopedProxyClass() {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
tb.setName(flag); tb.setName(flag);

View File

@ -45,4 +45,16 @@ public @interface Scope {
*/ */
String value() default BeanDefinition.SCOPE_SINGLETON; 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.
*
* <p>Defaults to {@link ScopedProxyMode#NO}, indicating no scoped proxy
* should be created.
*
* <p>Analogous to {@literal <aop:scoped-proxy/>} support in XML. Valid
* only in conjunction with a non-singleton, non-prototype {@link #value()}.
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.NO;
} }