From eb0ab8431bc967aff0f186a4b0bc4562e073fd01 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 14 Aug 2014 23:49:05 +0200 Subject: [PATCH] Lookup methods can support arguments, find a target bean based on the return type, and be identified by an @Lookup annotation Issue: SPR-7431 Issue: SPR-5192 --- .../beans/factory/BeanFactory.java | 3 +- .../AutowiredAnnotationBeanPostProcessor.java | 42 ++++++- .../beans/factory/annotation/Lookup.java | 65 ++++++++++ .../factory/support/AbstractBeanFactory.java | 9 +- ...CglibSubclassingInstantiationStrategy.java | 46 +++---- .../beans/factory/support/LookupOverride.java | 57 ++++++--- .../factory/support/ReplaceOverride.java | 14 +-- .../beans/factory/xml/spring-beans-4.1.xsd | 12 +- .../annotation/LookupAnnotationTests.java | 116 ++++++++++++++++++ .../factory/support/LookupMethodTests.java | 107 ++++++++++++++++ .../factory/support/lookupMethodTests.xml | 14 +++ 11 files changed, 428 insertions(+), 57 deletions(-) create mode 100644 spring-beans/src/main/java/org/springframework/beans/factory/annotation/Lookup.java create mode 100644 spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java create mode 100644 spring-beans/src/test/java/org/springframework/beans/factory/support/LookupMethodTests.java create mode 100644 spring-beans/src/test/java/org/springframework/beans/factory/support/lookupMethodTests.xml diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java index a2a1648eb74..428999cd7bc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java @@ -170,8 +170,7 @@ public interface BeanFactory { *

Allows for specifying explicit constructor arguments / factory method arguments, * overriding the specified default arguments (if any) in the bean definition. * @param name the name of the bean to retrieve - * @param args arguments to use if creating a prototype using explicit arguments to a - * static factory method. It is invalid to use a non-null args value in any other case. + * @param args arguments to use if creating a prototype using explicit arguments * @return an instance of the bean * @throws NoSuchBeanDefinitionException if there is no such bean definition * @throws BeanDefinitionStoreException if arguments have been given but diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index 189a76c81e2..0681c0ffe78 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -25,6 +25,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -44,10 +45,12 @@ import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.LookupOverride; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.BridgeMethodResolver; @@ -96,6 +99,12 @@ import org.springframework.util.StringUtils; * thus the latter configuration will override the former for properties wired through * both approaches. * + *

In addition to regular injection points as discussed above, this post-processor + * also handles Spring's {@link Lookup @Lookup} annotation which identifies lookup + * methods to be replaced by the container at runtime. This is essentially a type-safe + * version of {@code getBean(Class, args)} and {@code getBean(String, args)}, + * See {@link Lookup @Lookup's javadoc} for details. + * * @author Juergen Hoeller * @author Mark Fisher * @since 2.5 @@ -119,6 +128,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean private ConfigurableListableBeanFactory beanFactory; + private final Set lookupMethodsChecked = + Collections.newSetFromMap(new ConcurrentHashMap(64)); + private final Map, Constructor[]> candidateConstructorsCache = new ConcurrentHashMap, Constructor[]>(64); @@ -224,7 +236,28 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean } @Override - public Constructor[] determineCandidateConstructors(Class beanClass, String beanName) throws BeansException { + public Constructor[] determineCandidateConstructors(Class beanClass, final String beanName) throws BeansException { + if (!this.lookupMethodsChecked.contains(beanName)) { + ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + Lookup lookup = method.getAnnotation(Lookup.class); + if (lookup != null) { + LookupOverride override = new LookupOverride(method, lookup.value()); + try { + RootBeanDefinition mbd = (RootBeanDefinition) beanFactory.getMergedBeanDefinition(beanName); + mbd.getMethodOverrides().addOverride(override); + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanCreationException(beanName, + "Cannot apply @Lookup to beans without corresponding bean definition"); + } + } + } + }); + this.lookupMethodsChecked.add(beanName); + } + // Quick check on the concurrent map first, with minimal locking. Constructor[] candidateConstructors = this.candidateConstructorsCache.get(beanClass); if (candidateConstructors == null) { @@ -239,7 +272,8 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean AnnotationAttributes annotation = findAutowiredAnnotation(candidate); if (annotation != null) { if (requiredConstructor != null) { - throw new BeanCreationException("Invalid autowire-marked constructor: " + candidate + + throw new BeanCreationException(beanName, + "Invalid autowire-marked constructor: " + candidate + ". Found another constructor with 'required' Autowired annotation: " + requiredConstructor); } @@ -250,10 +284,10 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean boolean required = determineRequiredStatus(annotation); if (required) { if (!candidates.isEmpty()) { - throw new BeanCreationException( + throw new BeanCreationException(beanName, "Invalid autowire-marked constructors: " + candidates + ". Found another constructor with 'required' Autowired annotation: " + - requiredConstructor); + candidate); } requiredConstructor = candidate; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Lookup.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Lookup.java new file mode 100644 index 00000000000..6b76d234727 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Lookup.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2014 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.beans.factory.annotation; + +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; + +/** + * An annotation that indicates 'lookup' methods, to be overridden by the container + * to redirect them back to the {@link org.springframework.beans.factory.BeanFactory} + * for a {@code getBean} call. This is essentially an annotation-based version of the + * XML {@code lookup-method} attribute, resulting in the same runtime arrangement. + * + *

The resolution of the target bean can either be based on the return type + * ({@code getBean(Class)}) or on a suggested bean name ({@code getBean(String)}), + * in both cases passing the method's arguments to the {@code getBean} call + * for applying them as target factory method arguments or constructor arguments. + * + *

Such lookup methods can have default (stub) implementations that will simply + * get replaced by the container, or they can be declared as abstract - for the + * container to fill them in at runtime. In both cases, the container will generate + * runtime subclasses of the method's containing class via CGLIB, which is why such + * lookup methods can only work on beans that the container instantiates through + * regular constructors (i.e. lookup methods cannot get replaced on beans returned + * from factory methods where we can't dynamically provide a subclass for them). + * + *

Note: When used with component scanning or any other mechanism that filters + * out abstract beans, provide stub implementations of your lookup methods to be + * able to declare them as concrete classes. + * + * @author Juergen Hoeller + * @since 4.1 + * @see org.springframework.beans.factory.BeanFactory#getBean(Class, Object...) + * @see org.springframework.beans.factory.BeanFactory#getBean(String, Object...) + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Lookup { + + /** + * This annotation attribute may suggest a target bean name to look up. + * If not specified, the target bean will be resolved based on the + * annotated method's return type declaration. + */ + String value() default ""; + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 8fa8bc1b0a0..526ebd73c55 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -285,8 +285,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp if (dependsOn != null) { for (String dependsOnBean : dependsOn) { if (isDependent(beanName, dependsOnBean)) { - throw new BeanCreationException("Circular depends-on relationship between '" + - beanName + "' and '" + dependsOnBean + "'"); + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Circular depends-on relationship between '" + beanName + "' and '" + dependsOnBean + "'"); } registerDependentBean(dependsOnBean, beanName); getBean(dependsOnBean); @@ -1274,7 +1274,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp // Check validity of the usage of the args parameter. This can // only be used for prototypes constructed via a factory method. if (args != null && !mbd.isPrototype()) { - throw new BeanDefinitionStoreException( + throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, "Can only specify arguments for the getBean method when referring to a prototype bean definition"); } } @@ -1625,8 +1625,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp * instantiation within this class is performed by this method. * @param beanName the name of the bean * @param mbd the merged bean definition for the bean - * @param args arguments to use if creating a prototype using explicit arguments to a - * static factory method. This parameter must be {@code null} except in this case. + * @param args arguments to use if creating a prototype using explicit arguments * @return a new instance of the bean * @throws BeanCreationException if the bean could not be created */ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java index 740be138d72..8821bcab39c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -25,7 +25,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; - import org.springframework.cglib.core.SpringNamingPolicy; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.CallbackFilter; @@ -34,6 +33,7 @@ import org.springframework.cglib.proxy.Factory; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import org.springframework.cglib.proxy.NoOp; +import org.springframework.util.StringUtils; /** * Default object instantiation strategy for use in BeanFactories. @@ -89,14 +89,13 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt */ private static class CglibSubclassCreator { - private static final Class[] CALLBACK_TYPES = new Class[] { NoOp.class, - LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class }; + private static final Class[] CALLBACK_TYPES = new Class[] + {NoOp.class, LookupOverrideMethodInterceptor.class, ReplaceOverrideMethodInterceptor.class}; private final RootBeanDefinition beanDefinition; private final BeanFactory owner; - CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) { this.beanDefinition = beanDefinition; this.owner = owner; @@ -113,7 +112,6 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt */ Object instantiate(Constructor ctor, Object[] args) { Class subclass = createEnhancedSubclass(this.beanDefinition); - Object instance; if (ctor == null) { instance = BeanUtils.instantiate(subclass); @@ -123,19 +121,17 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt Constructor enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes()); instance = enhancedSubclassConstructor.newInstance(args); } - catch (Exception e) { + catch (Exception ex) { throw new BeanInstantiationException(this.beanDefinition.getBeanClass(), String.format( - "Failed to invoke construcor for CGLIB enhanced subclass [%s]", subclass.getName()), e); + "Failed to invoke constructor for CGLIB enhanced subclass [%s]", subclass.getName()), ex); } } - // SPR-10785: set callbacks directly on the instance instead of in the // enhanced class (via the Enhancer) in order to avoid memory leaks. Factory factory = (Factory) instance; - factory.setCallbacks(new Callback[] { NoOp.INSTANCE,// - new LookupOverrideMethodInterceptor(beanDefinition, owner),// - new ReplaceOverrideMethodInterceptor(beanDefinition, owner) }); - + factory.setCallbacks(new Callback[] {NoOp.INSTANCE, + new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner), + new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)}); return instance; } @@ -153,6 +149,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt } } + /** * Class providing hashCode and equals methods required by CGLIB to * ensure that CGLIB doesn't generate a distinct class per bean. @@ -162,7 +159,6 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt private final RootBeanDefinition beanDefinition; - CglibIdentitySupport(RootBeanDefinition beanDefinition) { this.beanDefinition = beanDefinition; } @@ -173,8 +169,8 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt @Override public boolean equals(Object other) { - return other.getClass().equals(this.getClass()) - && ((CglibIdentitySupport) other).getBeanDefinition().equals(this.getBeanDefinition()); + return (getClass().equals(other.getClass()) && + this.beanDefinition.equals(((CglibIdentitySupport) other).beanDefinition)); } @Override @@ -183,6 +179,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt } } + /** * CGLIB callback for filtering method interception behavior. */ @@ -190,7 +187,6 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt private static final Log logger = LogFactory.getLog(MethodOverrideCallbackFilter.class); - MethodOverrideCallbackFilter(RootBeanDefinition beanDefinition) { super(beanDefinition); } @@ -210,11 +206,12 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt else if (methodOverride instanceof ReplaceOverride) { return METHOD_REPLACER; } - throw new UnsupportedOperationException("Unexpected MethodOverride subclass: " - + methodOverride.getClass().getName()); + throw new UnsupportedOperationException("Unexpected MethodOverride subclass: " + + methodOverride.getClass().getName()); } } + /** * CGLIB MethodInterceptor to override methods, replacing them with an * implementation that returns a bean looked up in the container. @@ -223,7 +220,6 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt private final BeanFactory owner; - LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) { super(beanDefinition); this.owner = owner; @@ -233,10 +229,17 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { // Cast is safe, as CallbackFilter filters are used selectively. LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method); - return this.owner.getBean(lo.getBeanName()); + Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don't insist on args at all + if (StringUtils.hasText(lo.getBeanName())) { + return this.owner.getBean(lo.getBeanName(), argsToUse); + } + else { + return this.owner.getBean(method.getReturnType(), argsToUse); + } } } + /** * CGLIB MethodInterceptor to override methods, replacing them with a call * to a generic MethodReplacer. @@ -245,7 +248,6 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt private final BeanFactory owner; - ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) { super(beanDefinition); this.owner = owner; @@ -255,7 +257,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable { ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method); // TODO could cache if a singleton for minor performance optimization - MethodReplacer mr = owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class); + MethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class); return mr.reimplement(obj, method, args); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java index 75077729d55..5800d38a3f6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/LookupOverride.java @@ -17,8 +17,8 @@ package org.springframework.beans.factory.support; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; -import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** @@ -34,20 +34,33 @@ public class LookupOverride extends MethodOverride { private final String beanName; + private Method method; + /** * Construct a new LookupOverride. - * @param methodName the name of the method to override. - * This method must have no arguments. - * @param beanName the name of the bean in the current BeanFactory - * that the overridden method should return + * @param methodName the name of the method to override + * @param beanName the name of the bean in the current {@code BeanFactory} + * that the overridden method should return (may be {@code null}) */ public LookupOverride(String methodName, String beanName) { super(methodName); - Assert.notNull(beanName, "Bean name must not be null"); this.beanName = beanName; } + /** + * Construct a new LookupOverride. + * @param method the method to override + * @param beanName the name of the bean in the current {@code BeanFactory} + * that the overridden method should return (may be {@code null}) + */ + public LookupOverride(Method method, String beanName) { + super(method.getName()); + this.method = method; + this.beanName = beanName; + } + + /** * Return the name of the bean that should be returned by this method. */ @@ -56,22 +69,33 @@ public class LookupOverride extends MethodOverride { } /** - * Match the method of the given name, with no parameters. + * Match the specified method by {@link Method} reference or method name. + *

For backwards compatibility reasons, in a scenario with overloaded + * non-abstract methods of the given name, only the no-arg variant of a + * method will be turned into a container-driven lookup method. + *

In case of a provided {@link Method}, only straight matches will + * be considered, usually demarcated by the {@code @Lookup} annotation. */ @Override public boolean matches(Method method) { - return (method.getName().equals(getMethodName()) && method.getParameterTypes().length == 0); + if (this.method != null) { + return method.equals(this.method); + } + else { + return (method.getName().equals(getMethodName()) && (!isOverloaded() || + Modifier.isAbstract(method.getModifiers()) || method.getParameterTypes().length == 0)); + } } - @Override - public String toString() { - return "LookupOverride for method '" + getMethodName() + "'; will return bean '" + this.beanName + "'"; - } @Override public boolean equals(Object other) { - return (other instanceof LookupOverride && super.equals(other) && - ObjectUtils.nullSafeEquals(this.beanName, ((LookupOverride) other).beanName)); + if (!(other instanceof LookupOverride) || !super.equals(other)) { + return false; + } + LookupOverride that = (LookupOverride) other; + return (ObjectUtils.nullSafeEquals(this.method, that.method) && + ObjectUtils.nullSafeEquals(this.beanName, that.beanName)); } @Override @@ -79,4 +103,9 @@ public class LookupOverride extends MethodOverride { return (29 * super.hashCode() + ObjectUtils.nullSafeHashCode(this.beanName)); } + @Override + public String toString() { + return "LookupOverride for method '" + getMethodName() + "'"; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java index e570a24c573..957d3d17795 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 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. @@ -52,6 +52,7 @@ public class ReplaceOverride extends MethodOverride { this.methodReplacerBeanName = methodReplacerBeanName; } + /** * Return the name of the bean implementing MethodReplacer. */ @@ -97,12 +98,6 @@ public class ReplaceOverride extends MethodOverride { } - @Override - public String toString() { - return "Replace override for method '" + getMethodName() + "; will call bean '" + - this.methodReplacerBeanName + "'"; - } - @Override public boolean equals(Object other) { if (!(other instanceof ReplaceOverride) || !super.equals(other)) { @@ -121,4 +116,9 @@ public class ReplaceOverride extends MethodOverride { return hashCode; } + @Override + public String toString() { + return "Replace override for method '" + getMethodName() + "'"; + } + } diff --git a/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-4.1.xsd b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-4.1.xsd index 78ef2dcc101..9a9cb52bb11 100644 --- a/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-4.1.xsd +++ b/spring-beans/src/main/resources/org/springframework/beans/factory/xml/spring-beans-4.1.xsd @@ -680,7 +680,12 @@ @@ -688,9 +693,10 @@ diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java new file mode 100644 index 00000000000..d29770227a7 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/LookupAnnotationTests.java @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2014 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.beans.factory.annotation; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.tests.sample.beans.TestBean; + +import static org.junit.Assert.*; + +/** + * @author Karl Pietrzak + * @author Juergen Hoeller + */ +public class LookupAnnotationTests { + + private DefaultListableBeanFactory beanFactory; + + + @Before + public void setUp() { + beanFactory = new DefaultListableBeanFactory(); + AutowiredAnnotationBeanPostProcessor aabpp = new AutowiredAnnotationBeanPostProcessor(); + aabpp.setBeanFactory(beanFactory); + beanFactory.addBeanPostProcessor(aabpp); + beanFactory.registerBeanDefinition("abstractBean", new RootBeanDefinition(AbstractBean.class)); + RootBeanDefinition tbd = new RootBeanDefinition(TestBean.class); + tbd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + beanFactory.registerBeanDefinition("testBean", tbd); + } + + + @Test + public void testWithoutConstructorArg() { + AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); + assertNotNull(bean); + Object expected = bean.get(); + assertEquals(TestBean.class, expected.getClass()); + } + + @Test + public void testWithOverloadedArg() { + AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); + assertNotNull(bean); + TestBean expected = bean.get("haha"); + assertEquals(TestBean.class, expected.getClass()); + assertEquals("haha", expected.getName()); + } + + @Test + public void testWithOneConstructorArg() { + AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); + assertNotNull(bean); + TestBean expected = bean.getOneArgument("haha"); + assertEquals(TestBean.class, expected.getClass()); + assertEquals("haha", expected.getName()); + } + + @Test + public void testWithTwoConstructorArg() { + AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); + assertNotNull(bean); + TestBean expected = bean.getTwoArguments("haha", 72); + assertEquals(TestBean.class, expected.getClass()); + assertEquals("haha", expected.getName()); + assertEquals(72, expected.getAge()); + } + + @Test + public void testWithThreeArgsShouldFail() { + AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); + assertNotNull(bean); + try { + bean.getThreeArguments("name", 1, 2); + fail("TestBean does not have a three arg constructor so this should not have worked"); + } + catch (AbstractMethodError ex) { + } + } + + + public static abstract class AbstractBean { + + @Lookup + public abstract TestBean get(); + + @Lookup + public abstract TestBean get(String name); // overloaded + + @Lookup + public abstract TestBean getOneArgument(String name); + + @Lookup + public abstract TestBean getTwoArguments(String name, int age); + + public abstract TestBean getThreeArguments(String name, int age, int anotherArg); + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/LookupMethodTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/LookupMethodTests.java new file mode 100644 index 00000000000..e9ce2b82006 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/LookupMethodTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2002-2014 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.beans.factory.support; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.core.io.ClassPathResource; +import org.springframework.tests.sample.beans.TestBean; + +import static org.junit.Assert.*; + +/** + * @author Karl Pietrzak + * @author Juergen Hoeller + */ +public class LookupMethodTests { + + private DefaultListableBeanFactory beanFactory; + + + @Before + public void setUp() { + beanFactory = new DefaultListableBeanFactory(); + XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); + reader.loadBeanDefinitions(new ClassPathResource("lookupMethodTests.xml", getClass())); + } + + + @Test + public void testWithoutConstructorArg() { + AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); + assertNotNull(bean); + Object expected = bean.get(); + assertEquals(TestBean.class, expected.getClass()); + } + + @Test + public void testWithOverloadedArg() { + AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); + assertNotNull(bean); + TestBean expected = bean.get("haha"); + assertEquals(TestBean.class, expected.getClass()); + assertEquals("haha", expected.getName()); + } + + @Test + public void testWithOneConstructorArg() { + AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); + assertNotNull(bean); + TestBean expected = bean.getOneArgument("haha"); + assertEquals(TestBean.class, expected.getClass()); + assertEquals("haha", expected.getName()); + } + + @Test + public void testWithTwoConstructorArg() { + AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); + assertNotNull(bean); + TestBean expected = bean.getTwoArguments("haha", 72); + assertEquals(TestBean.class, expected.getClass()); + assertEquals("haha", expected.getName()); + assertEquals(72, expected.getAge()); + } + + @Test + public void testWithThreeArgsShouldFail() { + AbstractBean bean = (AbstractBean) beanFactory.getBean("abstractBean"); + assertNotNull(bean); + try { + bean.getThreeArguments("name", 1, 2); + fail("TestBean does not have a three arg constructor so this should not have worked"); + } + catch (AbstractMethodError ex) { + } + } + + + public static abstract class AbstractBean { + + public abstract TestBean get(); + + public abstract TestBean get(String name); // overloaded + + public abstract TestBean getOneArgument(String name); + + public abstract TestBean getTwoArguments(String name, int age); + + public abstract TestBean getThreeArguments(String name, int age, int anotherArg); + } + +} diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/lookupMethodTests.xml b/spring-beans/src/test/java/org/springframework/beans/factory/support/lookupMethodTests.xml new file mode 100644 index 00000000000..54f6dcba005 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/lookupMethodTests.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + +