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

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.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 <var>member</var> 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 <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.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;

View File

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

View File

@ -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.
*
* <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;
}