From 13dfa11def3fec960e86c19fb194c3e47fe0fc34 Mon Sep 17 00:00:00 2001 From: Mark Pollack Date: Fri, 13 Mar 2009 18:14:40 +0000 Subject: [PATCH] Add support for @ScopedProxy for factory beans using the @FactoryBean annotation within a @Component Add missing unit tests --- .../ClassPathBeanDefinitionScanner.java | 24 ++-- ...athScanningCandidateComponentProvider.java | 37 ++++++ .../context/annotation/BeanAge.java | 16 +++ ...PathFactoryBeanDefinitionScannerTests.java | 109 ++++++++++++++++++ .../annotation4/FactoryMethodComponent.java | 79 +++++++++++++ .../context/annotation4/SimpleBean.java | 37 ++++++ 6 files changed, 293 insertions(+), 9 deletions(-) create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/BeanAge.java create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathFactoryBeanDefinitionScannerTests.java create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation4/FactoryMethodComponent.java create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation4/SimpleBean.java diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index 24b3021016c..13093718310 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -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 beanDefinitions) { //TODO refactor increment index count as part of naming strategy. - int count = 0; Set factoryBeanDefinitions = new LinkedHashSet(); for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitions) { Set 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. diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 0dd7a419351..e673a1da778 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -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 excludeFilters = new LinkedList(); + 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 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; + } + } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanAge.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanAge.java new file mode 100644 index 00000000000..514404b5c83 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanAge.java @@ -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(); +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathFactoryBeanDefinitionScannerTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathFactoryBeanDefinitionScannerTests.java new file mode 100644 index 00000000000..3d42775c483 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ClassPathFactoryBeanDefinitionScannerTests.java @@ -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 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)); + + + + + + } +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation4/FactoryMethodComponent.java b/org.springframework.context/src/test/java/org/springframework/context/annotation4/FactoryMethodComponent.java new file mode 100644 index 00000000000..bc29fa2b559 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation4/FactoryMethodComponent.java @@ -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. + +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation4/SimpleBean.java b/org.springframework.context/src/test/java/org/springframework/context/annotation4/SimpleBean.java new file mode 100644 index 00000000000..ddd52aafbf5 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation4/SimpleBean.java @@ -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"); + } +}