Add support for @ScopedProxy for factory beans using the @FactoryBean annotation within a @Component

Add missing unit tests
This commit is contained in:
Mark Pollack 2009-03-13 18:14:40 +00:00
parent bf31766ff8
commit 13dfa11def
6 changed files with 293 additions and 9 deletions

View File

@ -65,8 +65,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
private boolean includeAnnotationConfig = true;
/**
* Create a new ClassPathBeanDefinitionScanner for the given bean factory.
* @param registry the BeanFactory to load bean definitions into,
@ -221,14 +220,23 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
protected void postProcessComponentBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
//TODO refactor increment index count as part of naming strategy.
int count = 0;
Set<BeanDefinitionHolder> factoryBeanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) {
Set<BeanDefinition> candidates = findCandidateFactoryMethods(beanDefinitionHolder);
for (BeanDefinition candidate : candidates ) {
//TODO refactor to introduce naming strategy and some sanity checks.
String beanName = beanDefinitionHolder.getBeanName() + "$" + candidate.getFactoryMethodName() + "#" + count++;
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
for (BeanDefinition candidate : candidates ) {
BeanDefinitionHolder definitionHolder;
if (candidate.getBeanClassName().equals("org.springframework.aop.scope.ScopedProxyFactoryBean")){
String scopedFactoryBeanName = "scopedTarget." + candidate.getPropertyValues().getPropertyValue("targetBeanName").getValue();
definitionHolder = new BeanDefinitionHolder(candidate, scopedFactoryBeanName);
} else {
String configurationComponentBeanName = beanDefinitionHolder.getBeanName();
String factoryMethodName = candidate.getFactoryMethodName();
String beanName = createFactoryBeanName(configurationComponentBeanName, factoryMethodName);
definitionHolder = new BeanDefinitionHolder(candidate, beanName);
}
factoryBeanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
@ -236,8 +244,6 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
beanDefinitions.addAll(factoryBeanDefinitions);
}
/**
* Apply further settings to the given bean definition,
* beyond the contents retrieved from scanning the component class.

View File

@ -27,6 +27,7 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.scope.ScopedProxyFactoryBean;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.Qualifier;
@ -34,6 +35,8 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
@ -77,6 +80,8 @@ public class ClassPathScanningCandidateComponentProvider implements ResourceLoad
protected static final String QUALIFIER_CLASS_NAME = "org.springframework.beans.factory.annotation.Qualifier";
protected static final String SCOPE_CLASS_NAME = "org.springframework.context.annotation.Scope";
protected static final String SCOPEDPROXY_CLASS_NAME = "org.springframework.beans.factory.annotation.ScopedProxy";
protected final Log logger = LogFactory.getLog(getClass());
@ -90,6 +95,8 @@ public class ClassPathScanningCandidateComponentProvider implements ResourceLoad
private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();
private int factoryBeanCount = 0;
/**
* Create a ClassPathScanningCandidateComponentProvider.
@ -259,10 +266,33 @@ public class ClassPathScanningCandidateComponentProvider implements ResourceLoad
factoryBeanDef.setResource(containingBeanDef.getResource());
factoryBeanDef.setSource(containingBeanDef.getSource());
if (debugEnabled) {
logger.debug("Identified candidate factory method in class: " + resource);
}
candidates.add(factoryBeanDef);
RootBeanDefinition scopedFactoryBeanDef = null;
if (methodMetadata.hasAnnotation(SCOPEDPROXY_CLASS_NAME)) {
//TODO validate that @ScopedProxy isn't applied to singleton/prototype beans.
Map<String, Object> attributes = methodMetadata.getAnnotationAttributes(SCOPEDPROXY_CLASS_NAME);
scopedFactoryBeanDef = new RootBeanDefinition(ScopedProxyFactoryBean.class);
String t= scopedFactoryBeanDef.getBeanClassName();
String targetBeanName = createFactoryBeanName(beanDefinitionHolder.getBeanName(), factoryBeanDef.getFactoryMethodName());
scopedFactoryBeanDef.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName);
//TODO handle cglib options
// scopedFactoryBeanDef.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE);
scopedFactoryBeanDef.setAutowireCandidate(false);
scopedFactoryBeanDef.setResource(containingBeanDef.getResource());
scopedFactoryBeanDef.setSource(containingBeanDef.getSource());
candidates.add(scopedFactoryBeanDef);
}
}
else {
if (traceEnabled) {
@ -365,4 +395,11 @@ public class ClassPathScanningCandidateComponentProvider implements ResourceLoad
return (beanDefinition.getMetadata().isConcrete() && beanDefinition.getMetadata().isIndependent());
}
protected String createFactoryBeanName(String configurationComponentBeanName, String factoryMethodName) {
//TODO consider adding hex string and passing in definition object.
String beanName = configurationComponentBeanName + "$" + factoryMethodName;
return beanName;
}
}

View File

@ -0,0 +1,16 @@
package org.springframework.context.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Qualifier;
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface BeanAge {
int value();
}

View File

@ -0,0 +1,109 @@
/*
* 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.context.annotation;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.Set;
import junit.framework.TestCase;
import org.springframework.aop.scope.ScopedObject;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.SimpleMapScope;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.context.annotation4.FactoryMethodComponent;
import org.springframework.context.support.GenericApplicationContext;
public class ClassPathFactoryBeanDefinitionScannerTests extends TestCase {
private static final String BASE_PACKAGE = FactoryMethodComponent.class.getPackage().getName();
private static final int NUM_DEFAULT_BEAN_DEFS = 4;
private static final int NUM_FACTORY_METHODS = 5; // @ScopedProxy creates another
private static final int NUM_COMPONENT_DEFS = 1;
public void testSingletonScopedFactoryMethod()
{
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
SimpleMapScope scope = new SimpleMapScope();
context.getBeanFactory().registerScope("request", scope);
int beanCount = scanner.scan(BASE_PACKAGE);
assertEquals(NUM_FACTORY_METHODS + NUM_COMPONENT_DEFS + NUM_DEFAULT_BEAN_DEFS, beanCount);
assertTrue(context.containsBean("factoryMethodComponent"));
assertTrue(context.containsBean("factoryMethodComponent$staticInstance"));
assertTrue(context.containsBean("factoryMethodComponent$getPublicInstance"));
TestBean staticTestBean = (TestBean)context.getBean("factoryMethodComponent$staticInstance");//1
assertEquals("staticInstance", staticTestBean.getName());
TestBean staticTestBean2 = (TestBean)context.getBean("factoryMethodComponent$staticInstance");//1
assertSame(staticTestBean, staticTestBean2);
TestBean tb = (TestBean)context.getBean("factoryMethodComponent$getPublicInstance"); //2
assertEquals("publicInstance", tb.getName());
TestBean tb2 = (TestBean)context.getBean("factoryMethodComponent$getPublicInstance"); //2
assertEquals("publicInstance", tb2.getName());
assertSame(tb2, tb);
//Were qualifiers applied to bean definition
ConfigurableListableBeanFactory cbf = (ConfigurableListableBeanFactory)context.getAutowireCapableBeanFactory();
AbstractBeanDefinition abd = (AbstractBeanDefinition)cbf.getBeanDefinition("factoryMethodComponent$getPublicInstance"); //2
Set<AutowireCandidateQualifier> qualifierSet = abd.getQualifiers();
assertEquals(1, qualifierSet.size());
tb = (TestBean)context.getBean("factoryMethodComponent$getProtectedInstance"); //3
assertEquals("protectedInstance", tb.getName());
tb2 = (TestBean)context.getBean("factoryMethodComponent$getProtectedInstance"); //3
assertEquals("protectedInstance", tb2.getName());
assertSame(tb2, tb);
tb = (TestBean)context.getBean("factoryMethodComponent$getPrivateInstance"); //4
assertEquals("privateInstance", tb.getName());
assertEquals(0, tb.getAge());
tb2 = (TestBean)context.getBean("factoryMethodComponent$getPrivateInstance"); //4
assertEquals(1, tb2.getAge());
assertNotSame(tb2, tb);
Object bean = context.getBean("scopedTarget.factoryMethodComponent$requestScopedInstance"); //5
assertNotNull(bean);
assertTrue(bean instanceof ScopedObject);
//Scope assertions
assertTrue(AopUtils.isCglibProxy(bean));
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.context.annotation4;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.FactoryMethod;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.ScopedProxy;
import org.springframework.context.annotation.BeanAge;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
* Class used to test the functionality of @FactoryMethod bean definitions declared inside
* a Spring @Component class.
*
* @author Mark Pollack
*/
@Component
public class FactoryMethodComponent {
private static TestBean staticTestBean = new TestBean("staticInstance",1);
@Autowired @Qualifier("public")
public TestBean autowiredTestBean;
private static int i;
@FactoryMethod @Qualifier("static")
public static TestBean staticInstance()
{
return staticTestBean;
}
public static TestBean nullInstance()
{
return null;
}
@FactoryMethod @Qualifier("public")
public TestBean getPublicInstance() {
return new TestBean("publicInstance");
}
@FactoryMethod @BeanAge(1)
protected TestBean getProtectedInstance() {
return new TestBean("protectedInstance", 1);
}
@FactoryMethod @Scope("prototype")
private TestBean getPrivateInstance() {
return new TestBean("privateInstance", i++);
}
@FactoryMethod @Scope("request") @ScopedProxy
public TestBean requestScopedInstance()
{
TestBean testBean = new TestBean("requestScopedInstance", 3);
return testBean;
}
//TODO method for test that fails if use @ScopedProxy with singleton scope.
}

View File

@ -0,0 +1,37 @@
/*
* 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.context.annotation4;
import org.springframework.beans.TestBean;
import org.springframework.beans.factory.annotation.FactoryMethod;
/**
* Class to test that @FactoryMethods are detected only when inside a class with an @Component
* class annotation.
*
* @author Mark Pollack
*/
public class SimpleBean {
// This should *not* recognized as a @FactoryMethod since it does not reside inside an @Component
@FactoryMethod
public TestBean getPublicInstance() {
return new TestBean("publicInstance");
}
}