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
This commit is contained in:
parent
e753f23110
commit
eb0ab8431b
|
@ -170,8 +170,7 @@ public interface BeanFactory {
|
|||
* <p>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
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>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<String> lookupMethodsChecked =
|
||||
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(64));
|
||||
|
||||
private final Map<Class<?>, Constructor<?>[]> candidateConstructorsCache =
|
||||
new ConcurrentHashMap<Class<?>, 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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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).
|
||||
*
|
||||
* <p>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 "";
|
||||
|
||||
}
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
* <p>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.
|
||||
* <p>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() + "'";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() + "'";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -680,7 +680,12 @@
|
|||
<xsd:attribute name="name" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The name of the lookup method. This method must take no arguments.
|
||||
The name of the lookup method. This method may have arguments which
|
||||
will be passed on to the target constructor or factory method. Note
|
||||
that 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.
|
||||
Consider using the @Lookup annotation for more specific demarcation.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
@ -688,9 +693,10 @@
|
|||
<xsd:annotation>
|
||||
<xsd:documentation><![CDATA[
|
||||
The name of the bean in the current or ancestor factories that
|
||||
the lookup method should resolve to. Often this bean will be a
|
||||
the lookup method should resolve to. Usually this bean will be a
|
||||
prototype, in which case the lookup method will return a distinct
|
||||
instance on every invocation. This is useful for single-threaded objects.
|
||||
instance on every invocation. If not specified, the lookup method's
|
||||
return type will be used for a type-based lookup.
|
||||
]]></xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">
|
||||
|
||||
<bean id="abstractBean" class="org.springframework.beans.factory.support.LookupMethodTests$AbstractBean">
|
||||
<lookup-method name="get"/> <!-- applying to overloaded methods, and based on return type since no bean name is given -->
|
||||
<lookup-method name="getOneArgument" bean="testBean"/>
|
||||
<lookup-method name="getTwoArguments" bean="testBean"/>
|
||||
</bean>
|
||||
|
||||
<bean id="testBean" class="org.springframework.tests.sample.beans.TestBean" scope="prototype"/>
|
||||
|
||||
</beans>
|
Loading…
Reference in New Issue